diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/public/experimental | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/public/experimental')
-rw-r--r-- | js/public/experimental/CTypes.h | 105 | ||||
-rw-r--r-- | js/public/experimental/CodeCoverage.h | 40 | ||||
-rw-r--r-- | js/public/experimental/CompileScript.h | 180 | ||||
-rw-r--r-- | js/public/experimental/Intl.h | 50 | ||||
-rw-r--r-- | js/public/experimental/JSStencil.h | 217 | ||||
-rw-r--r-- | js/public/experimental/JitInfo.h | 336 | ||||
-rw-r--r-- | js/public/experimental/PCCountProfiling.h | 162 | ||||
-rw-r--r-- | js/public/experimental/SourceHook.h | 99 | ||||
-rw-r--r-- | js/public/experimental/TypedData.h | 748 |
9 files changed, 1937 insertions, 0 deletions
diff --git a/js/public/experimental/CTypes.h b/js/public/experimental/CTypes.h new file mode 100644 index 0000000000..bcd3b28775 --- /dev/null +++ b/js/public/experimental/CTypes.h @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef js_experimental_CTypes_h +#define js_experimental_CTypes_h + +#include "mozilla/Attributes.h" // MOZ_RAII + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/TypeDecls.h" + +namespace JS { + +#ifdef JS_HAS_CTYPES + +/** + * Initialize the 'ctypes' object on a global variable 'obj'. The 'ctypes' + * object will be sealed. + */ +extern JS_PUBLIC_API bool InitCTypesClass(JSContext* cx, + Handle<JSObject*> global); + +#endif // JS_HAS_CTYPES + +/** + * The type of ctypes activity that is occurring. + */ +enum class CTypesActivityType { + BeginCall, + EndCall, + BeginCallback, + EndCallback, +}; + +/** + * The signature of a function invoked at the leading or trailing edge of ctypes + * activity. + */ +using CTypesActivityCallback = void (*)(JSContext*, CTypesActivityType); + +/** + * Sets a callback that is run whenever js-ctypes is about to be used when + * calling into C. + */ +extern JS_PUBLIC_API void SetCTypesActivityCallback(JSContext* cx, + CTypesActivityCallback cb); + +class MOZ_RAII JS_PUBLIC_API AutoCTypesActivityCallback { + private: + JSContext* cx; + CTypesActivityCallback callback; + CTypesActivityType endType; + + public: + AutoCTypesActivityCallback(JSContext* cx, CTypesActivityType beginType, + CTypesActivityType endType); + + ~AutoCTypesActivityCallback() { DoEndCallback(); } + + void DoEndCallback() { + if (callback) { + callback(cx, endType); + callback = nullptr; + } + } +}; + +#ifdef JS_HAS_CTYPES + +/** + * Convert a unicode string 'source' of length 'slen' to the platform native + * charset, returning a null-terminated string allocated with JS_malloc. On + * failure, this function should report an error. + */ +using CTypesUnicodeToNativeFun = char* (*)(JSContext*, const char16_t*, size_t); + +/** + * Set of function pointers that ctypes can use for various internal functions. + * See JS::SetCTypesCallbacks below. Providing nullptr for a function is safe + * and will result in the applicable ctypes functionality not being available. + */ +struct CTypesCallbacks { + CTypesUnicodeToNativeFun unicodeToNative; +}; + +/** + * Set the callbacks on the provided 'ctypesObj' object. 'callbacks' should be a + * pointer to static data that exists for the lifetime of 'ctypesObj', but it + * may safely be altered after calling this function and without having + * to call this function again. + */ +extern JS_PUBLIC_API void SetCTypesCallbacks(JSObject* ctypesObj, + const CTypesCallbacks* callbacks); + +#endif // JS_HAS_CTYPES + +} // namespace JS + +#endif // js_experimental_CTypes_h diff --git a/js/public/experimental/CodeCoverage.h b/js/public/experimental/CodeCoverage.h new file mode 100644 index 0000000000..32729c792d --- /dev/null +++ b/js/public/experimental/CodeCoverage.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef js_experimental_CodeCoverage_h +#define js_experimental_CodeCoverage_h + +#include "jstypes.h" // JS_PUBLIC_API +#include "js/Utility.h" // JS::UniqueChars + +struct JS_PUBLIC_API JSContext; + +namespace js { + +/** + * Enable the collection of lcov code coverage metrics. + * Must be called before a runtime is created and before any calls to + * GetCodeCoverageSummary. + */ +extern JS_PUBLIC_API void EnableCodeCoverage(); + +/** + * Generate lcov trace file content for the current realm, and allocate a new + * buffer and return the content in it, the size of the newly allocated content + * within the buffer would be set to the length out-param. The 'All' variant + * will collect data for all realms in the runtime. + * + * In case of out-of-memory, this function returns nullptr. The length + * out-param is undefined on failure. + */ +extern JS_PUBLIC_API JS::UniqueChars GetCodeCoverageSummary(JSContext* cx, + size_t* length); +extern JS_PUBLIC_API JS::UniqueChars GetCodeCoverageSummaryAll(JSContext* cx, + size_t* length); + +} // namespace js + +#endif // js_experimental_CodeCoverage_h diff --git a/js/public/experimental/CompileScript.h b/js/public/experimental/CompileScript.h new file mode 100644 index 0000000000..308a5848d9 --- /dev/null +++ b/js/public/experimental/CompileScript.h @@ -0,0 +1,180 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* JavaScript API for compiling scripts to stencil without depending on + * JSContext. */ + +#ifndef js_experimental_CompileScript_h +#define js_experimental_CompileScript_h + +#include "jspubtd.h" +#include "js/ErrorReport.h" // JSErrorReport +#include "js/experimental/JSStencil.h" +#include "js/GCAnnotations.h" +#include "js/Modules.h" +#include "js/Stack.h" +#include "js/UniquePtr.h" + +namespace js { +class FrontendContext; +namespace frontend { +struct CompilationInput; +} // namespace frontend +} // namespace js + +namespace JS { +using FrontendContext = js::FrontendContext; + +// Create a new front-end context. +JS_PUBLIC_API JS::FrontendContext* NewFrontendContext(); + +// Destroy a front-end context allocated with NewFrontendContext. +JS_PUBLIC_API void DestroyFrontendContext(JS::FrontendContext* fc); + +// Set the size of the native stack that should not be exceed. To disable +// stack size checking pass 0. +// +// WARNING: When the stack size checking is enabled, the JS::FrontendContext +// can be used only in the thread where JS::SetNativeStackQuota is called. +JS_PUBLIC_API void SetNativeStackQuota(JS::FrontendContext* fc, + JS::NativeStackSize stackSize); + +// Return the stack quota that can be passed to SetNativeStackQuota, for given +// stack size. +// This subtracts a margin from given stack size, to make sure the stack quota +// check performed internally is sufficient. +JS_PUBLIC_API JS::NativeStackSize ThreadStackQuotaForSize(size_t stackSize); + +// Returns true if there was any error reported to given FrontendContext. +JS_PUBLIC_API bool HadFrontendErrors(JS::FrontendContext* fc); + +// Convert the error reported to FrontendContext into runtime error in +// JSContext. Returns false if the error cannot be converted (such as due to +// OOM). An error might still be reported to the given JSContext. Also, returns +// false when OOM is converted. Returns true otherwise. +// +// The options parameter isn't actually used, but the CompileOptions +// provided to the compile/decode operation owns the filename pointer +// that the error and warnings reported to FrontendContext point to, +// so the CompileOptions must be alive until this call. +JS_PUBLIC_API bool ConvertFrontendErrorsToRuntimeErrors( + JSContext* cx, JS::FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options); + +// Returns an error report if given JS::FrontendContext had error and it has +// an error report associated. +// +// This can be nullptr even if JS::HadFrontendErrors returned true, if +// the error is one of: +// * over recursed +// * out of memory +// * allocation overflow +// +// The returned pointer is valid only while the given JS::FrontendContext is +// alive. +// +// See ConvertFrontendErrorsToRuntimeErrors for options parameter. +JS_PUBLIC_API const JSErrorReport* GetFrontendErrorReport( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options); + +// Returns true if the JS::FrontendContext had over recuresed error. +JS_PUBLIC_API bool HadFrontendOverRecursed(JS::FrontendContext* fc); + +// Returns true if the JS::FrontendContext had out of memory error. +JS_PUBLIC_API bool HadFrontendOutOfMemory(JS::FrontendContext* fc); + +// Returns true if the JS::FrontendContext had allocation overflow error. +JS_PUBLIC_API bool HadFrontendAllocationOverflow(JS::FrontendContext* fc); + +// Clear errors reported to the JS::FrontendContext. +// No-op when there's no errors. +JS_PUBLIC_API void ClearFrontendErrors(JS::FrontendContext* fc); + +// Returns the number of warnings reported to the JS::FrontendContext. +JS_PUBLIC_API size_t GetFrontendWarningCount(JS::FrontendContext* fc); + +// Returns an error report represents the index-th warning. +// +// The returned pointer is valid only while the JS::FrontendContext is alive. +// +// See ConvertFrontendErrorsToRuntimeErrors for options parameter. +JS_PUBLIC_API const JSErrorReport* GetFrontendWarningAt( + JS::FrontendContext* fc, size_t index, + const JS::ReadOnlyCompileOptions& options); + +// 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; + bool isBorrowed_ = false; + + public: + CompilationStorage() = default; + explicit CompilationStorage(js::frontend::CompilationInput* input) + : input_(input), isBorrowed_(true) {} + CompilationStorage(CompilationStorage&& other) + : input_(other.input_), isBorrowed_(other.isBorrowed_) { + other.input_ = nullptr; + } + + ~CompilationStorage(); + + private: + CompilationStorage(const CompilationStorage& other) = delete; + void operator=(const CompilationStorage& aOther) = delete; + + public: + bool hasInput() { return !!input_; } + + // Internal function that initializes the CompilationInput. It should only be + // called once. + bool allocateInput(FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options); + + js::frontend::CompilationInput& getInput() { + MOZ_ASSERT(hasInput()); + return *input_; + } + + // Size of dynamic data. Note that GC data is counted by GC and not here. + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + void trace(JSTracer* trc); +}; + +extern JS_PUBLIC_API already_AddRefed<JS::Stencil> CompileGlobalScriptToStencil( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<mozilla::Utf8Unit>& srcBuf, + JS::CompilationStorage& compileStorage); + +extern JS_PUBLIC_API already_AddRefed<JS::Stencil> CompileGlobalScriptToStencil( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, JS::CompilationStorage& compileStorage); + +extern JS_PUBLIC_API already_AddRefed<JS::Stencil> CompileModuleScriptToStencil( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<mozilla::Utf8Unit>& srcBuf, + JS::CompilationStorage& compileStorage); + +extern JS_PUBLIC_API already_AddRefed<JS::Stencil> CompileModuleScriptToStencil( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, JS::CompilationStorage& compileStorage); + +extern JS_PUBLIC_API bool PrepareForInstantiate( + JS::FrontendContext* fc, JS::Stencil& stencil, + JS::InstantiationStorage& storage); + +} // namespace JS + +#endif // js_experimental_CompileScript_h diff --git a/js/public/experimental/Intl.h b/js/public/experimental/Intl.h new file mode 100644 index 0000000000..a3d1c5c171 --- /dev/null +++ b/js/public/experimental/Intl.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef js_experimental_Intl_h +#define js_experimental_Intl_h + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/TypeDecls.h" + +namespace JS { + +/** + * Create and add the Intl.MozDateTimeFormat constructor function to the + * provided object. + * + * This custom date/time formatter constructor gives users the ability to + * specify a custom format pattern. This pattern is passed *directly* to ICU + * with NO SYNTAX PARSING OR VALIDATION WHATSOEVER. ICU appears to have a + * modicum of testing of this, and it won't fall over completely if passed bad + * input. But the current behavior is entirely under-specified and emphatically + * not shippable on the web, and it *must* be fixed before this functionality + * can be exposed in the real world. (There are also some questions about + * whether the format exposed here is the *right* one to standardize, that will + * also need to be resolved to ship this.) + * + * If JS was built without JS_HAS_INTL_API, this function will throw an + * exception. + */ +extern JS_PUBLIC_API bool AddMozDateTimeFormatConstructor( + JSContext* cx, Handle<JSObject*> intl); + +/** + * Create and add the Intl.MozDisplayNames constructor function to the + * provided object. This constructor acts like the standard |Intl.DisplayNames| + * but accepts certain additional syntax that isn't standardized to the point of + * being shippable. + * + * If JS was built without JS_HAS_INTL_API, this function will throw an + * exception. + */ +extern JS_PUBLIC_API bool AddMozDisplayNamesConstructor(JSContext* cx, + Handle<JSObject*> intl); + +} // namespace JS + +#endif // js_experimental_Intl_h diff --git a/js/public/experimental/JSStencil.h b/js/public/experimental/JSStencil.h new file mode 100644 index 0000000000..8a79687898 --- /dev/null +++ b/js/public/experimental/JSStencil.h @@ -0,0 +1,217 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef js_experimental_JSStencil_h +#define js_experimental_JSStencil_h + +/* The `JS::Stencil` type holds the output of the JS Parser before it is + * allocated on the GC heap as a `JSScript`. This form may be serialized as + * part of building a bytecode cache. This `Stencil` is not associated with any + * particular Realm and may be generated off-main-thread, making it useful for + * building script loaders. + */ + +#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf +#include "mozilla/RefPtr.h" // RefPtr, already_AddRefed +#include "mozilla/Utf8.h" // mozilla::Utf8Unit +#include "mozilla/Vector.h" // mozilla::Vector + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions, JS::InstantiateOptions, JS::ReadOnlyDecodeOptions +#include "js/SourceText.h" // JS::SourceText +#include "js/Transcoding.h" // JS::TranscodeBuffer, JS::TranscodeRange + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSTracer; + +// Underlying opaque type. +namespace js { +class FrontendContext; +namespace frontend { +struct CompilationStencil; +struct CompilationGCOutput; +struct CompilationInput; +struct PreallocatedCompilationGCOutput; +} // namespace frontend +} // namespace js + +// ************************************************************************ +// Types +// ************************************************************************ + +namespace JS { + +struct CompilationStorage; + +using Stencil = js::frontend::CompilationStencil; +using FrontendContext = js::FrontendContext; + +// Temporary storage used during instantiating Stencil. +// +// Off-thread APIs can allocate this instance off main thread, and pass it back +// to the main thread, in order to reduce the main thread allocation. +struct InstantiationStorage { + private: + // Owned CompilationGCOutput. + // + // This uses raw pointer instead of UniquePtr because + // PreallocatedCompilationGCOutput is opaque. + js::frontend::PreallocatedCompilationGCOutput* gcOutput_ = nullptr; + + friend JS_PUBLIC_API JSScript* InstantiateGlobalStencil( + JSContext* cx, const InstantiateOptions& options, Stencil* stencil, + InstantiationStorage* storage); + + friend JS_PUBLIC_API JSObject* InstantiateModuleStencil( + JSContext* cx, const InstantiateOptions& options, Stencil* stencil, + InstantiationStorage* storage); + + friend JS_PUBLIC_API bool PrepareForInstantiate( + JS::FrontendContext* fc, JS::Stencil& stencil, + JS::InstantiationStorage& storage); + + public: + InstantiationStorage() = default; + InstantiationStorage(InstantiationStorage&& other) + : gcOutput_(other.gcOutput_) { + other.gcOutput_ = nullptr; + } + + ~InstantiationStorage(); + + void operator=(InstantiationStorage&& other) { + gcOutput_ = other.gcOutput_; + other.gcOutput_ = nullptr; + } + + private: + InstantiationStorage(const InstantiationStorage& other) = delete; + void operator=(const InstantiationStorage& aOther) = delete; + + public: + bool isValid() const { return !!gcOutput_; } +}; + +} // namespace JS + +// ************************************************************************ +// Reference Count +// ************************************************************************ + +namespace JS { + +// These non-member functions let us manipulate the ref counts of the opaque +// Stencil type. The RefPtrTraits below calls these for use when using the +// RefPtr type. +JS_PUBLIC_API void StencilAddRef(Stencil* stencil); +JS_PUBLIC_API void StencilRelease(Stencil* stencil); + +} // namespace JS + +namespace mozilla { +template <> +struct RefPtrTraits<JS::Stencil> { + static void AddRef(JS::Stencil* stencil) { JS::StencilAddRef(stencil); } + static void Release(JS::Stencil* stencil) { JS::StencilRelease(stencil); } +}; +} // namespace mozilla + +// ************************************************************************ +// Properties +// ************************************************************************ + +namespace JS { + +// Return true if the stencil relies on external data as a result of XDR +// decoding. +extern JS_PUBLIC_API bool StencilIsBorrowed(Stencil* stencil); + +extern JS_PUBLIC_API size_t SizeOfStencil(Stencil* stencil, + mozilla::MallocSizeOf mallocSizeOf); + +} // namespace JS + +// ************************************************************************ +// Compilation +// ************************************************************************ + +namespace JS { + +// Compile the source text into a JS::Stencil using the provided options. The +// resulting stencil may be instantiated into any Realm on the current runtime +// and may be used multiple times. +// +// NOTE: On error, a null will be returned and an exception will be set on the +// JSContext. +extern JS_PUBLIC_API already_AddRefed<Stencil> CompileGlobalScriptToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<mozilla::Utf8Unit>& srcBuf); +extern JS_PUBLIC_API already_AddRefed<Stencil> CompileGlobalScriptToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf); + +// Compile the source text into a JS::Stencil using "module" parse goal. The +// ECMAScript spec defines special semantics so we use a seperate entry point +// here for clarity. The result is still a JS::Stencil, but should use the +// appropriate instantiate API below. +extern JS_PUBLIC_API already_AddRefed<Stencil> CompileModuleScriptToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<mozilla::Utf8Unit>& srcBuf); +extern JS_PUBLIC_API already_AddRefed<Stencil> CompileModuleScriptToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf); + +} // namespace JS + +// ************************************************************************ +// Instantiation +// ************************************************************************ + +namespace JS { + +// Instantiate the Stencil into current Realm and return the JSScript. +extern JS_PUBLIC_API JSScript* InstantiateGlobalStencil( + JSContext* cx, const InstantiateOptions& options, Stencil* stencil, + InstantiationStorage* storage = nullptr); + +// Instantiate a module Stencil and return the associated object. Inside the +// engine this is a js::ModuleObject. +extern JS_PUBLIC_API JSObject* InstantiateModuleStencil( + JSContext* cx, const InstantiateOptions& options, Stencil* stencil, + InstantiationStorage* storage = nullptr); + +} // namespace JS + +// ************************************************************************ +// Transcoding +// ************************************************************************ + +namespace JS { + +// Serialize the Stencil into the transcode buffer. +extern JS_PUBLIC_API TranscodeResult EncodeStencil(JSContext* cx, + Stencil* stencil, + TranscodeBuffer& buffer); + +// Deserialize data and create a new Stencil. +extern JS_PUBLIC_API TranscodeResult +DecodeStencil(JSContext* cx, const ReadOnlyDecodeOptions& options, + const TranscodeRange& range, Stencil** stencilOut); +extern JS_PUBLIC_API TranscodeResult +DecodeStencil(JS::FrontendContext* fc, const ReadOnlyDecodeOptions& options, + const TranscodeRange& range, Stencil** stencilOut); + +// Register an encoder on its script source, such that all functions can be +// encoded as they are delazified. +extern JS_PUBLIC_API bool StartIncrementalEncoding(JSContext* cx, + RefPtr<Stencil>&& stencil); + +} // namespace JS + +#endif // js_experimental_JSStencil_h diff --git a/js/public/experimental/JitInfo.h b/js/public/experimental/JitInfo.h new file mode 100644 index 0000000000..1b070021bd --- /dev/null +++ b/js/public/experimental/JitInfo.h @@ -0,0 +1,336 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef js_experimental_JitInfo_h +#define js_experimental_JitInfo_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include <stddef.h> // size_t +#include <stdint.h> // uint16_t, uint32_t + +#include "js/CallArgs.h" // JS::CallArgs, JS::detail::CallArgsBase, JSNative +#include "js/RootingAPI.h" // JS::{,Mutable}Handle, JS::Rooted +#include "js/Value.h" // JS::Value, JSValueType + +namespace js { + +namespace jit { + +enum class InlinableNative : uint16_t; + +} // namespace jit + +} // namespace js + +/** + * A class, expected to be passed by value, which represents the CallArgs for a + * JSJitGetterOp. + */ +class JSJitGetterCallArgs : protected JS::MutableHandle<JS::Value> { + public: + explicit JSJitGetterCallArgs(const JS::CallArgs& args) + : JS::MutableHandle<JS::Value>(args.rval()) {} + + explicit JSJitGetterCallArgs(JS::Rooted<JS::Value>* rooted) + : JS::MutableHandle<JS::Value>(rooted) {} + + explicit JSJitGetterCallArgs(JS::MutableHandle<JS::Value> handle) + : JS::MutableHandle<JS::Value>(handle) {} + + JS::MutableHandle<JS::Value> rval() { return *this; } +}; + +/** + * A class, expected to be passed by value, which represents the CallArgs for a + * JSJitSetterOp. + */ +class JSJitSetterCallArgs : protected JS::MutableHandle<JS::Value> { + public: + explicit JSJitSetterCallArgs(const JS::CallArgs& args) + : JS::MutableHandle<JS::Value>(args[0]) {} + + explicit JSJitSetterCallArgs(JS::Rooted<JS::Value>* rooted) + : JS::MutableHandle<JS::Value>(rooted) {} + + JS::MutableHandle<JS::Value> operator[](unsigned i) { + MOZ_ASSERT(i == 0); + return *this; + } + + unsigned length() const { return 1; } + + // Add get() or maybe hasDefined() as needed +}; + +struct JSJitMethodCallArgsTraits; + +/** + * A class, expected to be passed by reference, which represents the CallArgs + * for a JSJitMethodOp. + */ +class JSJitMethodCallArgs + : protected JS::detail::CallArgsBase<JS::detail::NoUsedRval> { + private: + using Base = JS::detail::CallArgsBase<JS::detail::NoUsedRval>; + friend struct JSJitMethodCallArgsTraits; + + public: + explicit JSJitMethodCallArgs(const JS::CallArgs& args) { + argv_ = args.array(); + argc_ = args.length(); + } + + JS::MutableHandle<JS::Value> rval() const { return Base::rval(); } + + unsigned length() const { return Base::length(); } + + JS::MutableHandle<JS::Value> operator[](unsigned i) const { + return Base::operator[](i); + } + + bool hasDefined(unsigned i) const { return Base::hasDefined(i); } + + JSObject& callee() const { + // We can't use Base::callee() because that will try to poke at + // this->usedRval_, which we don't have. + return argv_[-2].toObject(); + } + + JS::Handle<JS::Value> get(unsigned i) const { return Base::get(i); } + + bool requireAtLeast(JSContext* cx, const char* fnname, + unsigned required) const { + // Can just forward to Base, since it only needs the length and we + // forward that already. + return Base::requireAtLeast(cx, fnname, required); + } +}; + +struct JSJitMethodCallArgsTraits { + static constexpr size_t offsetOfArgv = offsetof(JSJitMethodCallArgs, argv_); + static constexpr size_t offsetOfArgc = offsetof(JSJitMethodCallArgs, argc_); +}; + +using JSJitGetterOp = bool (*)(JSContext*, JS::Handle<JSObject*>, void*, + JSJitGetterCallArgs); +using JSJitSetterOp = bool (*)(JSContext*, JS::Handle<JSObject*>, void*, + JSJitSetterCallArgs); +using JSJitMethodOp = bool (*)(JSContext*, JS::Handle<JSObject*>, void*, + const JSJitMethodCallArgs&); + +/** + * This struct contains metadata passed from the DOM to the JS Engine for JIT + * optimizations on DOM property accessors. + * + * Eventually, this should be made available to general JSAPI users as *not* + * experimental and *not* a friend API, but we're not ready to do so yet. + */ +class JSJitInfo { + public: + enum OpType { + Getter, + Setter, + Method, + StaticMethod, + InlinableNative, + IgnoresReturnValueNative, + // Must be last + OpTypeCount + }; + + enum ArgType { + // Basic types + String = (1 << 0), + Integer = (1 << 1), // Only 32-bit or less + Double = (1 << 2), // Maybe we want to add Float sometime too + Boolean = (1 << 3), + Object = (1 << 4), + Null = (1 << 5), + + // And derived types + Numeric = Integer | Double, + // Should "Primitive" use the WebIDL definition, which + // excludes string and null, or the typical JS one that includes them? + Primitive = Numeric | Boolean | Null | String, + ObjectOrNull = Object | Null, + Any = ObjectOrNull | Primitive, + + // Our sentinel value. + ArgTypeListEnd = (1 << 31) + }; + + static_assert(Any & String, "Any must include String"); + static_assert(Any & Integer, "Any must include Integer"); + static_assert(Any & Double, "Any must include Double"); + static_assert(Any & Boolean, "Any must include Boolean"); + static_assert(Any & Object, "Any must include Object"); + static_assert(Any & Null, "Any must include Null"); + + /** + * An enum that describes what this getter/setter/method aliases. This + * determines what things can be hoisted past this call, and if this + * call is movable what it can be hoisted past. + */ + enum AliasSet { + /** + * Alias nothing: a constant value, getting it can't affect any other + * values, nothing can affect it. + */ + AliasNone, + + /** + * Alias things that can modify the DOM but nothing else. Doing the + * call can't affect the behavior of any other function. + */ + AliasDOMSets, + + /** + * Alias the world. Calling this can change arbitrary values anywhere + * in the system. Most things fall in this bucket. + */ + AliasEverything, + + /** Must be last. */ + AliasSetCount + }; + + bool needsOuterizedThisObject() const { + return type() != Getter && type() != Setter; + } + + bool isTypedMethodJitInfo() const { return isTypedMethod; } + + OpType type() const { return OpType(type_); } + + AliasSet aliasSet() const { return AliasSet(aliasSet_); } + + JSValueType returnType() const { return JSValueType(returnType_); } + + union { + JSJitGetterOp getter; + JSJitSetterOp setter; + JSJitMethodOp method; + /** A DOM static method, used for Promise wrappers */ + JSNative staticMethod; + JSNative ignoresReturnValueMethod; + }; + + static unsigned offsetOfIgnoresReturnValueNative() { + return offsetof(JSJitInfo, ignoresReturnValueMethod); + } + + union { + uint16_t protoID; + js::jit::InlinableNative inlinableNative; + }; + + union { + uint16_t depth; + + // Additional opcode for some InlinableNative functions. + uint16_t nativeOp; + }; + + // These fields are carefully packed to take up 4 bytes. If you need more + // bits for whatever reason, please see if you can steal bits from existing + // fields before adding more members to this structure. + static constexpr size_t OpTypeBits = 4; + static constexpr size_t AliasSetBits = 4; + static constexpr size_t ReturnTypeBits = 8; + static constexpr size_t SlotIndexBits = 10; + + /** The OpType that says what sort of function we are. */ + uint32_t type_ : OpTypeBits; + + /** + * The alias set for this op. This is a _minimal_ alias set; in + * particular for a method it does not include whatever argument + * conversions might do. That's covered by argTypes and runtime + * analysis of the actual argument types being passed in. + */ + uint32_t aliasSet_ : AliasSetBits; + + /** The return type tag. Might be JSVAL_TYPE_UNKNOWN. */ + uint32_t returnType_ : ReturnTypeBits; + + static_assert(OpTypeCount <= (1 << OpTypeBits), + "Not enough space for OpType"); + static_assert(AliasSetCount <= (1 << AliasSetBits), + "Not enough space for AliasSet"); + static_assert((sizeof(JSValueType) * 8) <= ReturnTypeBits, + "Not enough space for JSValueType"); + + /** Is op fallible? False in setters. */ + uint32_t isInfallible : 1; + + /** + * Is op movable? To be movable the op must + * not AliasEverything, but even that might + * not be enough (e.g. in cases when it can + * throw or is explicitly not movable). + */ + uint32_t isMovable : 1; + + /** + * Can op be dead-code eliminated? Again, this + * depends on whether the op can throw, in + * addition to the alias set. + */ + uint32_t isEliminatable : 1; + + // XXXbz should we have a JSValueType for the type of the member? + /** + * True if this is a getter that can always + * get the value from a slot of the "this" object. + */ + uint32_t isAlwaysInSlot : 1; + + /** + * True if this is a getter that can sometimes (if the slot doesn't contain + * UndefinedValue()) get the value from a slot of the "this" object. + */ + uint32_t isLazilyCachedInSlot : 1; + + /** True if this is an instance of JSTypedMethodJitInfo. */ + uint32_t isTypedMethod : 1; + + /** + * If isAlwaysInSlot or isSometimesInSlot is true, + * the index of the slot to get the value from. + * Otherwise 0. + */ + uint32_t slotIndex : SlotIndexBits; + + static constexpr size_t maxSlotIndex = (1 << SlotIndexBits) - 1; +}; + +static_assert(sizeof(JSJitInfo) == (sizeof(void*) + 2 * sizeof(uint32_t)), + "There are several thousand instances of JSJitInfo stored in " + "a binary. Please don't increase its space requirements without " + "verifying that there is no other way forward (better packing, " + "smaller datatypes for fields, subclassing, etc.)."); + +struct JSTypedMethodJitInfo { + // We use C-style inheritance here, rather than C++ style inheritance + // because not all compilers support brace-initialization for non-aggregate + // classes. Using C++ style inheritance and constructors instead of + // brace-initialization would also force the creation of static + // constructors (on some compilers) when JSJitInfo and JSTypedMethodJitInfo + // structures are declared. Since there can be several thousand of these + // structures present and we want to have roughly equivalent performance + // across a range of compilers, we do things manually. + JSJitInfo base; + + const JSJitInfo::ArgType* const argTypes; /* For a method, a list of sets of + types that the function + expects. This can be used, + for example, to figure out + when argument coercions can + have side-effects. */ +}; + +#endif // js_experimental_JitInfo_h diff --git a/js/public/experimental/PCCountProfiling.h b/js/public/experimental/PCCountProfiling.h new file mode 100644 index 0000000000..132d48bcae --- /dev/null +++ b/js/public/experimental/PCCountProfiling.h @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Bytecode-level profiling support based on program counter offset within + * functions and overall scripts. + * + * SpiderMonkey compiles functions and scripts into bytecode representation. + * During execution, SpiderMonkey can keep a counter of how often each bytecode + * (specifically, bytecodes that are the targets of jumps) is reached, to track + * basic information about how many times any particular bytecode in a script + * or function is executed. These functions allow this counting to be started + * and stopped, and the results of such counting to be examined. + * + * Accumulated counting data is tracked per "script" (where "script" is either + * a JavaScript function from source text or a top-level script or module). + * Accumulated PCCount data thus prevents the particular scripts that were + * executed from being GC'd. Once PCCount profiling has been stopped, you + * should examine and then purge the accumulated data as soon as possible. + * + * Much of the data tracked here is pretty internal and may only have meaning if + * you're familiar with bytecode and Ion details. Hence why these APIs are + * "experimental". + */ + +#ifndef js_experimental_PCCountProfiling_h +#define js_experimental_PCCountProfiling_h + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSString; + +namespace JS { + +/** + * Start PCCount-based profiling if it hasn't already been started. + * + * This discards previously-accumulated PCCount profiling data from a prior + * PCCount profiling session. It also discards all active JIT code (as such + * code was compiled to *not* perform PCCount-based profiling), so a brief + * performance hit to code execution can be expected after calling this. + */ +extern JS_PUBLIC_API void StartPCCountProfiling(JSContext* cx); + +/** + * Stop PCCount-based profiling if it has been started. + * + * Internally, this accumulates PCCount profiling results into an array of + * (script, script count info) for future consumption. If accumulation fails, + * this array will just be empty. + * + * This also discard all active JIT code (as such code was compiled to perform + * PCCount-based profiling), so a brief performance hit to code execution can be + * expected after calling this. + */ +extern JS_PUBLIC_API void StopPCCountProfiling(JSContext* cx); + +/** + * Get a count of all scripts for which PCCount profiling data was accumulated, + * after PCCount profiling has been stopped. + * + * As noted above, if an internal error occurred when profiling was stopped, + * this function will return 0. + */ +extern JS_PUBLIC_API size_t GetPCCountScriptCount(JSContext* cx); + +/** + * Get a JSON string summarizing PCCount data for the given script, by index + * within the internal array computed when PCCount profiling was stopped, of + * length indicated by |JS::GetPCCountScriptCount|. + * + * The JSON string will encode an object of this form: + * + * { + * "file": filename for the script, + * "line": line number within the script, + * + * // OPTIONAL: only if this script is a function + * "name": function name + * + * "totals": + * { + * "interp": sum of execution counts at all bytecode locations, + * "ion": sum of Ion activity counts, + * } + * } + * + * There is no inherent ordering to the (script, script count info) pairs within + * the array. Callers generally should expect to call this function in a loop + * to get summaries for all executed scripts. + */ +extern JS_PUBLIC_API JSString* GetPCCountScriptSummary(JSContext* cx, + size_t script); + +/** + * Get a JSON string encoding detailed PCCount data for the given script, by + * index within the internal array computed when PCCount profiling was stopped, + * of length indicated by |JS::GetPCCountScriptCount|. + * + * The JSON string will encode an object of this form: + * + * { + * "text": a string containg (possibly decompiled) source of the script, + * "line": line number within the script, + * + * "opcodes": + * [ + * // array elements of this form, corresponding to the script's + * // bytecodes + * { + * "id": offset of bytecode within script + * "line": line number for bytecode, + * "name": the name of the bytecode in question, + * "text": text of the expression being evaluated, + * "counts": + * { + * // OPTIONAL: only if the count is nonzero + * "interp": count of times executed, + * }, + * ], + * + * // OPTIONAL: only if the script is executed under Ion + * "ion": + * [ + * // array of elements of this form, corresponding to Ion basic blocks + * { + * "id": Ion block id, + * "offset": Ion block offset, + * "successors": + * [ + * // list of Ion block ids from which execution may proceed after + * // this one + * ], + * "hits": count of times this block was hit during execution, + * "code": a string containing the code in this Ion basic block, + * } + * ], + * } + * + * There is no inherent ordering to the (script, script count info) pairs within + * the array. Callers generally should expect to call this function in a loop + * to get summaries for all executed scripts. + */ +extern JS_PUBLIC_API JSString* GetPCCountScriptContents(JSContext* cx, + size_t script); + +/** + * Purge all accumulated PCCount profiling data, particularly the internal array + * that |JS::GetPCCountScript{Count,Summary,Contents}| exposes -- and all that + * array's references to previously-executed scripts. + */ +extern JS_PUBLIC_API void PurgePCCounts(JSContext* cx); + +} // namespace JS + +#endif // js_experimental_PCCountProfiling_h diff --git a/js/public/experimental/SourceHook.h b/js/public/experimental/SourceHook.h new file mode 100644 index 0000000000..dc32fabbaf --- /dev/null +++ b/js/public/experimental/SourceHook.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * The context-wide source hook allows the source of scripts/functions to be + * discarded, if that source is constant and readily-reloadable if it's needed + * in the future. + * + * Ordinarily, functions and scripts store a copy of their underlying source, to + * support |Function.prototype.toString| and debuggers. Some scripts, however, + * might be constant and retrievable on demand -- perhaps burned into the binary + * or in a readonly file provided by the embedding. Why not just ask the + * embedding for a copy of the source? + * + * The context-wide |SourceHook| gives embedders a way to respond to these + * requests. The source of scripts/functions compiled with the compile option + * |JS::CompileOptions::setSourceIsLazy(true)| is eligible to be discarded. + * (The exact conditions under which source is discarded are unspecified.) *If* + * source is discarded, performing an operation that requires source uses the + * source hook to load the source. + * + * The source hook must return the *exact* same source for every call. (This is + * why the source hook is unsuitable for use with scripts loaded from the web: + * in general, their contents can change over time.) If the source hook doesn't + * return the exact same source, Very Bad Things may happen. (For example, + * previously-valid indexes into the source will no longer be coherent: they + * might index out of bounds, into the middle of multi-unit code points, &c.) + * + * These APIs are experimental because they shouldn't provide a per-*context* + * mechanism, rather something that's per-compilation. + */ + +#ifndef js_experimental_SourceHook_h +#define js_experimental_SourceHook_h + +#include "mozilla/UniquePtr.h" // mozilla::UniquePtr + +#include <stddef.h> // size_t + +#include "jstypes.h" // JS_PUBLIC_API + +struct JS_PUBLIC_API JSContext; + +namespace js { + +/** + * A class of objects that return source code on demand. + * + * When code is compiled with setSourceIsLazy(true), SpiderMonkey doesn't + * retain the source code (and doesn't do lazy bytecode generation). If we ever + * need the source code, say, in response to a call to Function.prototype. + * toSource or Debugger.Source.prototype.text, then we call the 'load' member + * function of the instance of this class that has hopefully been registered + * with the runtime, passing the code's URL, and hope that it will be able to + * find the source. + */ +class SourceHook { + public: + virtual ~SourceHook() = default; + + /** + * Attempt to load the source for |filename|. + * + * On success, return true and store an owning pointer to the UTF-8 or UTF-16 + * contents of the file in whichever of |twoByteSource| or |utf8Source| is + * non-null. (Exactly one of these will be non-null.) If the stored pointer + * is non-null, source was loaded and must be |js_free|'d when it's no longer + * needed. If the stored pointer is null, the JS engine will simply act as if + * source was unavailable, and users like |Function.prototype.toString| will + * produce fallback results, e.g. "[native code]". + * + * On failure, return false. The contents of whichever of |twoByteSource| or + * |utf8Source| was initially non-null are unspecified and must not be + * |js_free|'d. + */ + virtual bool load(JSContext* cx, const char* filename, + char16_t** twoByteSource, char** utf8Source, + size_t* length) = 0; +}; + +/** + * Have |cx| use |hook| to retrieve lazily-retrieved source code. See the + * comments for SourceHook. The context takes ownership of the hook, and + * will delete it when the context itself is deleted, or when a new hook is + * set. + */ +extern JS_PUBLIC_API void SetSourceHook(JSContext* cx, + mozilla::UniquePtr<SourceHook> hook); + +/** Remove |cx|'s source hook, and return it. The caller now owns the hook. */ +extern JS_PUBLIC_API mozilla::UniquePtr<SourceHook> ForgetSourceHook( + JSContext* cx); + +} // namespace js + +#endif // js_experimental_SourceHook_h diff --git a/js/public/experimental/TypedData.h b/js/public/experimental/TypedData.h new file mode 100644 index 0000000000..abb6bf81f4 --- /dev/null +++ b/js/public/experimental/TypedData.h @@ -0,0 +1,748 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Typed array, ArrayBuffer, and DataView creation, predicate, and accessor + * functions. + */ + +#ifndef js_experimental_TypedData_h +#define js_experimental_TypedData_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_CRASH +#include "mozilla/Casting.h" // mozilla::AssertedCast +#include "mozilla/Span.h" + +#include <stddef.h> // size_t +#include <stdint.h> // {,u}int8_t, {,u}int16_t, {,u}int32_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/Object.h" // JS::GetClass, JS::GetReservedSlot, JS::GetMaybePtrFromReservedSlot +#include "js/RootingAPI.h" // JS::Handle, JS_DECLARE_IS_HEAP_CONSTRUCTIBLE_TYPE +#include "js/ScalarType.h" // JS::Scalar::Type +#include "js/Wrapper.h" // js::CheckedUnwrapStatic + +struct JSClass; +class JS_PUBLIC_API JSObject; + +namespace JS { + +class JS_PUBLIC_API AutoRequireNoGC; + +} // namespace JS + +// JS_FOR_EACH_TYPED_ARRAY(MACRO) expands MACRO once for each specific +// typed array subtype (Int8Array, Float64Array, ...), passing arguments +// as MACRO(ExternalT, NativeT, Name) where +// +// ExternalT - externally-exposed element type (eg uint8_t) +// +// NativeT - element type used for the implementation (eg js::uint8_clamped_t) +// Note that this type is not exposed publicly. Internal files need to +// #include <vm/Uint8Clamped.h> to see it. +// +// Name - a name usable as both a JS::Scalar::Type value (eg +// JS::Scalar::Uint8Clamped) or the stem of a full typed array name (eg +// Uint8ClampedArray) +// +#define JS_FOR_EACH_TYPED_ARRAY(MACRO) \ + MACRO(int8_t, int8_t, Int8) \ + MACRO(uint8_t, uint8_t, Uint8) \ + MACRO(int16_t, int16_t, Int16) \ + MACRO(uint16_t, uint16_t, Uint16) \ + MACRO(int32_t, int32_t, Int32) \ + MACRO(uint32_t, uint32_t, Uint32) \ + MACRO(float, float, Float32) \ + MACRO(double, double, Float64) \ + MACRO(uint8_t, js::uint8_clamped, Uint8Clamped) \ + MACRO(int64_t, int64_t, BigInt64) \ + MACRO(uint64_t, uint64_t, BigUint64) + +/* + * JS_New(type)Array: + * + * Create a new typed array with nelements elements. + * + * These functions (except the WithBuffer variants) fill in the array with + * zeros. + * + * JS_New(type)ArrayFromArray: + * + * Create a new typed array and copy in values from the given object. The + * object is used as if it were an array; that is, the new array (if + * successfully created) will have length given by array.length, and its + * elements will be those specified by array[0], array[1], and so on, after + * conversion to the typed array element type. + * + * JS_New(type)ArrayWithBuffer: + * + * Create a new typed array using the given ArrayBuffer or + * SharedArrayBuffer for storage. The length value is optional; if -1 + * is passed, enough elements to use up the remainder of the byte + * array is used as the default value. + */ + +#define DECLARE_TYPED_ARRAY_CREATION_API(ExternalType, NativeType, Name) \ + extern JS_PUBLIC_API JSObject* JS_New##Name##Array(JSContext* cx, \ + size_t nelements); \ + extern JS_PUBLIC_API JSObject* JS_New##Name##ArrayFromArray( \ + JSContext* cx, JS::Handle<JSObject*> array); \ + extern JS_PUBLIC_API JSObject* JS_New##Name##ArrayWithBuffer( \ + JSContext* cx, JS::Handle<JSObject*> arrayBuffer, size_t byteOffset, \ + int64_t length); + +JS_FOR_EACH_TYPED_ARRAY(DECLARE_TYPED_ARRAY_CREATION_API) +#undef DECLARE_TYPED_ARRAY_CREATION_API + +/** + * Check whether obj supports JS_GetTypedArray* APIs. Note that this may return + * false if a security wrapper is encountered that denies the unwrapping. If + * this test or one of the JS_Is*Array tests succeeds, then it is safe to call + * the various accessor JSAPI calls defined below. + */ +extern JS_PUBLIC_API bool JS_IsTypedArrayObject(JSObject* obj); + +/** + * Check whether obj supports JS_GetArrayBufferView* APIs. Note that this may + * return false if a security wrapper is encountered that denies the + * unwrapping. If this test or one of the more specific tests succeeds, then it + * is safe to call the various ArrayBufferView accessor JSAPI calls defined + * below. + */ +extern JS_PUBLIC_API bool JS_IsArrayBufferViewObject(JSObject* obj); + +/** + * Return the isShared flag of a typed array, which denotes whether + * the underlying buffer is a SharedArrayBuffer. + * + * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow + * be known that it would pass such a test: it is a typed array or a wrapper of + * a typed array, and the unwrapping will succeed. + */ +extern JS_PUBLIC_API bool JS_GetTypedArraySharedness(JSObject* obj); + +/* + * Test for specific typed array types (ArrayBufferView subtypes) and return + * the unwrapped object if so, else nullptr. Never throws. + */ + +namespace js { + +extern JS_PUBLIC_API JSObject* UnwrapArrayBufferView(JSObject* obj); + +extern JS_PUBLIC_API JSObject* UnwrapReadableStream(JSObject* obj); + +namespace detail { + +constexpr size_t TypedArrayLengthSlot = 1; +constexpr size_t TypedArrayDataSlot = 3; + +} // namespace detail + +// This one isn't inlined because it's rather tricky (by dint of having to deal +// with a dozen-plus classes and varying slot layouts. +extern JS_PUBLIC_API void GetArrayBufferViewLengthAndData(JSObject* obj, + size_t* length, + bool* isSharedMemory, + uint8_t** data); + +} // namespace js + +/* + * JS_GetObjectAs(type)Array(JSObject* maybeWrapped, size_t* length, bool* + * isSharedMemory, element_type** data) + * + * Unwrap Typed arrays all at once. Return nullptr without throwing if the + * object cannot be viewed as the correct typed array, or the typed array + * object on success, filling both outparameters. + */ +#define DECLARE_GET_OBJECT_AS(ExternalType, NativeType, Name) \ + extern JS_PUBLIC_API JSObject* JS_GetObjectAs##Name##Array( \ + JSObject* maybeWrapped, size_t* length, bool* isSharedMemory, \ + ExternalType** data); +JS_FOR_EACH_TYPED_ARRAY(DECLARE_GET_OBJECT_AS) +#undef DECLARE_GET_OBJECT_AS + +extern JS_PUBLIC_API JSObject* JS_GetObjectAsArrayBufferView( + JSObject* obj, size_t* length, bool* isSharedMemory, uint8_t** data); + +/* + * Get the type of elements in a typed array, or MaxTypedArrayViewType if a + * DataView. + * + * |obj| must have passed a JS_IsArrayBufferView/JS_Is*Array test, or somehow + * be known that it would pass such a test: it is an ArrayBufferView or a + * wrapper of an ArrayBufferView, and the unwrapping will succeed. + */ +extern JS_PUBLIC_API JS::Scalar::Type JS_GetArrayBufferViewType(JSObject* obj); + +/** + * Return the number of elements in a typed array. + * + * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow + * be known that it would pass such a test: it is a typed array or a wrapper of + * a typed array, and the unwrapping will succeed. + */ +extern JS_PUBLIC_API size_t JS_GetTypedArrayLength(JSObject* obj); + +/** + * Return the byte offset from the start of an ArrayBuffer to the start of a + * typed array view. + * + * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow + * be known that it would pass such a test: it is a typed array or a wrapper of + * a typed array, and the unwrapping will succeed. + */ +extern JS_PUBLIC_API size_t JS_GetTypedArrayByteOffset(JSObject* obj); + +/** + * Return the byte length of a typed array. + * + * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow + * be known that it would pass such a test: it is a typed array or a wrapper of + * a typed array, and the unwrapping will succeed. + */ +extern JS_PUBLIC_API size_t JS_GetTypedArrayByteLength(JSObject* obj); + +/** + * More generic name for JS_GetTypedArrayByteLength to cover DataViews as well + */ +extern JS_PUBLIC_API size_t JS_GetArrayBufferViewByteLength(JSObject* obj); + +/** + * More generic name for JS_GetTypedArrayByteOffset to cover DataViews as well + */ +extern JS_PUBLIC_API size_t JS_GetArrayBufferViewByteOffset(JSObject* obj); + +/** + * Same as above, but for any kind of ArrayBufferView. Prefer the type-specific + * versions when possible. + */ +extern JS_PUBLIC_API void* JS_GetArrayBufferViewData( + JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC&); + +/** + * Return a "fixed" pointer (one that will not move during a GC) to the + * ArrayBufferView's data. Note that this will not keep the object alive; the + * holding object should be rooted or traced. If the view is storing the data + * inline, this will copy the data to the provided buffer, returning nullptr if + * bufSize is inadequate. + * + * Avoid using this unless necessary. JS_GetArrayBufferViewData is simpler and + * more efficient because it requires the caller to ensure that a GC will not + * occur and thus does not need to handle movable data. + */ +extern JS_PUBLIC_API uint8_t* JS_GetArrayBufferViewFixedData(JSObject* obj, + uint8_t* buffer, + size_t bufSize); + +/** + * If the bufSize passed to JS_GetArrayBufferViewFixedData is at least this + * many bytes, then any copied data is guaranteed to fit into the provided + * buffer. + */ +extern JS_PUBLIC_API size_t JS_MaxMovableTypedArraySize(); + +/** + * Return the ArrayBuffer or SharedArrayBuffer underlying an ArrayBufferView. + * This may return a detached buffer. |obj| must be an object that would + * return true for JS_IsArrayBufferViewObject(). + */ +extern JS_PUBLIC_API JSObject* JS_GetArrayBufferViewBuffer( + JSContext* cx, JS::Handle<JSObject*> obj, bool* isSharedMemory); + +/** + * Create a new DataView using the given buffer for storage. The given buffer + * must be an ArrayBuffer or SharedArrayBuffer (or a cross-compartment wrapper + * of either type), and the offset and length must fit within the bounds of the + * buffer. Currently, nullptr will be returned and an exception will be thrown + * if these conditions do not hold, but do not depend on that behavior. + */ +JS_PUBLIC_API JSObject* JS_NewDataView(JSContext* cx, + JS::Handle<JSObject*> buffer, + size_t byteOffset, size_t byteLength); + +namespace JS { + +/* + * Returns whether the passed array buffer view is 'large': its byteLength >= 2 + * GB. + * + * |obj| must pass a JS_IsArrayBufferViewObject test. + */ +JS_PUBLIC_API bool IsLargeArrayBufferView(JSObject* obj); + +/* + * Returns whether the passed array buffer view has a resizable or growable + * array buffer. + * + * |obj| must pass a JS_IsArrayBufferViewObject test. + */ +JS_PUBLIC_API bool IsResizableArrayBufferView(JSObject* obj); + +/* + * Given an ArrayBuffer or view, prevent the length of the underlying + * ArrayBuffer from changing (with pin=true) until unfrozen (with + * pin=false). Note that some objects (eg SharedArrayBuffers) cannot change + * length to begin with, and are treated as always pinned. + * + * ArrayBuffers and their views can change length by being detached, or + * if they are ResizableArrayBuffers or (shared) GrowableArrayBuffers. + * + * Returns whether the pinned status changed. + */ +JS_PUBLIC_API bool PinArrayBufferOrViewLength(JSObject* obj, bool pin); + +/* + * Given an ArrayBuffer or view, make sure its contents are not stored inline + * so that the data is safe for use even if a GC moves the owning object. + * + * Note that this by itself does not make it safe to use the data pointer + * if JS can run or the ArrayBuffer can be detached in any way. Consider using + * this in conjunction with PinArrayBufferOrViewLength, which will cause any + * potentially invalidating operations to fail. + */ +JS_PUBLIC_API bool EnsureNonInlineArrayBufferOrView(JSContext* cx, + JSObject* obj); + +namespace detail { + +// Map from eg Uint8Clamped -> uint8_t, Uint8 -> uint8_t, or Float64 -> +// double. Used as the DataType within a JS::TypedArray specialization. +template <JS::Scalar::Type ArrayType> +struct ExternalTypeOf {}; + +#define DEFINE_ELEMENT_TYPES(ExternalT, NativeT, Name) \ + template <> \ + struct ExternalTypeOf<JS::Scalar::Name> { \ + using Type = ExternalT; \ + }; +JS_FOR_EACH_TYPED_ARRAY(DEFINE_ELEMENT_TYPES) +#undef DEFINE_ELEMENT_TYPES + +template <JS::Scalar::Type ArrayType> +using ExternalTypeOf_t = typename ExternalTypeOf<ArrayType>::Type; + +} // namespace detail + +// A class holding a JSObject referring to a buffer of data. Either an +// ArrayBufferObject or some sort of ArrayBufferViewObject (see below). +// Note that this will always hold an unwrapped object. +class JS_PUBLIC_API ArrayBufferOrView { + public: + // Typed Arrays will set this to their specific element type. + // Everything else just claims to expose things as uint8_t*. + using DataType = uint8_t; + + protected: + JSObject* obj; + + explicit ArrayBufferOrView(JSObject* unwrapped) : obj(unwrapped) {} + + public: + // ArrayBufferOrView subclasses will set `obj` to nullptr if wrapping an + // object of the wrong type. So this allows: + // + // auto view = JS::TypedArray<JS::Scalar::Int8>::fromObject(obj); + // if (!view) { ... } + // + explicit operator bool() const { return !!obj; } + + // `obj` must be an unwrapped ArrayBuffer or view, or nullptr. + static inline ArrayBufferOrView fromObject(JSObject* unwrapped); + + // Unwrap an ArrayBuffer or view. Returns ArrayBufferOrView(nullptr) if + // `maybeWrapped` is the wrong type or fails unwrapping. Never throw. + static ArrayBufferOrView unwrap(JSObject* maybeWrapped); + + // Allow use as Rooted<JS::ArrayBufferOrView>. + void trace(JSTracer* trc) { + if (obj) { + js::gc::TraceExternalEdge(trc, &obj, "ArrayBufferOrView object"); + } + } + + bool isDetached() const; + bool isResizable() const; + + void exposeToActiveJS() const { + if (obj) { + js::BarrierMethods<JSObject*>::exposeToJS(obj); + } + } + + JSObject* asObject() const { + exposeToActiveJS(); + return obj; + } + + JSObject* asObjectUnbarriered() const { return obj; } + + JSObject** addressOfObject() { return &obj; } + + bool operator==(const ArrayBufferOrView& other) const { + return obj == other.asObjectUnbarriered(); + } + bool operator!=(const ArrayBufferOrView& other) const { + return obj != other.asObjectUnbarriered(); + } +}; + +class JS_PUBLIC_API ArrayBuffer : public ArrayBufferOrView { + static const JSClass* const FixedLengthUnsharedClass; + static const JSClass* const ResizableUnsharedClass; + static const JSClass* const FixedLengthSharedClass; + static const JSClass* const GrowableSharedClass; + + protected: + explicit ArrayBuffer(JSObject* unwrapped) : ArrayBufferOrView(unwrapped) {} + + public: + static ArrayBuffer fromObject(JSObject* unwrapped) { + if (unwrapped) { + const JSClass* clasp = GetClass(unwrapped); + if (clasp == FixedLengthUnsharedClass || + clasp == ResizableUnsharedClass || clasp == FixedLengthSharedClass || + clasp == GrowableSharedClass) { + return ArrayBuffer(unwrapped); + } + } + return ArrayBuffer(nullptr); + } + static ArrayBuffer unwrap(JSObject* maybeWrapped); + + static ArrayBuffer create(JSContext* cx, size_t nbytes); + + mozilla::Span<uint8_t> getData(bool* isSharedMemory, + const JS::AutoRequireNoGC&); +}; + +// A view into an ArrayBuffer, either a DataViewObject or a Typed Array variant. +class JS_PUBLIC_API ArrayBufferView : public ArrayBufferOrView { + protected: + explicit ArrayBufferView(JSObject* unwrapped) + : ArrayBufferOrView(unwrapped) {} + + public: + static inline ArrayBufferView fromObject(JSObject* unwrapped); + static ArrayBufferView unwrap(JSObject* maybeWrapped) { + if (!maybeWrapped) { + return ArrayBufferView(nullptr); + } + ArrayBufferView view = fromObject(maybeWrapped); + if (view) { + return view; + } + return fromObject(js::CheckedUnwrapStatic(maybeWrapped)); + } + + bool isDetached() const; + bool isResizable() const; + + mozilla::Span<uint8_t> getData(bool* isSharedMemory, + const JS::AutoRequireNoGC&); + + // Must only be called if !isDetached(). + size_t getByteLength(const JS::AutoRequireNoGC&); +}; + +class JS_PUBLIC_API DataView : public ArrayBufferView { + static const JSClass* const FixedLengthClassPtr; + static const JSClass* const ResizableClassPtr; + + protected: + explicit DataView(JSObject* unwrapped) : ArrayBufferView(unwrapped) {} + + public: + static DataView fromObject(JSObject* unwrapped) { + if (unwrapped) { + const JSClass* clasp = GetClass(unwrapped); + if (clasp == FixedLengthClassPtr || clasp == ResizableClassPtr) { + return DataView(unwrapped); + } + } + return DataView(nullptr); + } + + static DataView unwrap(JSObject* maybeWrapped) { + if (!maybeWrapped) { + return DataView(nullptr); + } + DataView view = fromObject(maybeWrapped); + if (view) { + return view; + } + return fromObject(js::CheckedUnwrapStatic(maybeWrapped)); + } +}; + +// Base type of all Typed Array variants. +class JS_PUBLIC_API TypedArray_base : public ArrayBufferView { + protected: + explicit TypedArray_base(JSObject* unwrapped) : ArrayBufferView(unwrapped) {} + + static const JSClass* const fixedLengthClasses; + static const JSClass* const resizableClasses; + + public: + static TypedArray_base fromObject(JSObject* unwrapped); + + static TypedArray_base unwrap(JSObject* maybeWrapped) { + if (!maybeWrapped) { + return TypedArray_base(nullptr); + } + TypedArray_base view = fromObject(maybeWrapped); + if (view) { + return view; + } + return fromObject(js::CheckedUnwrapStatic(maybeWrapped)); + } +}; + +template <JS::Scalar::Type TypedArrayElementType> +class JS_PUBLIC_API TypedArray : public TypedArray_base { + // This cannot be a static data member because on Windows, + // __declspec(dllexport) causes the class to be instantiated immediately, + // leading to errors when later explicit specializations of inline member + // functions are encountered ("error: explicit specialization of 'ClassPtr' + // after instantiation"). And those inlines need to be defined outside of the + // class due to order dependencies. This is the only way I could get it to + // work on both Windows and POSIX. + static const JSClass* fixedLengthClasp() { + return &TypedArray_base::fixedLengthClasses[static_cast<int>( + TypedArrayElementType)]; + } + static const JSClass* resizableClasp() { + return &TypedArray_base::resizableClasses[static_cast<int>( + TypedArrayElementType)]; + } + + protected: + explicit TypedArray(JSObject* unwrapped) : TypedArray_base(unwrapped) {} + + public: + using DataType = detail::ExternalTypeOf_t<TypedArrayElementType>; + + static constexpr JS::Scalar::Type Scalar = TypedArrayElementType; + + static TypedArray create(JSContext* cx, size_t nelements); + static TypedArray fromArray(JSContext* cx, HandleObject other); + static TypedArray fromBuffer(JSContext* cx, HandleObject arrayBuffer, + size_t byteOffset, int64_t length); + + // Return an interface wrapper around `obj`, or around nullptr if `obj` is not + // an unwrapped typed array of the correct type. + static TypedArray fromObject(JSObject* unwrapped) { + if (unwrapped) { + const JSClass* clasp = GetClass(unwrapped); + if (clasp == fixedLengthClasp() || clasp == resizableClasp()) { + return TypedArray(unwrapped); + } + } + return TypedArray(nullptr); + } + + static TypedArray unwrap(JSObject* maybeWrapped) { + if (!maybeWrapped) { + return TypedArray(nullptr); + } + TypedArray view = fromObject(maybeWrapped); + if (view) { + return view; + } + return fromObject(js::CheckedUnwrapStatic(maybeWrapped)); + } + + // Return a pointer to the start of the data referenced by a typed array. The + // data is still owned by the typed array, and should not be modified on + // another thread. Furthermore, the pointer can become invalid on GC (if the + // data is small and fits inside the array's GC header), so callers must take + // care not to hold on across anything that could GC. + // + // |obj| must have passed a JS_Is*Array test, or somehow be known that it + // would pass such a test: it is a typed array or a wrapper of a typed array, + // and the unwrapping will succeed. + // + // |*isSharedMemory| will be set to true if the typed array maps a + // SharedArrayBuffer, otherwise to false. + // + mozilla::Span<DataType> getData(bool* isSharedMemory, + const JS::AutoRequireNoGC& nogc); +}; + +ArrayBufferOrView ArrayBufferOrView::fromObject(JSObject* unwrapped) { + if (ArrayBuffer::fromObject(unwrapped) || + ArrayBufferView::fromObject(unwrapped)) { + return ArrayBufferOrView(unwrapped); + } + return ArrayBufferOrView(nullptr); +} + +ArrayBufferView ArrayBufferView::fromObject(JSObject* unwrapped) { + if (TypedArray_base::fromObject(unwrapped) || + DataView::fromObject(unwrapped)) { + return ArrayBufferView(unwrapped); + } + return ArrayBufferView(nullptr); +} + +} /* namespace JS */ + +/* + * JS_Get(type)ArrayData(JSObject* obj, + * bool* isSharedMemory, + * const JS::AutoRequireNoGC&) + * + * js::Get(type)ArrayLengthAndData(JSObject* obj, + * size_t* length, + * bool* isSharedMemory, + * const JS::AutoRequireNoGC&) + * + * Return a pointer to the start of the data referenced by a typed array. The + * data is still owned by the typed array, and should not be modified on + * another thread. Furthermore, the pointer can become invalid on GC (if the + * data is small and fits inside the array's GC header), so callers must take + * care not to hold on across anything that could GC. + * + * |obj| must have passed a JS_Is*Array test, or somehow be known that it would + * pass such a test: it is a typed array or a wrapper of a typed array, and the + * unwrapping will succeed. + * + * |*isSharedMemory| will be set to true if the typed array maps a + * SharedArrayBuffer, otherwise to false. + */ + +#define JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(ExternalType, NativeType, Name) \ + extern JS_PUBLIC_API ExternalType* JS_Get##Name##ArrayData( \ + JSObject* maybeWrapped, bool* isSharedMemory, \ + const JS::AutoRequireNoGC&); \ + \ + namespace js { \ + inline void Get##Name##ArrayLengthAndData(JSObject* unwrapped, \ + size_t* length, \ + bool* isSharedMemory, \ + ExternalType** data) { \ + MOZ_ASSERT(JS::TypedArray<JS::Scalar::Name>::fromObject(unwrapped)); \ + const JS::Value& lenSlot = \ + JS::GetReservedSlot(unwrapped, detail::TypedArrayLengthSlot); \ + *length = size_t(lenSlot.toPrivate()); \ + *isSharedMemory = JS_GetTypedArraySharedness(unwrapped); \ + *data = JS::GetMaybePtrFromReservedSlot<ExternalType>( \ + unwrapped, detail::TypedArrayDataSlot); \ + } \ + \ + JS_PUBLIC_API JSObject* Unwrap##Name##Array(JSObject* maybeWrapped); \ + } /* namespace js */ + +JS_FOR_EACH_TYPED_ARRAY(JS_DEFINE_DATA_AND_LENGTH_ACCESSOR) +#undef JS_DEFINE_DATA_AND_LENGTH_ACCESSOR + +namespace JS { + +#define IMPL_TYPED_ARRAY_CLASS(ExternalType, NativeType, Name) \ + template <> \ + inline JS::TypedArray<JS::Scalar::Name> \ + JS::TypedArray<JS::Scalar::Name>::create(JSContext* cx, size_t nelements) { \ + return fromObject(JS_New##Name##Array(cx, nelements)); \ + }; \ + \ + template <> \ + inline JS::TypedArray<JS::Scalar::Name> \ + JS::TypedArray<JS::Scalar::Name>::fromArray(JSContext* cx, \ + HandleObject other) { \ + return fromObject(JS_New##Name##ArrayFromArray(cx, other)); \ + }; \ + \ + template <> \ + inline JS::TypedArray<JS::Scalar::Name> \ + JS::TypedArray<JS::Scalar::Name>::fromBuffer( \ + JSContext* cx, HandleObject arrayBuffer, size_t byteOffset, \ + int64_t length) { \ + return fromObject( \ + JS_New##Name##ArrayWithBuffer(cx, arrayBuffer, byteOffset, length)); \ + }; + +JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS) +#undef IMPL_TYPED_ARRAY_CLASS + +// Create simple names like Int8Array, Float32Array, etc. +#define JS_DECLARE_CLASS_ALIAS(ExternalType, NativeType, Name) \ + using Name##Array = TypedArray<js::Scalar::Name>; +JS_FOR_EACH_TYPED_ARRAY(JS_DECLARE_CLASS_ALIAS) +#undef JS_DECLARE_CLASS_ALIAS + +} // namespace JS + +namespace js { + +template <typename T> +using EnableIfABOVType = + std::enable_if_t<std::is_base_of_v<JS::ArrayBufferOrView, T>>; + +template <typename T, typename Wrapper> +class WrappedPtrOperations<T, Wrapper, EnableIfABOVType<T>> { + auto get() const { return static_cast<const Wrapper*>(this)->get(); } + + public: + explicit operator bool() const { return bool(get()); } + JSObject* asObject() const { return get().asObject(); } + bool isDetached() const { return get().isDetached(); } + bool isSharedMemory() const { return get().isSharedMemory(); } + + mozilla::Span<typename T::DataType> getData(bool* isSharedMemory, + const JS::AutoRequireNoGC& nogc) { + return get().getData(isSharedMemory, nogc); + } +}; + +// Allow usage within Heap<T>. +template <typename T> +struct IsHeapConstructibleType<T, EnableIfABOVType<T>> : public std::true_type { +}; + +template <typename T> +struct BarrierMethods<T, EnableIfABOVType<T>> { + static gc::Cell* asGCThingOrNull(T view) { + return reinterpret_cast<gc::Cell*>(view.asObjectUnbarriered()); + } + static void postWriteBarrier(T* viewp, T prev, T next) { + BarrierMethods<JSObject*>::postWriteBarrier(viewp->addressOfObject(), + prev.asObjectUnbarriered(), + next.asObjectUnbarriered()); + } + static void exposeToJS(T view) { view.exposeToActiveJS(); } + static void readBarrier(T view) { + JSObject* obj = view.asObjectUnbarriered(); + if (obj) { + js::gc::IncrementalReadBarrier(JS::GCCellPtr(obj)); + } + } +}; + +} // namespace js + +namespace JS { +template <typename T> +struct SafelyInitialized<T, js::EnableIfABOVType<T>> { + static T create() { return T::fromObject(nullptr); } +}; +} // namespace JS + +/* + * JS_Is(type)Array(JSObject* maybeWrapped) + * + * Test for specific typed array types. + */ + +#define DECLARE_IS_ARRAY_TEST(_1, _2, Name) \ + inline JS_PUBLIC_API bool JS_Is##Name##Array(JSObject* maybeWrapped) { \ + return JS::TypedArray<js::Scalar::Name>::unwrap(maybeWrapped).asObject(); \ + } +JS_FOR_EACH_TYPED_ARRAY(DECLARE_IS_ARRAY_TEST) +#undef DECLARE_IS_ARRAY_TEST + +#endif // js_experimental_TypedData_h |