Bug 1773319 - Use wrapper around CompilationInput instead of UniquePtr r=arai

Embedders would not be able to use UniquePtr<CompilationInput> since
CompilationInput is opaque to them.

This also resolves a rooting hazard with the UniquePtr because the JS script
might have a 'uses asm' directive, which the hazard analysis reports could be a
problem if CompilationInput has GC pointers. We know the CompilationInput has
no GC pointers, however because we are only compiling to Stencil.

Differential Revision: https://phabricator.services.mozilla.com/D172336
This commit is contained in:
Bryan Thrall 2023-04-04 17:36:52 +00:00
parent 1ed143bc2a
commit 454b80333b
5 changed files with 161 additions and 57 deletions

View File

@ -12,6 +12,7 @@
#include "jspubtd.h"
#include "js/experimental/JSStencil.h"
#include "js/GCAnnotations.h"
#include "js/Modules.h"
#include "js/Stack.h"
#include "js/UniquePtr.h"
@ -19,8 +20,6 @@
namespace js {
class FrontendContext;
namespace frontend {
// TODO Create wrapper type around CompilationInput because it is opaque, which
// means UniquePtr can't perform deletion on it
struct CompilationInput;
} // namespace frontend
} // namespace js
@ -43,28 +42,96 @@ JS_PUBLIC_API bool SetSupportedImportAssertions(
JS::FrontendContext* fc,
const JS::ImportAssertionVector& supportedImportAssertions);
// Temporary storage used during compiling and preparing to instantiate a
// Stencil.
//
// Off-thread consumers can allocate this instance off main thread, and pass it
// back to the main thread, in order to reduce the main thread allocation.
struct CompilationStorage {
private:
// Owned CompilationInput.
//
// This uses raw pointer instead of UniquePtr because CompilationInput
// is opaque.
JS_HAZ_NON_GC_POINTER js::frontend::CompilationInput* input_ = nullptr;
friend JS_PUBLIC_API already_AddRefed<JS::Stencil>
CompileGlobalScriptToStencil(JS::FrontendContext* fc,
const JS::ReadOnlyCompileOptions& options,
JS::NativeStackLimit stackLimit,
JS::SourceText<mozilla::Utf8Unit>& srcBuf,
JS::CompilationStorage& compileStorage);
friend JS_PUBLIC_API already_AddRefed<JS::Stencil>
CompileGlobalScriptToStencil(JS::FrontendContext* fc,
const JS::ReadOnlyCompileOptions& options,
JS::NativeStackLimit stackLimit,
JS::SourceText<char16_t>& srcBuf,
JS::CompilationStorage& compileStorage);
friend JS_PUBLIC_API already_AddRefed<JS::Stencil>
CompileModuleScriptToStencil(JS::FrontendContext* fc,
const JS::ReadOnlyCompileOptions& options,
JS::NativeStackLimit stackLimit,
JS::SourceText<mozilla::Utf8Unit>& srcBuf,
JS::CompilationStorage& compileStorage);
friend JS_PUBLIC_API already_AddRefed<JS::Stencil>
CompileModuleScriptToStencil(JS::FrontendContext* fc,
const JS::ReadOnlyCompileOptions& options,
JS::NativeStackLimit stackLimit,
JS::SourceText<char16_t>& srcBuf,
JS::CompilationStorage& compileStorage);
friend JS_PUBLIC_API bool PrepareForInstantiate(
JS::FrontendContext* fc, JS::CompilationStorage& compileStorage,
JS::Stencil& stencil, JS::InstantiationStorage& storage);
public:
CompilationStorage() = default;
CompilationStorage(CompilationStorage&& other) : input_(other.input_) {
other.input_ = nullptr;
}
~CompilationStorage();
private:
CompilationStorage(const CompilationStorage& other) = delete;
void operator=(const CompilationStorage& aOther) = delete;
public:
bool hasInput() { return !!input_; }
js::frontend::CompilationInput& getInput() {
MOZ_ASSERT(hasInput());
return *input_;
}
void trace(JSTracer* trc);
};
extern JS_PUBLIC_API already_AddRefed<JS::Stencil> CompileGlobalScriptToStencil(
JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options,
JS::NativeStackLimit stackLimit, JS::SourceText<mozilla::Utf8Unit>& srcBuf,
js::UniquePtr<js::frontend::CompilationInput>& stencilInput);
JS::CompilationStorage& compileStorage);
extern JS_PUBLIC_API already_AddRefed<JS::Stencil> CompileGlobalScriptToStencil(
JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options,
JS::NativeStackLimit stackLimit, JS::SourceText<char16_t>& srcBuf,
js::UniquePtr<js::frontend::CompilationInput>& stencilInput);
JS::CompilationStorage& compileStorage);
extern JS_PUBLIC_API already_AddRefed<JS::Stencil> CompileModuleScriptToStencil(
JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options,
JS::NativeStackLimit stackLimit, JS::SourceText<mozilla::Utf8Unit>& srcBuf,
js::UniquePtr<js::frontend::CompilationInput>& stencilInput);
JS::CompilationStorage& compileStorage);
extern JS_PUBLIC_API already_AddRefed<JS::Stencil> CompileModuleScriptToStencil(
JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options,
JS::NativeStackLimit stackLimit, JS::SourceText<char16_t>& srcBuf,
js::UniquePtr<js::frontend::CompilationInput>& stencilInput);
JS::CompilationStorage& compileStorage);
extern JS_PUBLIC_API bool PrepareForInstantiate(
JS::FrontendContext* fc, js::frontend::CompilationInput& input,
JS::FrontendContext* fc, JS::CompilationStorage& compileStorage,
JS::Stencil& stencil, JS::InstantiationStorage& storage);
} // namespace JS

View File

@ -48,6 +48,8 @@ struct CompilationInput;
namespace JS {
struct CompilationStorage;
using Stencil = js::frontend::CompilationStencil;
using FrontendContext = js::FrontendContext;
@ -71,8 +73,8 @@ struct InstantiationStorage {
JSContext* cx, const InstantiateOptions& options, Stencil* stencil,
InstantiationStorage* storage);
friend JS_PUBLIC_API JS_PUBLIC_API bool PrepareForInstantiate(
JS::FrontendContext* fc, js::frontend::CompilationInput& input,
friend JS_PUBLIC_API bool PrepareForInstantiate(
JS::FrontendContext* fc, JS::CompilationStorage& compileStorage,
JS::Stencil& stencil, JS::InstantiationStorage& storage);
friend struct js::ParseTask;

View File

@ -6502,15 +6502,15 @@ static bool CompileToStencil(JSContext* cx, uint32_t argc, Value* vp) {
AutoReportFrontendContext fc(cx);
RefPtr<JS::Stencil> stencil;
JS::Rooted<UniquePtr<js::frontend::CompilationInput>> stencilInput(cx);
JS::CompilationStorage compileStorage;
if (isModule) {
stencil = JS::CompileModuleScriptToStencil(
&fc, options, cx->stackLimitForCurrentPrincipal(), srcBuf,
stencilInput.get());
compileStorage);
} else {
stencil = JS::CompileGlobalScriptToStencil(
&fc, options, cx->stackLimitForCurrentPrincipal(), srcBuf,
stencilInput.get());
compileStorage);
}
if (!stencil) {
return false;
@ -6522,8 +6522,7 @@ static bool CompileToStencil(JSContext* cx, uint32_t argc, Value* vp) {
JS::InstantiationStorage storage;
if (prepareForInstantiate) {
if (!JS::PrepareForInstantiate(&fc, *stencilInput.get(), *stencil,
storage)) {
if (!JS::PrepareForInstantiate(&fc, compileStorage, *stencil, storage)) {
return false;
}
}

View File

@ -35,18 +35,31 @@ JS_PUBLIC_API bool JS::SetSupportedImportAssertions(
return fc->setSupportedImportAssertions(supportedImportAssertions);
}
JS::CompilationStorage::~CompilationStorage() {
if (input_) {
js_delete(input_);
input_ = nullptr;
}
}
void JS::CompilationStorage::trace(JSTracer* trc) {
if (input_) {
input_->trace(trc);
}
}
template <typename CharT>
static already_AddRefed<JS::Stencil> CompileGlobalScriptToStencilImpl(
JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options,
JS::NativeStackLimit stackLimit, JS::SourceText<CharT>& srcBuf,
js::UniquePtr<js::frontend::CompilationInput>& stencilInput) {
js::frontend::CompilationInput*& stencilInput) {
ScopeKind scopeKind =
options.nonSyntacticScope ? ScopeKind::NonSyntactic : ScopeKind::Global;
JS::SourceText<CharT> data(std::move(srcBuf));
stencilInput =
fc->getAllocator()->make_unique<frontend::CompilationInput>(options);
MOZ_ASSERT(!stencilInput);
stencilInput = fc->getAllocator()->new_<frontend::CompilationInput>(options);
if (!stencilInput) {
return nullptr;
}
@ -64,12 +77,12 @@ template <typename CharT>
static already_AddRefed<JS::Stencil> CompileModuleScriptToStencilImpl(
JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& optionsInput,
JS::NativeStackLimit stackLimit, JS::SourceText<CharT>& srcBuf,
js::UniquePtr<js::frontend::CompilationInput>& stencilInput) {
js::frontend::CompilationInput*& stencilInput) {
JS::CompileOptions options(nullptr, optionsInput);
options.setModule();
stencilInput =
fc->getAllocator()->make_unique<frontend::CompilationInput>(options);
MOZ_ASSERT(!stencilInput);
stencilInput = fc->getAllocator()->new_<frontend::CompilationInput>(options);
if (!stencilInput) {
return nullptr;
}
@ -90,38 +103,58 @@ static already_AddRefed<JS::Stencil> CompileModuleScriptToStencilImpl(
already_AddRefed<JS::Stencil> JS::CompileGlobalScriptToStencil(
JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options,
JS::NativeStackLimit stackLimit, JS::SourceText<mozilla::Utf8Unit>& srcBuf,
js::UniquePtr<js::frontend::CompilationInput>& stencilInput) {
JS::CompilationStorage& compileStorage) {
return CompileGlobalScriptToStencilImpl(fc, options, stackLimit, srcBuf,
stencilInput);
compileStorage.input_);
}
already_AddRefed<JS::Stencil> JS::CompileGlobalScriptToStencil(
JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options,
JS::NativeStackLimit stackLimit, JS::SourceText<char16_t>& srcBuf,
js::UniquePtr<js::frontend::CompilationInput>& stencilInput) {
JS::CompilationStorage& compileStorage) {
return CompileGlobalScriptToStencilImpl(fc, options, stackLimit, srcBuf,
stencilInput);
compileStorage.input_);
}
already_AddRefed<JS::Stencil> JS::CompileModuleScriptToStencil(
JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& optionsInput,
JS::NativeStackLimit stackLimit, JS::SourceText<mozilla::Utf8Unit>& srcBuf,
js::UniquePtr<js::frontend::CompilationInput>& stencilInput) {
JS::CompilationStorage& compileStorage) {
return CompileModuleScriptToStencilImpl(fc, optionsInput, stackLimit, srcBuf,
stencilInput);
compileStorage.input_);
}
already_AddRefed<JS::Stencil> JS::CompileModuleScriptToStencil(
JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& optionsInput,
JS::NativeStackLimit stackLimit, JS::SourceText<char16_t>& srcBuf,
js::UniquePtr<js::frontend::CompilationInput>& stencilInput) {
JS::CompilationStorage& compileStorage) {
return CompileModuleScriptToStencilImpl(fc, optionsInput, stackLimit, srcBuf,
stencilInput);
compileStorage.input_);
}
bool JS::PrepareForInstantiate(JS::FrontendContext* fc, CompilationInput& input,
// We don't need to worry about GC if the CompilationInput has no GC pointers
static bool isGCSafe(js::frontend::CompilationInput* input_) {
if (!input_) {
return false;
}
bool isGlobalOrModule =
input_->target == CompilationInput::CompilationTarget::Global ||
input_->target == CompilationInput::CompilationTarget::Module;
bool scopeHasNoGC =
input_->enclosingScope.isStencil() || input_->enclosingScope.isNull();
bool scriptHasNoGC = input_->lazyOuterScript().isStencil() ||
input_->lazyOuterScript().isNull();
bool cacheHasNoGC = input_->atomCache.empty();
return isGlobalOrModule && scopeHasNoGC && scriptHasNoGC && cacheHasNoGC;
}
bool JS::PrepareForInstantiate(JS::FrontendContext* fc,
JS::CompilationStorage& compileStorage,
JS::Stencil& stencil,
JS::InstantiationStorage& storage) {
MOZ_ASSERT(isGCSafe(compileStorage.input_));
if (!storage.gcOutput_) {
storage.gcOutput_ =
fc->getAllocator()->new_<js::frontend::CompilationGCOutput>();
@ -129,6 +162,6 @@ bool JS::PrepareForInstantiate(JS::FrontendContext* fc, CompilationInput& input,
return false;
}
}
return CompilationStencil::prepareForInstantiate(fc, input.atomCache, stencil,
*storage.gcOutput_);
return CompilationStencil::prepareForInstantiate(
fc, compileStorage.input_->atomCache, stencil, *storage.gcOutput_);
}

View File

@ -52,9 +52,10 @@ bool testCompile() {
CHECK(buf16.init(cx, src_16.data(), src_16.length(),
JS::SourceOwnership::Borrowed));
js::UniquePtr<js::frontend::CompilationInput> stencilInput;
JS::CompilationStorage compileStorage;
RefPtr<JS::Stencil> stencil = CompileGlobalScriptToStencil(
fc, options, cx->stackLimitForCurrentPrincipal(), buf16, stencilInput);
fc, options, cx->stackLimitForCurrentPrincipal(), buf16,
compileStorage);
CHECK(stencil);
CHECK(stencil->scriptExtra.size() == 1);
CHECK(stencil->scriptExtra[0].extent.sourceStart == 0);
@ -62,7 +63,7 @@ bool testCompile() {
CHECK(stencil->scriptData.size() == 1);
CHECK(stencil->scriptData[0].hasSharedData()); // has generated bytecode
CHECK(stencil->scriptData[0].gcThingsLength == 1);
CHECK(stencilInput);
CHECK(compileStorage.hasInput());
}
{ // 8-bit characters
@ -70,9 +71,9 @@ bool testCompile() {
CHECK(
buf8.init(cx, src.data(), src.length(), JS::SourceOwnership::Borrowed));
js::UniquePtr<js::frontend::CompilationInput> stencilInput;
JS::CompilationStorage compileStorage;
RefPtr<JS::Stencil> stencil = CompileGlobalScriptToStencil(
fc, options, cx->stackLimitForCurrentPrincipal(), buf8, stencilInput);
fc, options, cx->stackLimitForCurrentPrincipal(), buf8, compileStorage);
CHECK(stencil);
CHECK(stencil->scriptExtra.size() == 1);
CHECK(stencil->scriptExtra[0].extent.sourceStart == 0);
@ -80,7 +81,7 @@ bool testCompile() {
CHECK(stencil->scriptData.size() == 1);
CHECK(stencil->scriptData[0].hasSharedData()); // has generated bytecode
CHECK(stencil->scriptData[0].gcThingsLength == 1);
CHECK(stencilInput);
CHECK(compileStorage.hasInput());
}
{ // propagates failures
@ -89,9 +90,10 @@ bool testCompile() {
CHECK(srcBuf.init(cx, badSrc.data(), badSrc.length(),
JS::SourceOwnership::Borrowed));
js::UniquePtr<js::frontend::CompilationInput> stencilInput;
JS::CompilationStorage compileStorage;
RefPtr<JS::Stencil> stencil = CompileGlobalScriptToStencil(
fc, options, cx->stackLimitForCurrentPrincipal(), srcBuf, stencilInput);
fc, options, cx->stackLimitForCurrentPrincipal(), srcBuf,
compileStorage);
CHECK(!stencil);
CHECK(fc->maybeError().isSome());
const js::CompileError& error = fc->maybeError().ref();
@ -119,10 +121,9 @@ bool testNonsyntacticCompile() {
auto destroyFc =
mozilla::MakeScopeExit([fc] { JS::DestroyFrontendContext(fc); });
JS::Rooted<js::UniquePtr<js::frontend::CompilationInput>> stencilInput(cx);
JS::CompilationStorage compileStorage;
RefPtr<JS::Stencil> stencil = CompileGlobalScriptToStencil(
fc, options, cx->stackLimitForCurrentPrincipal(), srcBuf,
stencilInput.get());
fc, options, cx->stackLimitForCurrentPrincipal(), srcBuf, compileStorage);
CHECK(stencil);
JS::InstantiateOptions instantiateOptions(options);
@ -153,9 +154,10 @@ bool testCompileModule() {
CHECK(buf16.init(cx, src_16.data(), src_16.length(),
JS::SourceOwnership::Borrowed));
js::UniquePtr<js::frontend::CompilationInput> stencilInput;
JS::CompilationStorage compileStorage;
RefPtr<JS::Stencil> stencil = CompileModuleScriptToStencil(
fc, options, cx->stackLimitForCurrentPrincipal(), buf16, stencilInput);
fc, options, cx->stackLimitForCurrentPrincipal(), buf16,
compileStorage);
CHECK(stencil);
CHECK(stencil->isModule());
CHECK(stencil->scriptExtra.size() == 1);
@ -164,7 +166,7 @@ bool testCompileModule() {
CHECK(stencil->scriptData.size() == 1);
CHECK(stencil->scriptData[0].hasSharedData()); // has generated bytecode
CHECK(stencil->scriptData[0].gcThingsLength == 1);
CHECK(stencilInput);
CHECK(compileStorage.hasInput());
}
{ // 8-bit characters
@ -172,9 +174,9 @@ bool testCompileModule() {
CHECK(
buf8.init(cx, src.data(), src.length(), JS::SourceOwnership::Borrowed));
js::UniquePtr<js::frontend::CompilationInput> stencilInput;
JS::CompilationStorage compileStorage;
RefPtr<JS::Stencil> stencil = CompileModuleScriptToStencil(
fc, options, cx->stackLimitForCurrentPrincipal(), buf8, stencilInput);
fc, options, cx->stackLimitForCurrentPrincipal(), buf8, compileStorage);
CHECK(stencil);
CHECK(stencil->scriptExtra.size() == 1);
CHECK(stencil->scriptExtra[0].extent.sourceStart == 0);
@ -182,7 +184,7 @@ bool testCompileModule() {
CHECK(stencil->scriptData.size() == 1);
CHECK(stencil->scriptData[0].hasSharedData()); // has generated bytecode
CHECK(stencil->scriptData[0].gcThingsLength == 1);
CHECK(stencilInput);
CHECK(compileStorage.hasInput());
}
{ // propagates failures
@ -191,9 +193,10 @@ bool testCompileModule() {
CHECK(srcBuf.init(cx, badSrc.data(), badSrc.length(),
JS::SourceOwnership::Borrowed));
js::UniquePtr<js::frontend::CompilationInput> stencilInput;
JS::CompilationStorage compileStorage;
RefPtr<JS::Stencil> stencil = CompileModuleScriptToStencil(
fc, options, cx->stackLimitForCurrentPrincipal(), srcBuf, stencilInput);
fc, options, cx->stackLimitForCurrentPrincipal(), srcBuf,
compileStorage);
CHECK(!stencil);
CHECK(fc->maybeError().isSome());
const js::CompileError& error = fc->maybeError().ref();
@ -220,22 +223,22 @@ bool testPrepareForInstantiate() {
auto destroyFc =
mozilla::MakeScopeExit([fc] { JS::DestroyFrontendContext(fc); });
js::UniquePtr<js::frontend::CompilationInput> stencilInput;
JS::CompilationStorage compileStorage;
RefPtr<JS::Stencil> stencil = CompileGlobalScriptToStencil(
fc, options, cx->stackLimitForCurrentPrincipal(), buf16, stencilInput);
fc, options, cx->stackLimitForCurrentPrincipal(), buf16, compileStorage);
CHECK(stencil);
CHECK(stencil->scriptData.size() == 2);
CHECK(stencil->scopeData.size() == 1); // function f
CHECK(stencil->parserAtomData.size() == 1); // 'field'
CHECK(stencilInput);
CHECK(stencilInput->atomCache.empty());
CHECK(compileStorage.hasInput());
CHECK(compileStorage.getInput().atomCache.empty());
JS::InstantiationStorage storage;
CHECK(JS::PrepareForInstantiate(fc, *stencilInput, *stencil, storage));
CHECK(stencilInput->atomCache.size() == 1); // 'field'
CHECK(JS::PrepareForInstantiate(fc, compileStorage, *stencil, storage));
CHECK(compileStorage.getInput().atomCache.size() == 1); // 'field'
CHECK(storage.isValid());
// TODO storage.gcOutput_ is private, so there isn't a good way to check the
// scripData and scopeData capacities
// scriptData and scopeData capacities
return true;
}