mirror of
https://github.com/roytam1/UXP.git
synced 2026-05-26 05:38:39 +00:00
Merge remote-tracking branch 'origin/custom' into custom-platform
This commit is contained in:
@@ -61,6 +61,44 @@ set_config('HAVE_YASM', have_yasm)
|
||||
# Until the YASM variable is not necessary in old-configure.
|
||||
add_old_configure_assignment('YASM', have_yasm)
|
||||
|
||||
# nasm detection
|
||||
# ==============================================================
|
||||
nasm = check_prog('NASM', ['nasm'], allow_missing=True)
|
||||
|
||||
@depends_if(nasm)
|
||||
@checking('nasm version')
|
||||
def nasm_version(nasm):
|
||||
version = check_cmd_output(
|
||||
nasm, '-v',
|
||||
onerror=lambda: die('Failed to get nasm version.')
|
||||
).splitlines()[0].split()[2]
|
||||
return Version(version)
|
||||
|
||||
@depends(nasm, nasm_version)
|
||||
def have_nasm(nasm, version):
|
||||
if nasm and version >= Version('2.14'):
|
||||
return True
|
||||
|
||||
set_config('HAVE_NASM', have_nasm)
|
||||
|
||||
@depends(have_nasm, target)
|
||||
def nasm_asflags(have_nasm, target):
|
||||
if have_nasm:
|
||||
asflags = {
|
||||
('OSX', 'x86'): '-f macho32',
|
||||
('OSX', 'x86_64'): '-f macho64',
|
||||
('WINNT', 'x86'): '-f win32',
|
||||
('WINNT', 'x86_64'): '-f win64',
|
||||
}.get((target.os, target.cpu), None)
|
||||
if asflags is None:
|
||||
if target.cpu == 'x86':
|
||||
asflags = '-f elf32'
|
||||
elif target.cpu == 'x86_64':
|
||||
asflags = '-f elf64'
|
||||
return asflags
|
||||
|
||||
set_config('NASM_ASFLAGS', nasm_asflags)
|
||||
|
||||
# Android NDK
|
||||
# ==============================================================
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -30,7 +30,7 @@ if CONFIG['MOZ_WEBM_ENCODER']:
|
||||
external_dirs += ['media/libvpx']
|
||||
|
||||
if CONFIG['MOZ_AV1']:
|
||||
external_dirs += ['media/libaom']
|
||||
external_dirs += ['media/libdav1d']
|
||||
|
||||
external_dirs += ['media/libpng']
|
||||
|
||||
|
||||
@@ -1005,6 +1005,7 @@ GK_ATOM(parameter, "parameter")
|
||||
GK_ATOM(parent, "parent")
|
||||
GK_ATOM(parentfocused, "parentfocused")
|
||||
GK_ATOM(parsetype, "parsetype")
|
||||
GK_ATOM(part, "part")
|
||||
GK_ATOM(password, "password")
|
||||
GK_ATOM(pattern, "pattern")
|
||||
GK_ATOM(patternSeparator, "pattern-separator")
|
||||
|
||||
@@ -26,7 +26,6 @@ support-files =
|
||||
image_yellow75.png
|
||||
imagebitmap_bug1239300.js
|
||||
imagebitmap_bug1239752.js
|
||||
imagebitmap_extensions.html
|
||||
imagebitmap_on_worker.js
|
||||
imagebitmap_structuredclone.js
|
||||
imagebitmap_structuredclone_iframe.html
|
||||
|
||||
@@ -112,7 +112,7 @@ private:
|
||||
DECL_MEDIA_PREF("media.ffmpeg.skip_loop_filter", FFmpegSkipLoopFilter, bool, false);
|
||||
#endif
|
||||
#ifdef MOZ_AV1
|
||||
DECL_MEDIA_PREF("media.av1.enabled", AV1Enabled, bool, false);
|
||||
DECL_MEDIA_PREF("media.av1.enabled", AV1Enabled, bool, true);
|
||||
#endif
|
||||
#ifdef XP_WIN
|
||||
DECL_MEDIA_PREF("media.wmf.enabled", PDMWMFEnabled, bool, true);
|
||||
|
||||
@@ -1,348 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "AOMDecoder.h"
|
||||
#include "MediaResult.h"
|
||||
#include "TimeUnits.h"
|
||||
#include "aom/aomdx.h"
|
||||
#include "aom/aom_image.h"
|
||||
#include "gfx2DGlue.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include "mozilla/SyncRunnable.h"
|
||||
#include "nsError.h"
|
||||
#include "prsystem.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#undef LOG
|
||||
#define LOG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("AOMDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace gfx;
|
||||
using namespace layers;
|
||||
|
||||
AOMDecoder::AOMDecoder(const CreateDecoderParams& aParams)
|
||||
: mImageContainer(aParams.mImageContainer)
|
||||
, mTaskQueue(aParams.mTaskQueue)
|
||||
, mCallback(aParams.mCallback)
|
||||
, mIsFlushing(false)
|
||||
, mInfo(aParams.VideoConfig())
|
||||
{
|
||||
PodZero(&mCodec);
|
||||
}
|
||||
|
||||
AOMDecoder::~AOMDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
AOMDecoder::Shutdown()
|
||||
{
|
||||
aom_codec_destroy(&mCodec);
|
||||
}
|
||||
|
||||
RefPtr<MediaDataDecoder::InitPromise>
|
||||
AOMDecoder::Init()
|
||||
{
|
||||
int decode_threads = 2;
|
||||
|
||||
aom_codec_iface_t* dx = aom_codec_av1_dx();
|
||||
if (mInfo.mDisplay.width >= 2048) {
|
||||
decode_threads = 8;
|
||||
}
|
||||
else if (mInfo.mDisplay.width >= 1024) {
|
||||
decode_threads = 4;
|
||||
}
|
||||
decode_threads = std::min(decode_threads, PR_GetNumberOfProcessors());
|
||||
|
||||
aom_codec_dec_cfg_t config;
|
||||
PodZero(&config);
|
||||
config.threads = decode_threads;
|
||||
config.w = config.h = 0; // set after decode
|
||||
config.allow_lowbitdepth = true;
|
||||
|
||||
aom_codec_flags_t flags = 0;
|
||||
|
||||
if (!dx || aom_codec_dec_init(&mCodec, dx, &config, flags)) {
|
||||
return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
|
||||
}
|
||||
return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
|
||||
}
|
||||
|
||||
void
|
||||
AOMDecoder::Flush()
|
||||
{
|
||||
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
|
||||
mIsFlushing = true;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () {
|
||||
// nothing to do for now.
|
||||
});
|
||||
SyncRunnable::DispatchToThread(mTaskQueue, r);
|
||||
mIsFlushing = false;
|
||||
}
|
||||
|
||||
// Ported from third_party/aom/tools_common.c.
|
||||
static aom_codec_err_t
|
||||
highbd_img_downshift(aom_image_t *dst, aom_image_t *src, int down_shift) {
|
||||
int plane;
|
||||
if (dst->d_w != src->d_w || dst->d_h != src->d_h)
|
||||
return AOM_CODEC_INVALID_PARAM;
|
||||
if (dst->x_chroma_shift != src->x_chroma_shift)
|
||||
return AOM_CODEC_INVALID_PARAM;
|
||||
if (dst->y_chroma_shift != src->y_chroma_shift)
|
||||
return AOM_CODEC_INVALID_PARAM;
|
||||
if (dst->fmt != (src->fmt & ~AOM_IMG_FMT_HIGHBITDEPTH))
|
||||
return AOM_CODEC_INVALID_PARAM;
|
||||
if (down_shift < 0)
|
||||
return AOM_CODEC_INVALID_PARAM;
|
||||
switch (dst->fmt) {
|
||||
case AOM_IMG_FMT_I420:
|
||||
case AOM_IMG_FMT_I422:
|
||||
case AOM_IMG_FMT_I444:
|
||||
break;
|
||||
default:
|
||||
return AOM_CODEC_INVALID_PARAM;
|
||||
}
|
||||
switch (src->fmt) {
|
||||
case AOM_IMG_FMT_I42016:
|
||||
case AOM_IMG_FMT_I42216:
|
||||
case AOM_IMG_FMT_I44416:
|
||||
break;
|
||||
default:
|
||||
// We don't support anything that's not 16 bit
|
||||
return AOM_CODEC_UNSUP_BITSTREAM;
|
||||
}
|
||||
for (plane = 0; plane < 3; plane++) {
|
||||
int w = src->d_w;
|
||||
int h = src->d_h;
|
||||
int x, y;
|
||||
if (plane) {
|
||||
w = (w + src->x_chroma_shift) >> src->x_chroma_shift;
|
||||
h = (h + src->y_chroma_shift) >> src->y_chroma_shift;
|
||||
}
|
||||
for (y = 0; y < h; y++) {
|
||||
uint16_t *p_src =
|
||||
(uint16_t *)(src->planes[plane] + y * src->stride[plane]);
|
||||
uint8_t *p_dst =
|
||||
dst->planes[plane] + y * dst->stride[plane];
|
||||
for (x = 0; x < w; x++) *p_dst++ = (*p_src++ >> down_shift) & 0xFF;
|
||||
}
|
||||
}
|
||||
return AOM_CODEC_OK;
|
||||
}
|
||||
|
||||
// UniquePtr dtor wrapper for aom_image_t.
|
||||
struct AomImageFree {
|
||||
void operator()(aom_image_t* img) { aom_img_free(img); }
|
||||
};
|
||||
|
||||
MediaResult
|
||||
AOMDecoder::DoDecode(MediaRawData* aSample)
|
||||
{
|
||||
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
||||
|
||||
#if defined(DEBUG)
|
||||
NS_ASSERTION(IsKeyframe(*aSample) == aSample->mKeyframe,
|
||||
"AOM Decode Keyframe error sample->mKeyframe and si.si_kf out of sync");
|
||||
#endif
|
||||
|
||||
if (aom_codec_err_t r = aom_codec_decode(&mCodec, aSample->Data(), aSample->Size(), nullptr)) {
|
||||
LOG("AOM Decode error: %s", aom_codec_err_to_string(r));
|
||||
return MediaResult(
|
||||
NS_ERROR_DOM_MEDIA_DECODE_ERR,
|
||||
RESULT_DETAIL("AOM error decoding AV1 sample: %s", aom_codec_err_to_string(r)));
|
||||
}
|
||||
|
||||
aom_codec_iter_t iter = nullptr;
|
||||
aom_image_t *img;
|
||||
UniquePtr<aom_image_t, AomImageFree> img8;
|
||||
|
||||
while ((img = aom_codec_get_frame(&mCodec, &iter))) {
|
||||
// Track whether the underlying buffer is 8 or 16 bits per channel.
|
||||
bool highbd = bool(img->fmt & AOM_IMG_FMT_HIGHBITDEPTH);
|
||||
if (highbd) {
|
||||
// Downsample images with more than 8 bits per channel.
|
||||
aom_img_fmt_t fmt8 = static_cast<aom_img_fmt_t>(img->fmt ^ AOM_IMG_FMT_HIGHBITDEPTH);
|
||||
img8.reset(aom_img_alloc(NULL, fmt8, img->d_w, img->d_h, 16));
|
||||
if (img8 == nullptr) {
|
||||
LOG("Couldn't allocate bitdepth reduction target!");
|
||||
return MediaResult(
|
||||
NS_ERROR_OUT_OF_MEMORY,
|
||||
RESULT_DETAIL("Couldn't allocate conversion buffer for AV1 frame"));
|
||||
}
|
||||
if (aom_codec_err_t r = highbd_img_downshift(img8.get(), img, img->bit_depth - 8)) {
|
||||
return MediaResult(
|
||||
NS_ERROR_DOM_MEDIA_DECODE_ERR,
|
||||
RESULT_DETAIL("Error converting AV1 frame to 8 bits: %s",
|
||||
aom_codec_err_to_string(r)));
|
||||
}
|
||||
// img normally points to storage owned by mCodec, so it is not freed.
|
||||
// To copy out the contents of img8 we can overwrite img with an alias.
|
||||
// Since img is assigned at the start of the while loop and img8 is held
|
||||
// outside that loop, the alias won't outlive the storage it points to.
|
||||
img = img8.get();
|
||||
highbd = false;
|
||||
}
|
||||
|
||||
NS_ASSERTION(img->fmt == AOM_IMG_FMT_I420 ||
|
||||
img->fmt == AOM_IMG_FMT_I42016 ||
|
||||
img->fmt == AOM_IMG_FMT_I444 ||
|
||||
img->fmt == AOM_IMG_FMT_I44416,
|
||||
"AV1 image format not I420 or I444");
|
||||
|
||||
// Chroma shifts are rounded down as per the decoding examples in the SDK
|
||||
VideoData::YCbCrBuffer b;
|
||||
b.mPlanes[0].mData = img->planes[0];
|
||||
b.mPlanes[0].mStride = img->stride[0];
|
||||
b.mPlanes[0].mHeight = img->d_h;
|
||||
b.mPlanes[0].mWidth = img->d_w;
|
||||
b.mPlanes[0].mOffset = 0;
|
||||
b.mPlanes[0].mSkip = highbd ? 1 : 0;
|
||||
|
||||
b.mPlanes[1].mData = img->planes[1];
|
||||
b.mPlanes[1].mStride = img->stride[1];
|
||||
b.mPlanes[1].mOffset = 0;
|
||||
b.mPlanes[1].mSkip = highbd ? 1 : 0;
|
||||
|
||||
b.mPlanes[2].mData = img->planes[2];
|
||||
b.mPlanes[2].mStride = img->stride[2];
|
||||
b.mPlanes[2].mOffset = 0;
|
||||
b.mPlanes[2].mSkip = highbd ? 1 : 0;
|
||||
|
||||
if (img->fmt == AOM_IMG_FMT_I420 ||
|
||||
img->fmt == AOM_IMG_FMT_I42016) {
|
||||
b.mPlanes[1].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
|
||||
b.mPlanes[1].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
|
||||
|
||||
b.mPlanes[2].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
|
||||
b.mPlanes[2].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
|
||||
} else if (img->fmt == AOM_IMG_FMT_I444) {
|
||||
b.mPlanes[1].mHeight = img->d_h;
|
||||
b.mPlanes[1].mWidth = img->d_w;
|
||||
|
||||
b.mPlanes[2].mHeight = img->d_h;
|
||||
b.mPlanes[2].mWidth = img->d_w;
|
||||
} else {
|
||||
LOG("AOM Unknown image format");
|
||||
return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
|
||||
RESULT_DETAIL("AOM Unknown image format"));
|
||||
}
|
||||
|
||||
switch (img->mc) {
|
||||
case AOM_CICP_MC_BT_601:
|
||||
b.mYUVColorSpace = YUVColorSpace::BT601;
|
||||
break;
|
||||
case AOM_CICP_MC_BT_709:
|
||||
b.mYUVColorSpace = YUVColorSpace::BT709;
|
||||
break;
|
||||
case AOM_CICP_MC_IDENTITY:
|
||||
b.mYUVColorSpace = YUVColorSpace::IDENTITY;
|
||||
break;
|
||||
default:
|
||||
LOG("Unhandled colorspace %d", img->mc);
|
||||
break;
|
||||
}
|
||||
b.mColorRange = img->range == AOM_CR_FULL_RANGE ? ColorRange::FULL
|
||||
: ColorRange::LIMITED;
|
||||
|
||||
RefPtr<VideoData> v =
|
||||
VideoData::CreateAndCopyData(mInfo,
|
||||
mImageContainer,
|
||||
aSample->mOffset,
|
||||
aSample->mTime,
|
||||
aSample->mDuration,
|
||||
b,
|
||||
aSample->mKeyframe,
|
||||
aSample->mTimecode,
|
||||
mInfo.ScaledImageRect(img->d_w,
|
||||
img->d_h));
|
||||
|
||||
if (!v) {
|
||||
LOG("Image allocation error source %ux%u display %ux%u picture %ux%u",
|
||||
img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height,
|
||||
mInfo.mImage.width, mInfo.mImage.height);
|
||||
return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__);
|
||||
}
|
||||
mCallback->Output(v);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
AOMDecoder::ProcessDecode(MediaRawData* aSample)
|
||||
{
|
||||
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
||||
if (mIsFlushing) {
|
||||
return;
|
||||
}
|
||||
MediaResult rv = DoDecode(aSample);
|
||||
if (NS_FAILED(rv)) {
|
||||
mCallback->Error(rv);
|
||||
} else {
|
||||
mCallback->InputExhausted();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AOMDecoder::Input(MediaRawData* aSample)
|
||||
{
|
||||
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
|
||||
mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
|
||||
this, &AOMDecoder::ProcessDecode, aSample));
|
||||
}
|
||||
|
||||
void
|
||||
AOMDecoder::ProcessDrain()
|
||||
{
|
||||
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
||||
mCallback->DrainComplete();
|
||||
}
|
||||
|
||||
void
|
||||
AOMDecoder::Drain()
|
||||
{
|
||||
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
|
||||
mTaskQueue->Dispatch(NewRunnableMethod(this, &AOMDecoder::ProcessDrain));
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool
|
||||
AOMDecoder::IsAV1(const nsACString& aMimeType)
|
||||
{
|
||||
return aMimeType.EqualsLiteral("video/webm; codecs=av1") ||
|
||||
aMimeType.EqualsLiteral("video/av1");
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool
|
||||
AOMDecoder::IsKeyframe(Span<const uint8_t> aBuffer) {
|
||||
aom_codec_stream_info_t info;
|
||||
PodZero(&info);
|
||||
|
||||
aom_codec_peek_stream_info(aom_codec_av1_dx(),
|
||||
aBuffer.Elements(),
|
||||
aBuffer.Length(),
|
||||
&info);
|
||||
|
||||
return bool(info.is_kf);
|
||||
}
|
||||
|
||||
/* static */
|
||||
nsIntSize
|
||||
AOMDecoder::GetFrameSize(Span<const uint8_t> aBuffer) {
|
||||
aom_codec_stream_info_t info;
|
||||
PodZero(&info);
|
||||
|
||||
aom_codec_peek_stream_info(aom_codec_av1_dx(),
|
||||
aBuffer.Elements(),
|
||||
aBuffer.Length(),
|
||||
&info);
|
||||
|
||||
return nsIntSize(info.w, info.h);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
#undef LOG
|
||||
@@ -1,61 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#if !defined(AOMDecoder_h_)
|
||||
#define AOMDecoder_h_
|
||||
|
||||
#include "PlatformDecoderModule.h"
|
||||
#include "mozilla/Span.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include "aom/aom_decoder.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class AOMDecoder : public MediaDataDecoder
|
||||
{
|
||||
public:
|
||||
explicit AOMDecoder(const CreateDecoderParams& aParams);
|
||||
|
||||
RefPtr<InitPromise> Init() override;
|
||||
void Input(MediaRawData* aSample) override;
|
||||
void Flush() override;
|
||||
void Drain() override;
|
||||
void Shutdown() override;
|
||||
const char* GetDescriptionName() const override
|
||||
{
|
||||
return "libaom (AV1) video decoder";
|
||||
}
|
||||
|
||||
// Return true if aMimeType is a one of the strings used
|
||||
// by our demuxers to identify AV1 streams.
|
||||
static bool IsAV1(const nsACString& aMimeType);
|
||||
|
||||
// Return true if a sample is a keyframe.
|
||||
static bool IsKeyframe(Span<const uint8_t> aBuffer);
|
||||
|
||||
// Return the frame dimensions for a sample.
|
||||
static nsIntSize GetFrameSize(Span<const uint8_t> aBuffer);
|
||||
|
||||
private:
|
||||
~AOMDecoder();
|
||||
void ProcessDecode(MediaRawData* aSample);
|
||||
MediaResult DoDecode(MediaRawData* aSample);
|
||||
void ProcessDrain();
|
||||
|
||||
const RefPtr<layers::ImageContainer> mImageContainer;
|
||||
const RefPtr<TaskQueue> mTaskQueue;
|
||||
MediaDataDecoderCallback* mCallback;
|
||||
Atomic<bool> mIsFlushing;
|
||||
|
||||
// AOM decoder state
|
||||
aom_codec_ctx_t mCodec;
|
||||
|
||||
const VideoInfo& mInfo;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // AOMDecoder_h_
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "TheoraDecoder.h"
|
||||
|
||||
#ifdef MOZ_AV1
|
||||
#include "AOMDecoder.h"
|
||||
#include "Dav1dDecoder.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
@@ -30,7 +30,7 @@ AgnosticDecoderModule::SupportsMimeType(const nsACString& aMimeType,
|
||||
TheoraDecoder::IsTheora(aMimeType);
|
||||
#ifdef MOZ_AV1
|
||||
if (MediaPrefs::AV1Enabled()) {
|
||||
supports |= AOMDecoder::IsAV1(aMimeType);
|
||||
supports |= Dav1dDecoder::IsAV1(aMimeType);
|
||||
}
|
||||
#endif
|
||||
MOZ_LOG(sPDMLog, LogLevel::Debug, ("Agnostic decoder %s requested type",
|
||||
@@ -47,9 +47,9 @@ AgnosticDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
|
||||
m = new VPXDecoder(aParams);
|
||||
}
|
||||
#ifdef MOZ_AV1
|
||||
else if (AOMDecoder::IsAV1(aParams.mConfig.mMimeType) &&
|
||||
else if (Dav1dDecoder::IsAV1(aParams.mConfig.mMimeType) &&
|
||||
MediaPrefs::AV1Enabled()) {
|
||||
m = new AOMDecoder(aParams);
|
||||
m = new Dav1dDecoder(aParams);
|
||||
}
|
||||
#endif
|
||||
else if (TheoraDecoder::IsTheora(aParams.mConfig.mMimeType)) {
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "Dav1dDecoder.h"
|
||||
#include "MediaResult.h"
|
||||
#include "TimeUnits.h"
|
||||
#include "dav1d/dav1d.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include "mozilla/SyncRunnable.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/UniquePtrExtensions.h"
|
||||
#include "nsError.h"
|
||||
#include "prsystem.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#undef LOG
|
||||
#define LOG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("Dav1dDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace gfx;
|
||||
using namespace layers;
|
||||
|
||||
Dav1dDecoder::Dav1dDecoder(const CreateDecoderParams& aParams)
|
||||
: mImageContainer(aParams.mImageContainer)
|
||||
, mTaskQueue(aParams.mTaskQueue)
|
||||
, mCallback(aParams.mCallback)
|
||||
, mIsFlushing(false)
|
||||
, mDecoder(nullptr)
|
||||
, mInfo(aParams.VideoConfig())
|
||||
{
|
||||
}
|
||||
|
||||
Dav1dDecoder::~Dav1dDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
Dav1dDecoder::Shutdown()
|
||||
{
|
||||
if (mDecoder) {
|
||||
dav1d_close(&mDecoder);
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<MediaDataDecoder::InitPromise>
|
||||
Dav1dDecoder::Init()
|
||||
{
|
||||
int decodeThreads = 2;
|
||||
if (mInfo.mDisplay.width >= 2048) {
|
||||
decodeThreads = 8;
|
||||
} else if (mInfo.mDisplay.width >= 1024) {
|
||||
decodeThreads = 4;
|
||||
}
|
||||
decodeThreads = std::min(decodeThreads, PR_GetNumberOfProcessors());
|
||||
|
||||
Dav1dSettings settings;
|
||||
dav1d_default_settings(&settings);
|
||||
settings.n_threads = decodeThreads;
|
||||
settings.logger.callback = nullptr;
|
||||
|
||||
if (dav1d_open(&mDecoder, &settings) < 0) {
|
||||
return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
|
||||
}
|
||||
return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
|
||||
}
|
||||
|
||||
void
|
||||
Dav1dDecoder::Flush()
|
||||
{
|
||||
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
|
||||
mIsFlushing = true;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () {
|
||||
if (mDecoder) {
|
||||
dav1d_flush(mDecoder);
|
||||
}
|
||||
});
|
||||
SyncRunnable::DispatchToThread(mTaskQueue, r);
|
||||
mIsFlushing = false;
|
||||
}
|
||||
|
||||
static bool
|
||||
GetPlaneSize(int aWidth, int aHeight, size_t* aSize)
|
||||
{
|
||||
CheckedInt<size_t> size = aWidth;
|
||||
size *= aHeight;
|
||||
if (!size.isValid()) {
|
||||
return false;
|
||||
}
|
||||
*aSize = size.value();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
DownshiftPlane(uint8_t* aDst, int aDstStride, const uint8_t* aSrc,
|
||||
ptrdiff_t aSrcStride, int aWidth, int aHeight, int aShift)
|
||||
{
|
||||
for (int y = 0; y < aHeight; y++) {
|
||||
const uint16_t* src =
|
||||
reinterpret_cast<const uint16_t*>(aSrc + y * aSrcStride);
|
||||
uint8_t* dst = aDst + y * aDstStride;
|
||||
for (int x = 0; x < aWidth; x++) {
|
||||
dst[x] = (src[x] >> aShift) & 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MediaResult
|
||||
Dav1dDecoder::OutputPicture(const Dav1dPicture& aPicture)
|
||||
{
|
||||
const int width = aPicture.p.w;
|
||||
const int height = aPicture.p.h;
|
||||
if (width <= 0 || height <= 0) {
|
||||
return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
|
||||
RESULT_DETAIL("dav1d returned invalid AV1 picture size"));
|
||||
}
|
||||
|
||||
int chromaWidth = width;
|
||||
int chromaHeight = height;
|
||||
switch (aPicture.p.layout) {
|
||||
case DAV1D_PIXEL_LAYOUT_I420:
|
||||
chromaWidth = (width + 1) >> 1;
|
||||
chromaHeight = (height + 1) >> 1;
|
||||
break;
|
||||
case DAV1D_PIXEL_LAYOUT_I422:
|
||||
chromaWidth = (width + 1) >> 1;
|
||||
chromaHeight = height;
|
||||
break;
|
||||
case DAV1D_PIXEL_LAYOUT_I444:
|
||||
break;
|
||||
default:
|
||||
return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
|
||||
RESULT_DETAIL("dav1d returned unsupported AV1 pixel layout: %d",
|
||||
int(aPicture.p.layout)));
|
||||
}
|
||||
|
||||
const int planeWidths[3] = { width, chromaWidth, chromaWidth };
|
||||
const int planeHeights[3] = { height, chromaHeight, chromaHeight };
|
||||
const ptrdiff_t srcStrides[3] = {
|
||||
aPicture.stride[0],
|
||||
aPicture.stride[1],
|
||||
aPicture.stride[1]
|
||||
};
|
||||
uint8_t* planeData[3] = {
|
||||
static_cast<uint8_t*>(aPicture.data[0]),
|
||||
static_cast<uint8_t*>(aPicture.data[1]),
|
||||
static_cast<uint8_t*>(aPicture.data[2])
|
||||
};
|
||||
uint32_t planeStrides[3] = { 0, 0, 0 };
|
||||
UniquePtr<uint8_t[]> downshifted[3];
|
||||
|
||||
if (aPicture.p.bpc > 8) {
|
||||
const int shift = aPicture.p.bpc - 8;
|
||||
for (int plane = 0; plane < 3; plane++) {
|
||||
size_t planeSize;
|
||||
if (!GetPlaneSize(planeWidths[plane], planeHeights[plane], &planeSize)) {
|
||||
return MediaResult(NS_ERROR_OUT_OF_MEMORY,
|
||||
RESULT_DETAIL("AV1 downshift plane size overflow"));
|
||||
}
|
||||
downshifted[plane] = MakeUniqueFallible<uint8_t[]>(planeSize);
|
||||
if (!downshifted[plane]) {
|
||||
return MediaResult(NS_ERROR_OUT_OF_MEMORY,
|
||||
RESULT_DETAIL("Couldn't allocate AV1 conversion buffer"));
|
||||
}
|
||||
DownshiftPlane(downshifted[plane].get(), planeWidths[plane],
|
||||
planeData[plane], srcStrides[plane],
|
||||
planeWidths[plane], planeHeights[plane], shift);
|
||||
planeData[plane] = downshifted[plane].get();
|
||||
planeStrides[plane] = planeWidths[plane];
|
||||
}
|
||||
} else {
|
||||
for (int plane = 0; plane < 3; plane++) {
|
||||
CheckedInt<uint32_t> stride(srcStrides[plane]);
|
||||
if (srcStrides[plane] < 0 || !stride.isValid()) {
|
||||
return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
|
||||
RESULT_DETAIL("dav1d returned invalid AV1 stride"));
|
||||
}
|
||||
planeStrides[plane] = stride.value();
|
||||
}
|
||||
}
|
||||
|
||||
VideoData::YCbCrBuffer b;
|
||||
for (int plane = 0; plane < 3; plane++) {
|
||||
b.mPlanes[plane].mData = planeData[plane];
|
||||
b.mPlanes[plane].mStride = planeStrides[plane];
|
||||
b.mPlanes[plane].mHeight = planeHeights[plane];
|
||||
b.mPlanes[plane].mWidth = planeWidths[plane];
|
||||
b.mPlanes[plane].mOffset = 0;
|
||||
b.mPlanes[plane].mSkip = 0;
|
||||
}
|
||||
|
||||
if (aPicture.seq_hdr) {
|
||||
switch (aPicture.seq_hdr->mtrx) {
|
||||
case DAV1D_MC_BT601:
|
||||
case DAV1D_MC_BT470BG:
|
||||
b.mYUVColorSpace = YUVColorSpace::BT601;
|
||||
break;
|
||||
case DAV1D_MC_BT709:
|
||||
b.mYUVColorSpace = YUVColorSpace::BT709;
|
||||
break;
|
||||
case DAV1D_MC_IDENTITY:
|
||||
b.mYUVColorSpace = YUVColorSpace::IDENTITY;
|
||||
break;
|
||||
default:
|
||||
LOG("Unhandled colorspace %d", aPicture.seq_hdr->mtrx);
|
||||
break;
|
||||
}
|
||||
b.mColorRange = aPicture.seq_hdr->color_range ? ColorRange::FULL
|
||||
: ColorRange::LIMITED;
|
||||
}
|
||||
|
||||
const int64_t time =
|
||||
aPicture.m.timestamp == INT64_MIN ? 0 : aPicture.m.timestamp;
|
||||
const int64_t duration = aPicture.m.duration;
|
||||
const int64_t offset = aPicture.m.offset;
|
||||
const bool keyframe =
|
||||
aPicture.frame_hdr &&
|
||||
aPicture.frame_hdr->frame_type == DAV1D_FRAME_TYPE_KEY;
|
||||
|
||||
RefPtr<VideoData> v =
|
||||
VideoData::CreateAndCopyData(mInfo,
|
||||
mImageContainer,
|
||||
offset,
|
||||
time,
|
||||
duration,
|
||||
b,
|
||||
keyframe,
|
||||
time,
|
||||
mInfo.ScaledImageRect(width, height));
|
||||
|
||||
if (!v) {
|
||||
LOG("Image allocation error source %dx%d display %ux%u picture %ux%u",
|
||||
width, height, mInfo.mDisplay.width, mInfo.mDisplay.height,
|
||||
mInfo.mImage.width, mInfo.mImage.height);
|
||||
return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__);
|
||||
}
|
||||
mCallback->Output(v);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MediaResult
|
||||
Dav1dDecoder::DrainOutput()
|
||||
{
|
||||
while (true) {
|
||||
Dav1dPicture picture;
|
||||
PodZero(&picture);
|
||||
const int res = dav1d_get_picture(mDecoder, &picture);
|
||||
if (res == DAV1D_ERR(EAGAIN)) {
|
||||
return NS_OK;
|
||||
}
|
||||
if (res < 0) {
|
||||
return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
|
||||
RESULT_DETAIL("dav1d error getting AV1 picture: %d",
|
||||
res));
|
||||
}
|
||||
|
||||
MediaResult rv = OutputPicture(picture);
|
||||
dav1d_picture_unref(&picture);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MediaResult
|
||||
Dav1dDecoder::DoDecode(MediaRawData* aSample)
|
||||
{
|
||||
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
||||
|
||||
Dav1dData data;
|
||||
PodZero(&data);
|
||||
uint8_t* dst = dav1d_data_create(&data, aSample->Size());
|
||||
if (!dst) {
|
||||
return MediaResult(NS_ERROR_OUT_OF_MEMORY,
|
||||
RESULT_DETAIL("Couldn't allocate dav1d AV1 input buffer"));
|
||||
}
|
||||
memcpy(dst, aSample->Data(), aSample->Size());
|
||||
data.m.timestamp = aSample->mTime;
|
||||
data.m.duration = aSample->mDuration;
|
||||
data.m.offset = aSample->mOffset;
|
||||
data.m.size = aSample->Size();
|
||||
|
||||
while (data.sz) {
|
||||
const int res = dav1d_send_data(mDecoder, &data);
|
||||
if (res == DAV1D_ERR(EAGAIN)) {
|
||||
MediaResult rv = DrainOutput();
|
||||
if (NS_FAILED(rv)) {
|
||||
dav1d_data_unref(&data);
|
||||
return rv;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (res < 0) {
|
||||
dav1d_data_unref(&data);
|
||||
return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
|
||||
RESULT_DETAIL("dav1d error decoding AV1 sample: %d",
|
||||
res));
|
||||
}
|
||||
}
|
||||
|
||||
dav1d_data_unref(&data);
|
||||
return DrainOutput();
|
||||
}
|
||||
|
||||
void
|
||||
Dav1dDecoder::ProcessDecode(MediaRawData* aSample)
|
||||
{
|
||||
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
||||
if (mIsFlushing) {
|
||||
return;
|
||||
}
|
||||
MediaResult rv = DoDecode(aSample);
|
||||
if (NS_FAILED(rv)) {
|
||||
mCallback->Error(rv);
|
||||
} else {
|
||||
mCallback->InputExhausted();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Dav1dDecoder::Input(MediaRawData* aSample)
|
||||
{
|
||||
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
|
||||
mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
|
||||
this, &Dav1dDecoder::ProcessDecode, aSample));
|
||||
}
|
||||
|
||||
void
|
||||
Dav1dDecoder::ProcessDrain()
|
||||
{
|
||||
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
||||
MediaResult rv = DrainOutput();
|
||||
if (NS_FAILED(rv)) {
|
||||
mCallback->Error(rv);
|
||||
} else {
|
||||
mCallback->DrainComplete();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Dav1dDecoder::Drain()
|
||||
{
|
||||
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
|
||||
mTaskQueue->Dispatch(NewRunnableMethod(this, &Dav1dDecoder::ProcessDrain));
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool
|
||||
Dav1dDecoder::IsAV1(const nsACString& aMimeType)
|
||||
{
|
||||
return aMimeType.EqualsLiteral("video/webm; codecs=av1") ||
|
||||
aMimeType.EqualsLiteral("video/av1");
|
||||
}
|
||||
|
||||
/* static */
|
||||
nsIntSize
|
||||
Dav1dDecoder::GetFrameSize(Span<const uint8_t> aBuffer)
|
||||
{
|
||||
Dav1dSequenceHeader seqHdr;
|
||||
PodZero(&seqHdr);
|
||||
if (dav1d_parse_sequence_header(&seqHdr,
|
||||
aBuffer.Elements(),
|
||||
aBuffer.Length()) < 0) {
|
||||
return nsIntSize(0, 0);
|
||||
}
|
||||
return nsIntSize(seqHdr.max_width, seqHdr.max_height);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
#undef LOG
|
||||
@@ -0,0 +1,61 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#if !defined(Dav1dDecoder_h_)
|
||||
#define Dav1dDecoder_h_
|
||||
|
||||
#include "PlatformDecoderModule.h"
|
||||
#include "mozilla/Span.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct Dav1dContext Dav1dContext;
|
||||
typedef struct Dav1dPicture Dav1dPicture;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class Dav1dDecoder : public MediaDataDecoder
|
||||
{
|
||||
public:
|
||||
explicit Dav1dDecoder(const CreateDecoderParams& aParams);
|
||||
|
||||
RefPtr<InitPromise> Init() override;
|
||||
void Input(MediaRawData* aSample) override;
|
||||
void Flush() override;
|
||||
void Drain() override;
|
||||
void Shutdown() override;
|
||||
const char* GetDescriptionName() const override
|
||||
{
|
||||
return "dav1d (AV1) video decoder";
|
||||
}
|
||||
|
||||
// Return true if aMimeType is a one of the strings used
|
||||
// by our demuxers to identify AV1 streams.
|
||||
static bool IsAV1(const nsACString& aMimeType);
|
||||
|
||||
// Return the frame dimensions from a sequence header, when one is present.
|
||||
static nsIntSize GetFrameSize(Span<const uint8_t> aBuffer);
|
||||
|
||||
private:
|
||||
~Dav1dDecoder();
|
||||
void ProcessDecode(MediaRawData* aSample);
|
||||
MediaResult DoDecode(MediaRawData* aSample);
|
||||
MediaResult DrainOutput();
|
||||
MediaResult OutputPicture(const Dav1dPicture& aPicture);
|
||||
void ProcessDrain();
|
||||
|
||||
const RefPtr<layers::ImageContainer> mImageContainer;
|
||||
const RefPtr<TaskQueue> mTaskQueue;
|
||||
MediaDataDecoderCallback* mCallback;
|
||||
Atomic<bool> mIsFlushing;
|
||||
|
||||
Dav1dContext* mDecoder;
|
||||
|
||||
const VideoInfo& mInfo;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // Dav1dDecoder_h_
|
||||
@@ -58,10 +58,10 @@ if CONFIG['MOZ_FFMPEG']:
|
||||
|
||||
if CONFIG['MOZ_AV1']:
|
||||
EXPORTS += [
|
||||
'agnostic/AOMDecoder.h',
|
||||
'agnostic/Dav1dDecoder.h',
|
||||
]
|
||||
UNIFIED_SOURCES += [
|
||||
'agnostic/AOMDecoder.cpp',
|
||||
'agnostic/Dav1dDecoder.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_APPLEMEDIA']:
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/Preferences.h"
|
||||
#ifdef MOZ_AV1
|
||||
#include "AOMDecoder.h"
|
||||
#endif
|
||||
#include "MediaPrefs.h"
|
||||
#include "MediaDecoderStateMachine.h"
|
||||
#include "WebMDemuxer.h"
|
||||
@@ -136,4 +133,3 @@ WebMDecoder::GetMozDebugReaderData(nsAString& aString)
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "MediaResource.h"
|
||||
#ifdef MOZ_AV1
|
||||
#include "AOMDecoder.h"
|
||||
#include "Dav1dDecoder.h"
|
||||
#endif
|
||||
#include "OpusDecoder.h"
|
||||
#include "VPXDecoder.h"
|
||||
@@ -711,7 +711,7 @@ WebMDemuxer::GetNextPacket(TrackInfo::TrackType aType, MediaRawDataQueue *aSampl
|
||||
break;
|
||||
#ifdef MOZ_AV1
|
||||
case NESTEGG_CODEC_AV1:
|
||||
isKeyframe = AOMDecoder::IsKeyframe(sample);
|
||||
isKeyframe = nestegg_packet_has_keyframe(holder->Packet()) == NESTEGG_PACKET_HAS_KEYFRAME_TRUE;
|
||||
break;
|
||||
#endif
|
||||
case NESTEGG_CODEC_AVC1:
|
||||
@@ -734,16 +734,18 @@ WebMDemuxer::GetNextPacket(TrackInfo::TrackType aType, MediaRawDataQueue *aSampl
|
||||
break;
|
||||
#ifdef MOZ_AV1
|
||||
case NESTEGG_CODEC_AV1:
|
||||
dimensions = AOMDecoder::GetFrameSize(sample);
|
||||
dimensions = Dav1dDecoder::GetFrameSize(sample);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
if (mLastSeenFrameSize.isSome()
|
||||
&& (dimensions != mLastSeenFrameSize.value())) {
|
||||
mInfo.mVideo.mDisplay = dimensions;
|
||||
mSharedVideoTrackInfo = new SharedTrackInfo(mInfo.mVideo, ++sStreamSourceID);
|
||||
if (dimensions.width > 0 && dimensions.height > 0) {
|
||||
if (mLastSeenFrameSize.isSome()
|
||||
&& (dimensions != mLastSeenFrameSize.value())) {
|
||||
mInfo.mVideo.mDisplay = dimensions;
|
||||
mSharedVideoTrackInfo = new SharedTrackInfo(mInfo.mVideo, ++sStreamSourceID);
|
||||
}
|
||||
mLastSeenFrameSize = Some(dimensions);
|
||||
}
|
||||
mLastSeenFrameSize = Some(dimensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@ if CONFIG['MOZ_WEBM_ENCODER']:
|
||||
'WebMWriter.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_AV1']:
|
||||
LOCAL_INCLUDES += [
|
||||
'/dom/media/platforms/agnostic',
|
||||
]
|
||||
|
||||
CXXFLAGS += CONFIG['MOZ_LIBVPX_CFLAGS']
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
@@ -301,7 +301,6 @@ LoadContextOptions(const char* aPrefName, void* /* aClosure */)
|
||||
.setAsyncStack(GetWorkerPref<bool>(NS_LITERAL_CSTRING("asyncstack")))
|
||||
.setWerror(GetWorkerPref<bool>(NS_LITERAL_CSTRING("werror")))
|
||||
.setStreams(GetWorkerPref<bool>(NS_LITERAL_CSTRING("streams")))
|
||||
.setWeakRefs(GetWorkerPref<bool>(NS_LITERAL_CSTRING("weakrefs")))
|
||||
.setExtraWarnings(GetWorkerPref<bool>(NS_LITERAL_CSTRING("strict")))
|
||||
.setArrayProtoValues(GetWorkerPref<bool>(
|
||||
NS_LITERAL_CSTRING("array_prototype_values")));
|
||||
|
||||
@@ -35,7 +35,6 @@ WORKER_SIMPLE_PREF("dom.serviceWorkers.openWindow.enabled", OpenWindowEnabled, O
|
||||
WORKER_SIMPLE_PREF("dom.storageManager.enabled", StorageManagerEnabled, STORAGEMANAGER_ENABLED)
|
||||
WORKER_SIMPLE_PREF("dom.push.enabled", PushEnabled, PUSH_ENABLED)
|
||||
WORKER_SIMPLE_PREF("dom.streams.enabled", StreamsEnabled, STREAMS_ENABLED)
|
||||
WORKER_SIMPLE_PREF("javascript.options.weakrefs", WeakRefsEnabled, WEAKREFS_ENABLED)
|
||||
WORKER_SIMPLE_PREF("dom.requestcontext.enabled", RequestContextEnabled, REQUESTCONTEXT_ENABLED)
|
||||
WORKER_SIMPLE_PREF("gfx.offscreencanvas.enabled", OffscreenCanvasEnabled, OFFSCREENCANVAS_ENABLED)
|
||||
WORKER_SIMPLE_PREF("dom.webkitBlink.dirPicker.enabled", WebkitBlinkDirectoryPickerEnabled, DOM_WEBKITBLINK_DIRPICKER_WEBKITBLINK)
|
||||
|
||||
@@ -823,16 +823,37 @@ nsBindingManager::WalkAllRules(nsIStyleRuleProcessor::EnumFunc aFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// The approach in WalkAllShadowRootHostRules seems reasonable on the surface and reminds me of what is done with mScopedRoot elsewhere. But something important is missing, either here or in the code that would normally use it.
|
||||
|
||||
void
|
||||
nsBindingManager::WalkAllShadowRootHostRules(nsIStyleRuleProcessor::EnumFunc aFunc,
|
||||
ElementDependentRuleProcessorData* aData)
|
||||
{
|
||||
aData->mTreeMatchContext.mOnlyMatchHostPseudo = true;
|
||||
WalkAllRules(aFunc, aData, true);
|
||||
aData->mTreeMatchContext.mOnlyMatchHostPseudo = false;
|
||||
}
|
||||
{
|
||||
if (!mBoundContentSet) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool oldOnlyMatchHostPseudo = aData->mTreeMatchContext.mOnlyMatchHostPseudo;
|
||||
nsIContent* oldScopedRoot = aData->mTreeMatchContext.mScopedRoot;
|
||||
aData->mTreeMatchContext.mOnlyMatchHostPseudo = true;
|
||||
|
||||
for (auto iter = mBoundContentSet->Iter(); !iter.Done(); iter.Next()) {
|
||||
nsIContent* boundContent = iter.Get()->GetKey();
|
||||
ShadowRoot* shadowRoot = boundContent->GetShadowRoot();
|
||||
if (!shadowRoot) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nsXBLBinding* binding = shadowRoot->GetAssociatedBinding();
|
||||
if (!binding) {
|
||||
continue;
|
||||
}
|
||||
|
||||
aData->mTreeMatchContext.mScopedRoot = boundContent;
|
||||
binding->WalkRules(aFunc, aData);
|
||||
}
|
||||
|
||||
aData->mTreeMatchContext.mScopedRoot = oldScopedRoot;
|
||||
aData->mTreeMatchContext.mOnlyMatchHostPseudo = oldOnlyMatchHostPseudo;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsBindingManager::MediumFeaturesChanged(nsPresContext* aPresContext,
|
||||
|
||||
@@ -446,7 +446,7 @@ for (k = 0; k < 2; k++) {
|
||||
while ((c = fgetc(f)) != '\n' && c != EOF);
|
||||
/* issue warning if not a comment */
|
||||
if (buf[0] != '%') {
|
||||
fprintf(stderr, "Warning: skipping too long pattern (more than %lu chars)\n", sizeof(buf));
|
||||
fprintf(stderr, "Warning: skipping too long pattern (more than %zu chars)\n", sizeof(buf));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -47,10 +47,12 @@
|
||||
#include "builtin/AtomicsObject.h"
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Casting.h"
|
||||
#include "mozilla/FloatingPoint.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
#include "builtin/Promise.h"
|
||||
#include "jsapi.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "jsnum.h"
|
||||
@@ -58,7 +60,9 @@
|
||||
#include "jit/AtomicOperations.h"
|
||||
#include "jit/InlinableNatives.h"
|
||||
#include "js/Class.h"
|
||||
#include "vm/BigIntType.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/HelperThreads.h"
|
||||
#include "vm/Time.h"
|
||||
#include "vm/TypedArrayObject.h"
|
||||
#include "wasm/WasmInstance.h"
|
||||
@@ -121,6 +125,68 @@ GetTypedArrayIndex(JSContext* cx, HandleValue v, Handle<TypedArrayObject*> view,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
IsWaitableTypedArray(Scalar::Type viewType)
|
||||
{
|
||||
return viewType == Scalar::Int32 || viewType == Scalar::BigInt64;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
GetWaiterByteOffset(Handle<TypedArrayObject*> view, uint32_t offset)
|
||||
{
|
||||
return view->byteOffset() + offset * TypedArrayElemSize(view->type());
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
BigIntToRawBits(Scalar::Type viewType, BigInt* value)
|
||||
{
|
||||
if (viewType == Scalar::BigInt64)
|
||||
return uint64_t(BigInt::toInt64(value));
|
||||
MOZ_ASSERT(viewType == Scalar::BigUint64);
|
||||
return BigInt::toUint64(value);
|
||||
}
|
||||
|
||||
static bool
|
||||
BigIntToRawBits(JSContext* cx, Scalar::Type viewType, HandleValue value, uint64_t* bits)
|
||||
{
|
||||
MOZ_ASSERT(Scalar::isBigIntType(viewType));
|
||||
|
||||
RootedBigInt bigint(cx, ToBigInt(cx, value));
|
||||
if (!bigint)
|
||||
return false;
|
||||
|
||||
*bits = BigIntToRawBits(viewType, bigint);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
SetBigIntResult(JSContext* cx, Scalar::Type viewType, uint64_t bits, MutableHandleValue result)
|
||||
{
|
||||
MOZ_ASSERT(Scalar::isBigIntType(viewType));
|
||||
|
||||
BigInt* bigint = viewType == Scalar::BigInt64
|
||||
? BigInt::createFromInt64(cx, mozilla::BitwiseCast<int64_t>(bits))
|
||||
: BigInt::createFromUint64(cx, bits);
|
||||
if (!bigint)
|
||||
return false;
|
||||
|
||||
result.setBigInt(bigint);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
WaitValueMatches(Handle<TypedArrayObject*> view, uint32_t offset, uint64_t expected)
|
||||
{
|
||||
SharedMem<void*> viewData = view->viewDataShared();
|
||||
if (view->type() == Scalar::BigInt64) {
|
||||
return jit::AtomicOperations::loadSafeWhenRacy(viewData.cast<uint64_t*>() + offset) ==
|
||||
expected;
|
||||
}
|
||||
|
||||
return uint32_t(jit::AtomicOperations::loadSafeWhenRacy(viewData.cast<int32_t*>() + offset)) ==
|
||||
uint32_t(expected);
|
||||
}
|
||||
|
||||
static int32_t
|
||||
CompareExchange(Scalar::Type viewType, int32_t oldCandidate, int32_t newCandidate,
|
||||
SharedMem<void*> viewData, uint32_t offset, bool* badArrayType = nullptr)
|
||||
@@ -191,6 +257,20 @@ js::atomics_compareExchange(JSContext* cx, unsigned argc, Value* vp)
|
||||
uint32_t offset;
|
||||
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
|
||||
return false;
|
||||
|
||||
if (Scalar::isBigIntType(view->type())) {
|
||||
uint64_t oldCandidate;
|
||||
if (!BigIntToRawBits(cx, view->type(), oldv, &oldCandidate))
|
||||
return false;
|
||||
uint64_t newCandidate;
|
||||
if (!BigIntToRawBits(cx, view->type(), newv, &newCandidate))
|
||||
return false;
|
||||
|
||||
uint64_t result = jit::AtomicOperations::compareExchangeSeqCst(
|
||||
view->viewDataShared().cast<uint64_t*>() + offset, oldCandidate, newCandidate);
|
||||
return SetBigIntResult(cx, view->type(), result, r);
|
||||
}
|
||||
|
||||
int32_t oldCandidate;
|
||||
if (!ToInt32(cx, oldv, &oldCandidate))
|
||||
return false;
|
||||
@@ -259,6 +339,22 @@ js::atomics_load(JSContext* cx, unsigned argc, Value* vp)
|
||||
r.setNumber(v);
|
||||
return true;
|
||||
}
|
||||
case Scalar::BigInt64: {
|
||||
int64_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<int64_t*>() + offset);
|
||||
BigInt* bigint = BigInt::createFromInt64(cx, v);
|
||||
if (!bigint)
|
||||
return false;
|
||||
r.setBigInt(bigint);
|
||||
return true;
|
||||
}
|
||||
case Scalar::BigUint64: {
|
||||
uint64_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<uint64_t*>() + offset);
|
||||
BigInt* bigint = BigInt::createFromUint64(cx, v);
|
||||
if (!bigint)
|
||||
return false;
|
||||
r.setBigInt(bigint);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return ReportBadArrayType(cx);
|
||||
}
|
||||
@@ -337,6 +433,25 @@ ExchangeOrStore(JSContext* cx, unsigned argc, Value* vp)
|
||||
uint32_t offset;
|
||||
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
|
||||
return false;
|
||||
|
||||
if (Scalar::isBigIntType(view->type())) {
|
||||
RootedBigInt bigint(cx, ToBigInt(cx, valv));
|
||||
if (!bigint)
|
||||
return false;
|
||||
|
||||
uint64_t value = BigIntToRawBits(view->type(), bigint);
|
||||
if (op == DoStore) {
|
||||
jit::AtomicOperations::storeSeqCst(view->viewDataShared().cast<uint64_t*>() + offset,
|
||||
value);
|
||||
r.setBigInt(bigint);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t result = jit::AtomicOperations::exchangeSeqCst(
|
||||
view->viewDataShared().cast<uint64_t*>() + offset, value);
|
||||
return SetBigIntResult(cx, view->type(), result, r);
|
||||
}
|
||||
|
||||
double integerValue;
|
||||
if (!ToInteger(cx, valv, &integerValue))
|
||||
return false;
|
||||
@@ -380,6 +495,24 @@ AtomicsBinop(JSContext* cx, HandleValue objv, HandleValue idxv, HandleValue valv
|
||||
uint32_t offset;
|
||||
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
|
||||
return false;
|
||||
|
||||
if (Scalar::isBigIntType(view->type())) {
|
||||
uint64_t value;
|
||||
if (!BigIntToRawBits(cx, view->type(), valv, &value))
|
||||
return false;
|
||||
|
||||
SharedMem<uint64_t*> addr = view->viewDataShared().cast<uint64_t*>() + offset;
|
||||
uint64_t old = jit::AtomicOperations::loadSeqCst(addr);
|
||||
for (;;) {
|
||||
uint64_t replacement = T::operate64(old, value);
|
||||
uint64_t observed = jit::AtomicOperations::compareExchangeSeqCst(addr, old,
|
||||
replacement);
|
||||
if (observed == old)
|
||||
return SetBigIntResult(cx, view->type(), old, r);
|
||||
old = observed;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t numberValue;
|
||||
if (!ToInt32(cx, valv, &numberValue))
|
||||
return false;
|
||||
@@ -434,6 +567,7 @@ class PerformAdd
|
||||
public:
|
||||
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAddSeqCst)
|
||||
static int32_t perform(int32_t x, int32_t y) { return x + y; }
|
||||
static uint64_t operate64(uint64_t x, uint64_t y) { return x + y; }
|
||||
};
|
||||
|
||||
bool
|
||||
@@ -448,6 +582,7 @@ class PerformSub
|
||||
public:
|
||||
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchSubSeqCst)
|
||||
static int32_t perform(int32_t x, int32_t y) { return x - y; }
|
||||
static uint64_t operate64(uint64_t x, uint64_t y) { return x - y; }
|
||||
};
|
||||
|
||||
bool
|
||||
@@ -462,6 +597,7 @@ class PerformAnd
|
||||
public:
|
||||
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAndSeqCst)
|
||||
static int32_t perform(int32_t x, int32_t y) { return x & y; }
|
||||
static uint64_t operate64(uint64_t x, uint64_t y) { return x & y; }
|
||||
};
|
||||
|
||||
bool
|
||||
@@ -476,6 +612,7 @@ class PerformOr
|
||||
public:
|
||||
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchOrSeqCst)
|
||||
static int32_t perform(int32_t x, int32_t y) { return x | y; }
|
||||
static uint64_t operate64(uint64_t x, uint64_t y) { return x | y; }
|
||||
};
|
||||
|
||||
bool
|
||||
@@ -490,6 +627,7 @@ class PerformXor
|
||||
public:
|
||||
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchXorSeqCst)
|
||||
static int32_t perform(int32_t x, int32_t y) { return x ^ y; }
|
||||
static uint64_t operate64(uint64_t x, uint64_t y) { return x ^ y; }
|
||||
};
|
||||
|
||||
bool
|
||||
@@ -693,11 +831,13 @@ js::atomics_cmpxchg_asm_callout(wasm::Instance* instance, int32_t vt, int32_t of
|
||||
|
||||
namespace js {
|
||||
|
||||
class AtomicsWaitAsyncTask;
|
||||
|
||||
// Represents one waiting worker.
|
||||
//
|
||||
// The type is declared opaque in SharedArrayObject.h. Instances of
|
||||
// js::FutexWaiter are stack-allocated and linked onto a list across a
|
||||
// call to FutexRuntime::wait().
|
||||
// js::FutexWaiter are linked onto a list across a call to FutexRuntime::wait()
|
||||
// or an async wait task.
|
||||
//
|
||||
// The 'waiters' field of the SharedArrayRawBuffer points to the highest
|
||||
// priority waiter in the list, and lower priority nodes are linked through
|
||||
@@ -711,14 +851,34 @@ class FutexWaiter
|
||||
public:
|
||||
FutexWaiter(uint32_t offset, JSRuntime* rt)
|
||||
: offset(offset),
|
||||
kind(Sync),
|
||||
rt(rt),
|
||||
asyncTask(nullptr),
|
||||
lower_pri(nullptr),
|
||||
back(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t offset; // int32 element index within the SharedArrayBuffer
|
||||
FutexWaiter(uint32_t offset, AtomicsWaitAsyncTask* asyncTask)
|
||||
: offset(offset),
|
||||
kind(Async),
|
||||
rt(nullptr),
|
||||
asyncTask(asyncTask),
|
||||
lower_pri(nullptr),
|
||||
back(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
bool isWaiting() const;
|
||||
void notify();
|
||||
|
||||
uint32_t offset; // byte offset within the SharedArrayBuffer
|
||||
enum WaiterKind {
|
||||
Sync,
|
||||
Async
|
||||
} kind;
|
||||
JSRuntime* rt; // The runtime of the waiter
|
||||
AtomicsWaitAsyncTask* asyncTask; // The async waiter task, if any
|
||||
FutexWaiter* lower_pri; // Lower priority nodes in circular doubly-linked list of waiters
|
||||
FutexWaiter* back; // Other direction
|
||||
};
|
||||
@@ -742,8 +902,195 @@ class AutoLockFutexAPI
|
||||
js::UniqueLock<js::Mutex>& unique() { return *unique_; }
|
||||
};
|
||||
|
||||
static void
|
||||
AddWaiter(SharedArrayRawBuffer* sarb, FutexWaiter* waiter)
|
||||
{
|
||||
if (FutexWaiter* waiters = sarb->waiters()) {
|
||||
waiter->lower_pri = waiters;
|
||||
waiter->back = waiters->back;
|
||||
waiters->back->lower_pri = waiter;
|
||||
waiters->back = waiter;
|
||||
} else {
|
||||
waiter->lower_pri = waiter->back = waiter;
|
||||
sarb->setWaiters(waiter);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
RemoveWaiter(SharedArrayRawBuffer* sarb, FutexWaiter* waiter)
|
||||
{
|
||||
if (waiter->lower_pri == waiter) {
|
||||
sarb->setWaiters(nullptr);
|
||||
} else {
|
||||
waiter->lower_pri->back = waiter->back;
|
||||
waiter->back->lower_pri = waiter->lower_pri;
|
||||
if (sarb->waiters() == waiter)
|
||||
sarb->setWaiters(waiter->lower_pri);
|
||||
}
|
||||
waiter->lower_pri = nullptr;
|
||||
waiter->back = nullptr;
|
||||
}
|
||||
|
||||
class AtomicsWaitAsyncTask : public PromiseTask
|
||||
{
|
||||
enum class State {
|
||||
Waiting,
|
||||
Notified,
|
||||
TimedOut
|
||||
};
|
||||
|
||||
SharedArrayRawBuffer* sarb_;
|
||||
FutexWaiter waiter_;
|
||||
mozilla::Maybe<mozilla::TimeDuration> timeout_;
|
||||
ConditionVariable cond_;
|
||||
State state_;
|
||||
bool isInWaiterList_;
|
||||
|
||||
public:
|
||||
AtomicsWaitAsyncTask(JSContext* cx, Handle<PromiseObject*> promise,
|
||||
SharedArrayRawBuffer* sarb, uint32_t offset,
|
||||
mozilla::Maybe<mozilla::TimeDuration>& timeout)
|
||||
: PromiseTask(cx, promise),
|
||||
sarb_(sarb),
|
||||
waiter_(offset, this),
|
||||
timeout_(timeout),
|
||||
state_(State::Waiting),
|
||||
isInWaiterList_(false)
|
||||
{
|
||||
}
|
||||
|
||||
~AtomicsWaitAsyncTask() {
|
||||
if (isInWaiterList_) {
|
||||
AutoLockFutexAPI lock;
|
||||
if (isInWaiterList_)
|
||||
removeFromWaiterList();
|
||||
}
|
||||
sarb_->dropReference();
|
||||
}
|
||||
|
||||
FutexWaiter* waiter() {
|
||||
return &waiter_;
|
||||
}
|
||||
|
||||
void setInWaiterList() {
|
||||
isInWaiterList_ = true;
|
||||
}
|
||||
|
||||
bool isWaiting() const {
|
||||
return state_ == State::Waiting;
|
||||
}
|
||||
|
||||
void notify() {
|
||||
MOZ_ASSERT(isWaiting());
|
||||
state_ = State::Notified;
|
||||
cond_.notify_all();
|
||||
}
|
||||
|
||||
void execute() override {
|
||||
AutoLockFutexAPI lock;
|
||||
|
||||
const bool isTimed = timeout_.isSome();
|
||||
auto finalEnd = timeout_.map([](mozilla::TimeDuration& timeout) {
|
||||
return mozilla::TimeStamp::Now() + timeout;
|
||||
});
|
||||
auto maxSlice = mozilla::TimeDuration::FromSeconds(4000.0);
|
||||
|
||||
while (state_ == State::Waiting) {
|
||||
if (isTimed) {
|
||||
auto sliceEnd = finalEnd.map([&](mozilla::TimeStamp& finalEnd) {
|
||||
auto sliceEnd = mozilla::TimeStamp::Now() + maxSlice;
|
||||
if (finalEnd < sliceEnd)
|
||||
sliceEnd = finalEnd;
|
||||
return sliceEnd;
|
||||
});
|
||||
|
||||
mozilla::Unused << cond_.wait_until(lock.unique(), *sliceEnd);
|
||||
if (state_ == State::Waiting && mozilla::TimeStamp::Now() >= *finalEnd) {
|
||||
state_ = State::TimedOut;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
cond_.wait(lock.unique());
|
||||
}
|
||||
}
|
||||
|
||||
if (isInWaiterList_)
|
||||
removeFromWaiterList();
|
||||
}
|
||||
|
||||
private:
|
||||
void removeFromWaiterList() {
|
||||
MOZ_ASSERT(isInWaiterList_);
|
||||
RemoveWaiter(sarb_, &waiter_);
|
||||
isInWaiterList_ = false;
|
||||
}
|
||||
|
||||
bool finishPromise(JSContext* cx, Handle<PromiseObject*> promise) override {
|
||||
RootedValue result(cx);
|
||||
if (state_ == State::TimedOut)
|
||||
result.setString(cx->names().futexTimedOut);
|
||||
else
|
||||
result.setString(cx->names().futexOK);
|
||||
return PromiseObject::resolve(cx, promise, result);
|
||||
}
|
||||
};
|
||||
|
||||
bool
|
||||
FutexWaiter::isWaiting() const
|
||||
{
|
||||
if (kind == Sync)
|
||||
return rt->fx.isWaiting();
|
||||
return asyncTask->isWaiting();
|
||||
}
|
||||
|
||||
void
|
||||
FutexWaiter::notify()
|
||||
{
|
||||
if (kind == Sync) {
|
||||
rt->fx.notify(FutexRuntime::NotifyExplicit);
|
||||
return;
|
||||
}
|
||||
asyncTask->notify();
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
static bool
|
||||
GetWaitTimeout(JSContext* cx, HandleValue timeoutv,
|
||||
mozilla::Maybe<mozilla::TimeDuration>* timeout)
|
||||
{
|
||||
timeout->reset();
|
||||
if (!timeoutv.isUndefined()) {
|
||||
double timeout_ms;
|
||||
if (!ToNumber(cx, timeoutv, &timeout_ms))
|
||||
return false;
|
||||
if (!mozilla::IsNaN(timeout_ms)) {
|
||||
if (timeout_ms < 0)
|
||||
*timeout = mozilla::Some(mozilla::TimeDuration::FromSeconds(0.0));
|
||||
else if (!mozilla::IsInfinite(timeout_ms))
|
||||
*timeout = mozilla::Some(mozilla::TimeDuration::FromMilliseconds(timeout_ms));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
CreateWaitAsyncResult(JSContext* cx, bool isAsync, HandleValue value, MutableHandleValue rval)
|
||||
{
|
||||
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
RootedValue asyncValue(cx, BooleanValue(isAsync));
|
||||
if (!NativeDefineDataProperty(cx, obj, cx->names().async, asyncValue, JSPROP_ENUMERATE))
|
||||
return false;
|
||||
if (!NativeDefineDataProperty(cx, obj, cx->names().value, value, JSPROP_ENUMERATE))
|
||||
return false;
|
||||
|
||||
rval.setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -759,26 +1106,24 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
|
||||
Rooted<TypedArrayObject*> view(cx, nullptr);
|
||||
if (!GetSharedTypedArray(cx, objv, &view))
|
||||
return false;
|
||||
if (view->type() != Scalar::Int32)
|
||||
if (!IsWaitableTypedArray(view->type()))
|
||||
return ReportBadArrayType(cx);
|
||||
uint32_t offset;
|
||||
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
|
||||
return false;
|
||||
int32_t value;
|
||||
if (!ToInt32(cx, valv, &value))
|
||||
return false;
|
||||
mozilla::Maybe<mozilla::TimeDuration> timeout;
|
||||
if (!timeoutv.isUndefined()) {
|
||||
double timeout_ms;
|
||||
if (!ToNumber(cx, timeoutv, &timeout_ms))
|
||||
uint64_t value = 0;
|
||||
if (view->type() == Scalar::BigInt64) {
|
||||
if (!BigIntToRawBits(cx, view->type(), valv, &value))
|
||||
return false;
|
||||
if (!mozilla::IsNaN(timeout_ms)) {
|
||||
if (timeout_ms < 0)
|
||||
timeout = mozilla::Some(mozilla::TimeDuration::FromSeconds(0.0));
|
||||
else if (!mozilla::IsInfinite(timeout_ms))
|
||||
timeout = mozilla::Some(mozilla::TimeDuration::FromMilliseconds(timeout_ms));
|
||||
}
|
||||
} else {
|
||||
int32_t int32Value;
|
||||
if (!ToInt32(cx, valv, &int32Value))
|
||||
return false;
|
||||
value = uint32_t(int32Value);
|
||||
}
|
||||
mozilla::Maybe<mozilla::TimeDuration> timeout;
|
||||
if (!GetWaitTimeout(cx, timeoutv, &timeout))
|
||||
return false;
|
||||
|
||||
if (!rt->fx.canWait())
|
||||
return ReportCannotWait(cx);
|
||||
@@ -787,8 +1132,7 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
|
||||
// and it provides the necessary memory fence.
|
||||
AutoLockFutexAPI lock;
|
||||
|
||||
SharedMem<int32_t*> addr = view->viewDataShared().cast<int32_t*>() + offset;
|
||||
if (jit::AtomicOperations::loadSafeWhenRacy(addr) != value) {
|
||||
if (!WaitValueMatches(view, offset, value)) {
|
||||
r.setString(cx->names().futexNotEqual);
|
||||
return true;
|
||||
}
|
||||
@@ -796,16 +1140,8 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
|
||||
Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
|
||||
SharedArrayRawBuffer* sarb = sab->rawBufferObject();
|
||||
|
||||
FutexWaiter w(offset, rt);
|
||||
if (FutexWaiter* waiters = sarb->waiters()) {
|
||||
w.lower_pri = waiters;
|
||||
w.back = waiters->back;
|
||||
waiters->back->lower_pri = &w;
|
||||
waiters->back = &w;
|
||||
} else {
|
||||
w.lower_pri = w.back = &w;
|
||||
sarb->setWaiters(&w);
|
||||
}
|
||||
FutexWaiter w(GetWaiterByteOffset(view, offset), rt);
|
||||
AddWaiter(sarb, &w);
|
||||
|
||||
FutexRuntime::WaitResult result = FutexRuntime::FutexOK;
|
||||
bool retval = rt->fx.wait(cx, lock.unique(), timeout, &result);
|
||||
@@ -820,17 +1156,98 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
|
||||
}
|
||||
}
|
||||
|
||||
if (w.lower_pri == &w) {
|
||||
sarb->setWaiters(nullptr);
|
||||
} else {
|
||||
w.lower_pri->back = w.back;
|
||||
w.back->lower_pri = w.lower_pri;
|
||||
if (sarb->waiters() == &w)
|
||||
sarb->setWaiters(w.lower_pri);
|
||||
}
|
||||
RemoveWaiter(sarb, &w);
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool
|
||||
js::atomics_waitAsync(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
HandleValue objv = args.get(0);
|
||||
HandleValue idxv = args.get(1);
|
||||
HandleValue valv = args.get(2);
|
||||
HandleValue timeoutv = args.get(3);
|
||||
MutableHandleValue r = args.rval();
|
||||
|
||||
Rooted<TypedArrayObject*> view(cx, nullptr);
|
||||
if (!GetSharedTypedArray(cx, objv, &view))
|
||||
return false;
|
||||
if (!IsWaitableTypedArray(view->type()))
|
||||
return ReportBadArrayType(cx);
|
||||
uint32_t offset;
|
||||
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
|
||||
return false;
|
||||
uint64_t value = 0;
|
||||
if (view->type() == Scalar::BigInt64) {
|
||||
if (!BigIntToRawBits(cx, view->type(), valv, &value))
|
||||
return false;
|
||||
} else {
|
||||
int32_t int32Value;
|
||||
if (!ToInt32(cx, valv, &int32Value))
|
||||
return false;
|
||||
value = uint32_t(int32Value);
|
||||
}
|
||||
mozilla::Maybe<mozilla::TimeDuration> timeout;
|
||||
if (!GetWaitTimeout(cx, timeoutv, &timeout))
|
||||
return false;
|
||||
|
||||
Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
|
||||
if (!promise)
|
||||
return false;
|
||||
|
||||
RootedValue promiseValue(cx, ObjectValue(*promise));
|
||||
RootedValue result(cx);
|
||||
if (!CreateWaitAsyncResult(cx, true, promiseValue, &result))
|
||||
return false;
|
||||
|
||||
Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
|
||||
SharedArrayRawBuffer* sarb = sab->rawBufferObject();
|
||||
if (!sarb->addReference()) {
|
||||
JS_ReportErrorASCII(cx, "Reference count overflow on SharedArrayBuffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto task = cx->make_unique<AtomicsWaitAsyncTask>(cx, promise, sarb, offset, timeout);
|
||||
if (!task) {
|
||||
sarb->dropReference();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isAsync = false;
|
||||
RootedValue immediateResult(cx);
|
||||
{
|
||||
AutoLockFutexAPI lock;
|
||||
|
||||
if (!WaitValueMatches(view, offset, value)) {
|
||||
immediateResult.setString(cx->names().futexNotEqual);
|
||||
} else if (timeout.isSome() && timeout->ToMilliseconds() == 0.0) {
|
||||
immediateResult.setString(cx->names().futexTimedOut);
|
||||
} else {
|
||||
if (!cx->startAsyncTaskCallback || !cx->finishAsyncTaskCallback ||
|
||||
!CanUseExtraThreads())
|
||||
{
|
||||
JS_ReportErrorASCII(cx, "Atomics.waitAsync not supported in this runtime.");
|
||||
return false;
|
||||
}
|
||||
|
||||
task->waiter()->offset = GetWaiterByteOffset(view, offset);
|
||||
AddWaiter(sarb, task->waiter());
|
||||
task->setInWaiterList();
|
||||
isAsync = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAsync)
|
||||
return CreateWaitAsyncResult(cx, false, immediateResult, r);
|
||||
|
||||
if (!StartPromiseTask(cx, Move(task)))
|
||||
return false;
|
||||
|
||||
r.set(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
js::atomics_notify(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -843,11 +1260,12 @@ js::atomics_notify(JSContext* cx, unsigned argc, Value* vp)
|
||||
Rooted<TypedArrayObject*> view(cx, nullptr);
|
||||
if (!GetSharedTypedArray(cx, objv, &view))
|
||||
return false;
|
||||
if (view->type() != Scalar::Int32)
|
||||
if (!IsWaitableTypedArray(view->type()))
|
||||
return ReportBadArrayType(cx);
|
||||
uint32_t offset;
|
||||
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
|
||||
return false;
|
||||
offset = GetWaiterByteOffset(view, offset);
|
||||
double count;
|
||||
if (countv.isUndefined()) {
|
||||
count = mozilla::PositiveInfinity<double>();
|
||||
@@ -870,9 +1288,9 @@ js::atomics_notify(JSContext* cx, unsigned argc, Value* vp)
|
||||
do {
|
||||
FutexWaiter* c = iter;
|
||||
iter = iter->lower_pri;
|
||||
if (c->offset != offset || !c->rt->fx.isWaiting())
|
||||
if (c->offset != offset || !c->isWaiting())
|
||||
continue;
|
||||
c->rt->fx.notify(FutexRuntime::NotifyExplicit);
|
||||
c->notify();
|
||||
++woken;
|
||||
--count;
|
||||
} while (count > 0 && iter != waiters);
|
||||
@@ -1106,6 +1524,7 @@ const JSFunctionSpec AtomicsMethods[] = {
|
||||
JS_INLINABLE_FN("xor", atomics_xor, 3,0, AtomicsXor),
|
||||
JS_INLINABLE_FN("isLockFree", atomics_isLockFree, 1,0, AtomicsIsLockFree),
|
||||
JS_FN("wait", atomics_wait, 4,0),
|
||||
JS_FN("waitAsync", atomics_waitAsync, 4,0),
|
||||
JS_FN("notify", atomics_notify, 3,0),
|
||||
JS_FN("wake", atomics_notify, 3,0), //Legacy name
|
||||
JS_FS_END
|
||||
|
||||
@@ -35,6 +35,7 @@ MOZ_MUST_USE bool atomics_or(JSContext* cx, unsigned argc, Value* vp);
|
||||
MOZ_MUST_USE bool atomics_xor(JSContext* cx, unsigned argc, Value* vp);
|
||||
MOZ_MUST_USE bool atomics_isLockFree(JSContext* cx, unsigned argc, Value* vp);
|
||||
MOZ_MUST_USE bool atomics_wait(JSContext* cx, unsigned argc, Value* vp);
|
||||
MOZ_MUST_USE bool atomics_waitAsync(JSContext* cx, unsigned argc, Value* vp);
|
||||
MOZ_MUST_USE bool atomics_notify(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
/* asm.js callouts */
|
||||
|
||||
@@ -0,0 +1,608 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* 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/. */
|
||||
|
||||
#include "builtin/FinalizationRegistryObject.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
#include "jscntxt.h"
|
||||
|
||||
#include "builtin/WeakRefObject.h"
|
||||
#include "gc/Nursery.h"
|
||||
#include "gc/Tracer.h"
|
||||
#include "vm/EqualityOperations.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/Interpreter.h"
|
||||
#include "vm/Symbol.h"
|
||||
|
||||
#include "jswrapper.h"
|
||||
#include "jsobjinlines.h"
|
||||
|
||||
#include "vm/Interpreter-inl.h"
|
||||
#include "vm/NativeObject-inl.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
enum class WeakCellKind {
|
||||
Empty,
|
||||
Object,
|
||||
Symbol
|
||||
};
|
||||
|
||||
static JSObject*
|
||||
NormalizeWeakObject(JSObject* obj)
|
||||
{
|
||||
if (JSObject* unwrapped = CheckedUnwrap(obj, /* stopAtWindowProxy = */ false))
|
||||
return unwrapped;
|
||||
return obj;
|
||||
}
|
||||
|
||||
static bool
|
||||
GetPrototypeFromFinalizationRegistryConstructor(JSContext* cx, HandleObject newTarget,
|
||||
MutableHandleObject proto)
|
||||
{
|
||||
if (!GetPrototypeFromConstructor(cx, newTarget, proto))
|
||||
return false;
|
||||
if (proto)
|
||||
return true;
|
||||
|
||||
RootedObject realmObject(cx, CheckedUnwrap(newTarget, /* stopAtWindowProxy = */ false));
|
||||
if (!realmObject)
|
||||
return false;
|
||||
|
||||
{
|
||||
JSAutoCompartment ac(cx, realmObject);
|
||||
Rooted<GlobalObject*> global(cx, &realmObject->global());
|
||||
if (!GlobalObject::ensureConstructor(cx, global, JSProto_FinalizationRegistry))
|
||||
return false;
|
||||
proto.set(&global->getPrototype(JSProto_FinalizationRegistry).toObject());
|
||||
}
|
||||
|
||||
return cx->compartment()->wrap(cx, proto);
|
||||
}
|
||||
|
||||
struct WeakCell {
|
||||
WeakCellKind kind;
|
||||
WeakRef<JSObject*> object;
|
||||
WeakRef<JS::Symbol*> symbol;
|
||||
|
||||
WeakCell() : kind(WeakCellKind::Empty), object(nullptr), symbol(nullptr) {}
|
||||
explicit WeakCell(JSObject* obj) : kind(WeakCellKind::Object), object(obj), symbol(nullptr) {}
|
||||
explicit WeakCell(JS::Symbol* sym) : kind(WeakCellKind::Symbol), object(nullptr), symbol(sym) {}
|
||||
|
||||
void clear() {
|
||||
kind = WeakCellKind::Empty;
|
||||
object = nullptr;
|
||||
symbol = nullptr;
|
||||
}
|
||||
|
||||
bool isEmpty() const { return kind == WeakCellKind::Empty; }
|
||||
|
||||
bool isDead() const {
|
||||
if (kind == WeakCellKind::Object)
|
||||
return !object.unbarrieredGet();
|
||||
if (kind == WeakCellKind::Symbol)
|
||||
return !symbol.unbarrieredGet();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sameValue(HandleValue value) const {
|
||||
if (kind == WeakCellKind::Object)
|
||||
return value.isObject() && object.get() == NormalizeWeakObject(&value.toObject());
|
||||
if (kind == WeakCellKind::Symbol)
|
||||
return value.isSymbol() && symbol.get() == value.toSymbol();
|
||||
return false;
|
||||
}
|
||||
|
||||
void trace(JSTracer* trc, const char* name) {
|
||||
if (kind == WeakCellKind::Object) {
|
||||
JSObject* target = object.unbarrieredGet();
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
if (IsInsideNursery(target)) {
|
||||
TraceManuallyBarrieredEdge(trc, object.unsafeGet(), name);
|
||||
} else {
|
||||
if (trc->isMarkingTracer() && !target->asTenured().zone()->isCollecting())
|
||||
return;
|
||||
TraceWeakEdge(trc, &object, name);
|
||||
}
|
||||
} else if (kind == WeakCellKind::Symbol) {
|
||||
JS::Symbol* target = symbol.unbarrieredGet();
|
||||
if (target) {
|
||||
if (trc->isMarkingTracer() && !target->asTenured().zone()->isCollecting())
|
||||
return;
|
||||
TraceWeakEdge(trc, &symbol, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void traceIfZoneIsCollecting(JSTracer* trc, const char* name) {
|
||||
if (kind == WeakCellKind::Object) {
|
||||
JSObject* target = object.unbarrieredGet();
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
if (IsInsideNursery(target)) {
|
||||
TraceManuallyBarrieredEdge(trc, object.unsafeGet(), name);
|
||||
} else if (target->asTenured().zone()->isCollecting()) {
|
||||
TraceWeakEdge(trc, &object, name);
|
||||
}
|
||||
} else if (kind == WeakCellKind::Symbol) {
|
||||
JS::Symbol* target = symbol.unbarrieredGet();
|
||||
if (target && target->asTenured().zone()->isCollecting())
|
||||
TraceWeakEdge(trc, &symbol, name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct FinalizationRecord {
|
||||
WeakCell target;
|
||||
JS::Heap<Value> heldValue;
|
||||
WeakCell unregisterToken;
|
||||
bool active;
|
||||
bool queued;
|
||||
|
||||
FinalizationRecord(HandleValue targetValue, HandleValue heldValue,
|
||||
HandleValue unregisterTokenValue)
|
||||
: heldValue(heldValue),
|
||||
active(true),
|
||||
queued(false)
|
||||
{
|
||||
if (targetValue.isObject())
|
||||
target = WeakCell(NormalizeWeakObject(&targetValue.toObject()));
|
||||
else
|
||||
target = WeakCell(targetValue.toSymbol());
|
||||
|
||||
if (unregisterTokenValue.isObject())
|
||||
unregisterToken = WeakCell(NormalizeWeakObject(&unregisterTokenValue.toObject()));
|
||||
else if (unregisterTokenValue.isSymbol())
|
||||
unregisterToken = WeakCell(unregisterTokenValue.toSymbol());
|
||||
}
|
||||
|
||||
void trace(JSTracer* trc) {
|
||||
if (active || queued)
|
||||
JS::TraceEdge(trc, &heldValue, "FinalizationRegistry held value");
|
||||
|
||||
if (active) {
|
||||
target.trace(trc, "FinalizationRegistry target");
|
||||
if (!unregisterToken.isEmpty())
|
||||
unregisterToken.trace(trc, "FinalizationRegistry unregister token");
|
||||
}
|
||||
}
|
||||
|
||||
void traceWeakEdgesForCollectedZones(JSTracer* trc) {
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
target.traceIfZoneIsCollecting(trc, "FinalizationRegistry target");
|
||||
if (!unregisterToken.isEmpty())
|
||||
unregisterToken.traceIfZoneIsCollecting(trc,
|
||||
"FinalizationRegistry unregister token");
|
||||
}
|
||||
|
||||
bool targetIsDead() const {
|
||||
return active && target.isDead();
|
||||
}
|
||||
|
||||
bool matchesToken(HandleValue token) const {
|
||||
return active && !queued && !unregisterToken.isEmpty() &&
|
||||
unregisterToken.sameValue(token);
|
||||
}
|
||||
|
||||
void queueForCleanup() {
|
||||
MOZ_ASSERT(active);
|
||||
MOZ_ASSERT(!queued);
|
||||
active = false;
|
||||
queued = true;
|
||||
target.clear();
|
||||
unregisterToken.clear();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
active = false;
|
||||
queued = false;
|
||||
target.clear();
|
||||
unregisterToken.clear();
|
||||
heldValue.setUndefined();
|
||||
}
|
||||
};
|
||||
|
||||
using FinalizationRecordVector = Vector<FinalizationRecord*, 0, SystemAllocPolicy>;
|
||||
|
||||
struct FinalizationRegistryObject::Data {
|
||||
JS::Heap<JSObject*> cleanupCallback;
|
||||
JS::Heap<JSObject*> cleanupJob;
|
||||
FinalizationRecordVector records;
|
||||
FinalizationRecordVector cleanupQueue;
|
||||
bool queuedForCleanup;
|
||||
|
||||
explicit Data(JSObject* cleanupCallback)
|
||||
: cleanupCallback(cleanupCallback),
|
||||
cleanupJob(nullptr),
|
||||
records(SystemAllocPolicy()),
|
||||
cleanupQueue(SystemAllocPolicy()),
|
||||
queuedForCleanup(false)
|
||||
{}
|
||||
|
||||
~Data() {
|
||||
for (size_t i = 0; i < records.length(); i++)
|
||||
js_delete(records[i]);
|
||||
}
|
||||
|
||||
void trace(JSTracer* trc) {
|
||||
JS::TraceEdge(trc, &cleanupCallback, "FinalizationRegistry cleanup callback");
|
||||
if (cleanupJob.unbarrieredGet())
|
||||
JS::TraceEdge(trc, &cleanupJob, "FinalizationRegistry cleanup job");
|
||||
for (size_t i = 0; i < records.length(); i++)
|
||||
records[i]->trace(trc);
|
||||
}
|
||||
|
||||
bool appendRecord(FinalizationRecord* record) {
|
||||
return records.append(record);
|
||||
}
|
||||
|
||||
bool appendCleanupRecord(FinalizationRecord* record) {
|
||||
return cleanupQueue.append(record);
|
||||
}
|
||||
|
||||
void compactRecords() {
|
||||
for (size_t i = 0; i < records.length();) {
|
||||
FinalizationRecord* record = records[i];
|
||||
if (!record->active && !record->queued) {
|
||||
js_delete(record);
|
||||
records.erase(records.begin() + i);
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void unregister(HandleValue token, bool* removed) {
|
||||
*removed = false;
|
||||
for (size_t i = 0; i < records.length(); i++) {
|
||||
FinalizationRecord* record = records[i];
|
||||
if (record->matchesToken(token)) {
|
||||
record->clear();
|
||||
*removed = true;
|
||||
}
|
||||
}
|
||||
compactRecords();
|
||||
}
|
||||
};
|
||||
|
||||
static FinalizationRegistryObject::Data*
|
||||
GetData(JSObject* obj)
|
||||
{
|
||||
return obj->as<FinalizationRegistryObject>().getData();
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE bool
|
||||
IsFinalizationRegistry(HandleValue v)
|
||||
{
|
||||
return v.isObject() && v.toObject().is<FinalizationRegistryObject>();
|
||||
}
|
||||
|
||||
static bool
|
||||
FinalizationRegistry_register_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsFinalizationRegistry(args.thisv()));
|
||||
|
||||
if (!CanBeHeldWeakly(args.get(0))) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BAD_FINALIZATION_REGISTRY_TARGET);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedValue target(cx, args[0]);
|
||||
RootedValue heldValue(cx, args.get(1));
|
||||
|
||||
bool same;
|
||||
if (!SameValue(cx, target, heldValue, &same))
|
||||
return false;
|
||||
if (same) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BAD_FINALIZATION_REGISTRY_HELD_VALUE);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedValue unregisterToken(cx, args.get(2));
|
||||
if (!CanBeHeldWeakly(unregisterToken)) {
|
||||
if (!unregisterToken.isUndefined()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN);
|
||||
return false;
|
||||
}
|
||||
unregisterToken.setUndefined();
|
||||
}
|
||||
|
||||
FinalizationRecord* record = cx->new_<FinalizationRecord>(target, heldValue, unregisterToken);
|
||||
if (!record)
|
||||
return false;
|
||||
|
||||
auto* data = GetData(&args.thisv().toObject());
|
||||
if (!data->appendRecord(record)) {
|
||||
js_delete(record);
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
FinalizationRegistry_unregister_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsFinalizationRegistry(args.thisv()));
|
||||
|
||||
RootedValue unregisterToken(cx, args.get(0));
|
||||
if (!CanBeHeldWeakly(unregisterToken)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool removed = false;
|
||||
auto* data = GetData(&args.thisv().toObject());
|
||||
data->unregister(unregisterToken, &removed);
|
||||
|
||||
args.rval().setBoolean(removed);
|
||||
return true;
|
||||
}
|
||||
|
||||
const JSPropertySpec FinalizationRegistryObject::properties[] = {
|
||||
JS_PS_END
|
||||
};
|
||||
|
||||
const JSFunctionSpec FinalizationRegistryObject::methods[] = {
|
||||
JS_FN("register", FinalizationRegistryObject::register_, 2, 0),
|
||||
JS_FN("unregister", FinalizationRegistryObject::unregister, 1, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
static JSObject*
|
||||
InitFinalizationRegistryClass(JSContext* cx, HandleObject obj, bool defineMembers)
|
||||
{
|
||||
Handle<GlobalObject*> global = obj.as<GlobalObject>();
|
||||
RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
|
||||
if (!proto)
|
||||
return nullptr;
|
||||
|
||||
RootedFunction ctor(cx, GlobalObject::createConstructor(cx, FinalizationRegistryObject::construct,
|
||||
ClassName(JSProto_FinalizationRegistry, cx), 1));
|
||||
if (!ctor)
|
||||
return nullptr;
|
||||
|
||||
if (!LinkConstructorAndPrototype(cx, ctor, proto))
|
||||
return nullptr;
|
||||
|
||||
if (defineMembers) {
|
||||
if (!DefinePropertiesAndFunctions(cx, proto, FinalizationRegistryObject::properties,
|
||||
FinalizationRegistryObject::methods)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!DefineToStringTag(cx, proto, cx->names().FinalizationRegistry))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_FinalizationRegistry, ctor, proto))
|
||||
return nullptr;
|
||||
return proto;
|
||||
}
|
||||
|
||||
/* static */ FinalizationRegistryObject*
|
||||
FinalizationRegistryObject::create(JSContext* cx, HandleObject cleanupCallback,
|
||||
HandleObject proto /* = nullptr */)
|
||||
{
|
||||
Rooted<FinalizationRegistryObject*> obj(cx,
|
||||
NewObjectWithClassProto<FinalizationRegistryObject>(cx, proto));
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
Data* data = cx->new_<Data>(cleanupCallback);
|
||||
if (!data)
|
||||
return nullptr;
|
||||
|
||||
obj->setPrivate(data);
|
||||
|
||||
RootedAtom funName(cx, cx->names().empty);
|
||||
RootedFunction cleanupJob(cx, NewNativeFunction(cx, FinalizationRegistryObject::cleanupJob, 0,
|
||||
funName, gc::AllocKind::FUNCTION_EXTENDED,
|
||||
GenericObject));
|
||||
if (!cleanupJob)
|
||||
return nullptr;
|
||||
|
||||
cleanupJob->setExtendedSlot(0, ObjectValue(*obj));
|
||||
data->cleanupJob = cleanupJob;
|
||||
|
||||
if (!cx->zone()->finalizationRegistries.append(WeakRef<JSObject*>(obj))) {
|
||||
ReportOutOfMemory(cx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
FinalizationRegistryObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
if (!ThrowIfNotConstructing(cx, args, "FinalizationRegistry"))
|
||||
return false;
|
||||
|
||||
RootedValue cleanupCallbackValue(cx, args.get(0));
|
||||
RootedObject cleanupCallback(cx, ValueToCallable(cx, cleanupCallbackValue, 0, NO_CONSTRUCT));
|
||||
if (!cleanupCallback)
|
||||
return false;
|
||||
|
||||
RootedObject proto(cx);
|
||||
RootedObject newTarget(cx, &args.newTarget().toObject());
|
||||
if (!GetPrototypeFromFinalizationRegistryConstructor(cx, newTarget, &proto))
|
||||
return false;
|
||||
|
||||
Rooted<FinalizationRegistryObject*> obj(cx,
|
||||
FinalizationRegistryObject::create(cx, cleanupCallback, proto));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
args.rval().setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
FinalizationRegistryObject::register_(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsFinalizationRegistry, FinalizationRegistry_register_impl>(cx, args);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
FinalizationRegistryObject::unregister(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsFinalizationRegistry, FinalizationRegistry_unregister_impl>(cx, args);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
FinalizationRegistryObject::cleanupJob(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
RootedFunction job(cx, &args.callee().as<JSFunction>());
|
||||
Rooted<FinalizationRegistryObject*> registry(cx,
|
||||
&job->getExtendedSlot(0).toObject().as<FinalizationRegistryObject>());
|
||||
Data* data = registry->getData();
|
||||
if (!data) {
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
data->queuedForCleanup = false;
|
||||
|
||||
RootedValue callback(cx, ObjectValue(*data->cleanupCallback.get()));
|
||||
RootedValue heldValue(cx);
|
||||
RootedValue ignored(cx);
|
||||
RootedValue undefined(cx, UndefinedValue());
|
||||
|
||||
while (data->cleanupQueue.length() > 0) {
|
||||
FinalizationRecord* record = data->cleanupQueue[0];
|
||||
data->cleanupQueue.erase(data->cleanupQueue.begin());
|
||||
|
||||
if (!record->queued) {
|
||||
data->compactRecords();
|
||||
continue;
|
||||
}
|
||||
|
||||
heldValue.set(record->heldValue.get());
|
||||
record->clear();
|
||||
data->compactRecords();
|
||||
|
||||
if (!Call(cx, callback, undefined, heldValue, &ignored))
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
FinalizationRegistryObject::trace(JSTracer* trc, JSObject* obj)
|
||||
{
|
||||
if (Data* data = GetData(obj))
|
||||
data->trace(trc);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
FinalizationRegistryObject::finalize(FreeOp* fop, JSObject* obj)
|
||||
{
|
||||
if (Data* data = GetData(obj))
|
||||
fop->delete_(data);
|
||||
}
|
||||
|
||||
void
|
||||
FinalizationRegistryObject::traceWeakEdgesForCollectedZones(JSTracer* trc)
|
||||
{
|
||||
Data* data = getData();
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < data->records.length(); i++)
|
||||
data->records[i]->traceWeakEdgesForCollectedZones(trc);
|
||||
}
|
||||
|
||||
void
|
||||
FinalizationRegistryObject::sweepAfterGC(JSRuntime* rt)
|
||||
{
|
||||
Data* data = getData();
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
bool needsCleanupJob = false;
|
||||
for (size_t i = 0; i < data->records.length(); i++) {
|
||||
FinalizationRecord* record = data->records[i];
|
||||
if (!record->targetIsDead())
|
||||
continue;
|
||||
|
||||
record->queueForCleanup();
|
||||
if (!data->appendCleanupRecord(record)) {
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
oomUnsafe.crash("queueing FinalizationRegistry cleanup record");
|
||||
}
|
||||
needsCleanupJob = true;
|
||||
}
|
||||
|
||||
if (needsCleanupJob && !data->queuedForCleanup) {
|
||||
JSContext* cx = rt->contextFromMainThread();
|
||||
RootedObject cleanupJob(cx, data->cleanupJob.unbarrieredGet());
|
||||
if (!rt->enqueueFinalizationRegistryCleanupJob(cx, cleanupJob)) {
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
oomUnsafe.crash("queueing FinalizationRegistry cleanup job");
|
||||
}
|
||||
data->queuedForCleanup = true;
|
||||
}
|
||||
|
||||
data->compactRecords();
|
||||
}
|
||||
|
||||
static const ClassOps FinalizationRegistryObjectClassOps = {
|
||||
nullptr, /* addProperty */
|
||||
nullptr, /* delProperty */
|
||||
nullptr, /* getProperty */
|
||||
nullptr, /* setProperty */
|
||||
nullptr, /* enumerate */
|
||||
nullptr, /* resolve */
|
||||
nullptr, /* mayResolve */
|
||||
FinalizationRegistryObject::finalize,
|
||||
nullptr, /* call */
|
||||
nullptr, /* hasInstance */
|
||||
nullptr, /* construct */
|
||||
FinalizationRegistryObject::trace
|
||||
};
|
||||
|
||||
const Class FinalizationRegistryObject::class_ = {
|
||||
"FinalizationRegistry",
|
||||
JSCLASS_HAS_PRIVATE |
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_FinalizationRegistry) |
|
||||
JSCLASS_FOREGROUND_FINALIZE,
|
||||
&FinalizationRegistryObjectClassOps
|
||||
};
|
||||
|
||||
/* static */ JSObject*
|
||||
FinalizationRegistryObject::initClass(JSContext* cx, HandleObject obj)
|
||||
{
|
||||
return ::InitFinalizationRegistryClass(cx, obj, true);
|
||||
}
|
||||
|
||||
JSObject*
|
||||
js::InitFinalizationRegistryClass(JSContext* cx, HandleObject obj)
|
||||
{
|
||||
return FinalizationRegistryObject::initClass(cx, obj);
|
||||
}
|
||||
|
||||
JSObject*
|
||||
js::InitBareFinalizationRegistryCtor(JSContext* cx, HandleObject obj)
|
||||
{
|
||||
return ::InitFinalizationRegistryClass(cx, obj, false);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* 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 builtin_FinalizationRegistryObject_h
|
||||
#define builtin_FinalizationRegistryObject_h
|
||||
|
||||
#include "gc/Barrier.h"
|
||||
#include "vm/NativeObject.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
class FinalizationRegistryObject : public NativeObject
|
||||
{
|
||||
public:
|
||||
struct Data;
|
||||
|
||||
static const Class class_;
|
||||
|
||||
static JSObject* initClass(JSContext* cx, HandleObject obj);
|
||||
static FinalizationRegistryObject* create(JSContext* cx, HandleObject cleanupCallback,
|
||||
HandleObject proto = nullptr);
|
||||
|
||||
static void trace(JSTracer* trc, JSObject* obj);
|
||||
static void finalize(FreeOp* fop, JSObject* obj);
|
||||
void traceWeakEdgesForCollectedZones(JSTracer* trc);
|
||||
[[nodiscard]] static bool construct(JSContext* cx, unsigned argc, Value* vp);
|
||||
[[nodiscard]] static bool register_(JSContext* cx, unsigned argc, Value* vp);
|
||||
[[nodiscard]] static bool unregister(JSContext* cx, unsigned argc, Value* vp);
|
||||
[[nodiscard]] static bool cleanupJob(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
void sweepAfterGC(JSRuntime* rt);
|
||||
|
||||
Data* getData() const {
|
||||
return static_cast<Data*>(getPrivate());
|
||||
}
|
||||
|
||||
static const JSPropertySpec properties[];
|
||||
static const JSFunctionSpec methods[];
|
||||
};
|
||||
|
||||
extern JSObject*
|
||||
InitFinalizationRegistryClass(JSContext* cx, HandleObject obj);
|
||||
|
||||
extern JSObject*
|
||||
InitBareFinalizationRegistryCtor(JSContext* cx, HandleObject obj);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* builtin_FinalizationRegistryObject_h */
|
||||
@@ -55,6 +55,49 @@ function MapForEach(callbackfn, thisArg = undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
// ES2024
|
||||
// Map.groupBy ( items, callbackfn )
|
||||
function MapGroupBy(items, callbackfn) {
|
||||
// Step 1.
|
||||
RequireObjectCoercible(items);
|
||||
|
||||
// Step 2.
|
||||
if (!IsCallable(callbackfn))
|
||||
ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn));
|
||||
|
||||
// Step 3.
|
||||
var groups = std_Map_create();
|
||||
|
||||
// Step 4.
|
||||
var k = 0;
|
||||
|
||||
// Steps 5-8.
|
||||
for (var value of allowContentIter(items)) {
|
||||
// Step 6.a.
|
||||
if (k >= MAX_NUMERIC_INDEX)
|
||||
ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
|
||||
|
||||
// Step 6.b.
|
||||
var key = callContentFunction(callbackfn, undefined, value, k);
|
||||
|
||||
// Steps 6.c-d.
|
||||
var elements;
|
||||
if (callFunction(std_Map_has, groups, key)) {
|
||||
elements = callFunction(std_Map_get, groups, key);
|
||||
callFunction(std_Array_push, elements, value);
|
||||
} else {
|
||||
elements = [value];
|
||||
callFunction(std_Map_set, groups, key, elements);
|
||||
}
|
||||
|
||||
// Step 6.e.
|
||||
k++;
|
||||
}
|
||||
|
||||
// Step 9.
|
||||
return groups;
|
||||
}
|
||||
|
||||
var iteratorTemp = { mapIterationResultPair : null };
|
||||
|
||||
function MapIteratorNext() {
|
||||
|
||||
@@ -332,9 +332,15 @@ const JSPropertySpec MapObject::staticProperties[] = {
|
||||
JS_PS_END
|
||||
};
|
||||
|
||||
const JSFunctionSpec MapObject::staticMethods[] = {
|
||||
JS_SELF_HOSTED_FN("groupBy", "MapGroupBy", 2, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
static JSObject*
|
||||
InitClass(JSContext* cx, Handle<GlobalObject*> global, const Class* clasp, JSProtoKey key, Native construct,
|
||||
const JSPropertySpec* properties, const JSFunctionSpec* methods,
|
||||
const JSFunctionSpec* staticMethods,
|
||||
const JSPropertySpec* staticProperties)
|
||||
{
|
||||
RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
|
||||
@@ -343,8 +349,13 @@ InitClass(JSContext* cx, Handle<GlobalObject*> global, const Class* clasp, JSPro
|
||||
|
||||
Rooted<JSFunction*> ctor(cx, global->createConstructor(cx, construct, ClassName(key, cx), 0));
|
||||
if (!ctor ||
|
||||
!JS_DefineProperties(cx, ctor, staticProperties) ||
|
||||
!LinkConstructorAndPrototype(cx, ctor, proto) ||
|
||||
!JS_DefineProperties(cx, ctor, staticProperties))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if (staticMethods && !JS_DefineFunctions(cx, ctor, staticMethods))
|
||||
return nullptr;
|
||||
if (!LinkConstructorAndPrototype(cx, ctor, proto) ||
|
||||
!DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
|
||||
!GlobalObject::initBuiltinConstructor(cx, global, key, ctor, proto))
|
||||
{
|
||||
@@ -359,7 +370,7 @@ MapObject::initClass(JSContext* cx, JSObject* obj)
|
||||
Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
|
||||
RootedObject proto(cx,
|
||||
InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods,
|
||||
staticProperties));
|
||||
staticMethods, staticProperties));
|
||||
if (proto) {
|
||||
// Define the "entries" method.
|
||||
JSFunction* fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0);
|
||||
@@ -1084,7 +1095,7 @@ SetObject::initClass(JSContext* cx, JSObject* obj)
|
||||
Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
|
||||
RootedObject proto(cx,
|
||||
InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods,
|
||||
staticProperties));
|
||||
nullptr, staticProperties));
|
||||
if (proto) {
|
||||
// Define the "values" method.
|
||||
JSFunction* fun = JS_DefineFunction(cx, proto, "values", values, 0, 0);
|
||||
|
||||
@@ -110,7 +110,9 @@ class MapObject : public NativeObject {
|
||||
static MOZ_MUST_USE bool getKeysAndValuesInterleaved(JSContext* cx, HandleObject obj,
|
||||
JS::MutableHandle<GCVector<JS::Value>> entries);
|
||||
static MOZ_MUST_USE bool entries(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool get(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool has(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool set(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MapObject* create(JSContext* cx, HandleObject proto = nullptr);
|
||||
|
||||
// Publicly exposed Map calls for JSAPI access (webidl maplike/setlike
|
||||
@@ -137,6 +139,7 @@ class MapObject : public NativeObject {
|
||||
|
||||
static const JSPropertySpec properties[];
|
||||
static const JSFunctionSpec methods[];
|
||||
static const JSFunctionSpec staticMethods[];
|
||||
static const JSPropertySpec staticProperties[];
|
||||
ValueMap* getData() { return static_cast<ValueMap*>(getPrivate()); }
|
||||
static ValueMap& extract(HandleObject o);
|
||||
@@ -153,10 +156,8 @@ class MapObject : public NativeObject {
|
||||
static MOZ_MUST_USE bool size_impl(JSContext* cx, const CallArgs& args);
|
||||
static MOZ_MUST_USE bool size(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool get_impl(JSContext* cx, const CallArgs& args);
|
||||
static MOZ_MUST_USE bool get(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool has_impl(JSContext* cx, const CallArgs& args);
|
||||
static MOZ_MUST_USE bool set_impl(JSContext* cx, const CallArgs& args);
|
||||
static MOZ_MUST_USE bool set(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool delete_impl(JSContext* cx, const CallArgs& args);
|
||||
static MOZ_MUST_USE bool delete_(JSContext* cx, unsigned argc, Value* vp);
|
||||
static MOZ_MUST_USE bool keys_impl(JSContext* cx, const CallArgs& args);
|
||||
|
||||
@@ -240,12 +240,16 @@ function ObjectGroupBy(items, callbackfn) {
|
||||
// Steps 5-8.
|
||||
for (var value of allowContentIter(items)) {
|
||||
// Step 6.a.
|
||||
var key = callContentFunction(callbackfn, undefined, value, k);
|
||||
if (k >= MAX_NUMERIC_INDEX)
|
||||
ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
|
||||
|
||||
// Step 6.b.
|
||||
var key = callContentFunction(callbackfn, undefined, value, k);
|
||||
|
||||
// Step 6.c.
|
||||
key = ToPropertyKey(key);
|
||||
|
||||
// Steps 6.c-d.
|
||||
// Steps 6.d-e.
|
||||
var elements = groups[key];
|
||||
if (elements === undefined) {
|
||||
_DefineDataProperty(groups, key, [value]);
|
||||
@@ -253,7 +257,7 @@ function ObjectGroupBy(items, callbackfn) {
|
||||
callFunction(std_Array_push, elements, value);
|
||||
}
|
||||
|
||||
// Step 6.e.
|
||||
// Step 6.f.
|
||||
k++;
|
||||
}
|
||||
|
||||
|
||||
@@ -3722,6 +3722,48 @@ Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
// ES2024
|
||||
// Promise.withResolvers ( )
|
||||
static bool
|
||||
Promise_static_withResolvers(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1.
|
||||
if (!args.thisv().isObject()) {
|
||||
ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, -1, args.thisv(), nullptr);
|
||||
return false;
|
||||
}
|
||||
RootedObject C(cx, &args.thisv().toObject());
|
||||
|
||||
// Step 2.
|
||||
Rooted<PromiseCapability> capability(cx);
|
||||
if (!NewPromiseCapability(cx, C, &capability, false))
|
||||
return false;
|
||||
|
||||
// Step 3.
|
||||
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
// Steps 4-7.
|
||||
RootedValue promise(cx, ObjectValue(*capability.promise()));
|
||||
if (!::JS_DefineProperty(cx, obj, "promise", promise, JSPROP_ENUMERATE))
|
||||
return false;
|
||||
|
||||
RootedValue resolve(cx, ObjectValue(*capability.resolve()));
|
||||
if (!::JS_DefineProperty(cx, obj, "resolve", resolve, JSPROP_ENUMERATE))
|
||||
return false;
|
||||
|
||||
RootedValue reject(cx, ObjectValue(*capability.reject()));
|
||||
if (!::JS_DefineProperty(cx, obj, "reject", reject, JSPROP_ENUMERATE))
|
||||
return false;
|
||||
|
||||
// Step 8.
|
||||
args.rval().setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unforgeable version of ES2016, 25.4.4.5, Promise.resolve.
|
||||
*/
|
||||
@@ -5251,6 +5293,7 @@ static const JSFunctionSpec promise_static_methods[] = {
|
||||
JS_FN("race", Promise_static_race, 1, 0),
|
||||
JS_FN("reject", Promise_reject, 1, 0),
|
||||
JS_FN("resolve", Promise_static_resolve, 1, 0),
|
||||
JS_FN("withResolvers", Promise_static_withResolvers, 0, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
|
||||
@@ -654,6 +654,82 @@ function String_repeat(count) {
|
||||
return T;
|
||||
}
|
||||
|
||||
// ES2024
|
||||
// String.prototype.isWellFormed ( )
|
||||
function String_isWellFormed() {
|
||||
// Steps 1-2.
|
||||
RequireObjectCoercible(this);
|
||||
var S = ToString(this);
|
||||
|
||||
// Step 3.
|
||||
var length = S.length;
|
||||
for (var k = 0; k < length; k++) {
|
||||
var c = callFunction(std_String_charCodeAt, S, k);
|
||||
if (c >= 0xD800 && c <= 0xDBFF) {
|
||||
if (k + 1 >= length)
|
||||
return false;
|
||||
|
||||
var d = callFunction(std_String_charCodeAt, S, k + 1);
|
||||
if (d < 0xDC00 || d > 0xDFFF)
|
||||
return false;
|
||||
|
||||
k++;
|
||||
} else if (c >= 0xDC00 && c <= 0xDFFF) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4.
|
||||
return true;
|
||||
}
|
||||
|
||||
// ES2024
|
||||
// String.prototype.toWellFormed ( )
|
||||
function String_toWellFormed() {
|
||||
// Steps 1-2.
|
||||
RequireObjectCoercible(this);
|
||||
var S = ToString(this);
|
||||
|
||||
// Step 3.
|
||||
var length = S.length;
|
||||
var result = "";
|
||||
var copied = 0;
|
||||
|
||||
for (var k = 0; k < length; k++) {
|
||||
var c = callFunction(std_String_charCodeAt, S, k);
|
||||
var isUnpairedSurrogate = false;
|
||||
|
||||
if (c >= 0xD800 && c <= 0xDBFF) {
|
||||
if (k + 1 < length) {
|
||||
var d = callFunction(std_String_charCodeAt, S, k + 1);
|
||||
if (d >= 0xDC00 && d <= 0xDFFF) {
|
||||
k++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
isUnpairedSurrogate = true;
|
||||
} else if (c >= 0xDC00 && c <= 0xDFFF) {
|
||||
isUnpairedSurrogate = true;
|
||||
}
|
||||
|
||||
if (isUnpairedSurrogate) {
|
||||
if (copied < k)
|
||||
result += callFunction(String_substring, S, copied, k);
|
||||
result += "\uFFFD";
|
||||
copied = k + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (copied === 0)
|
||||
return S;
|
||||
|
||||
if (copied < length)
|
||||
result += callFunction(String_substring, S, copied, length);
|
||||
|
||||
// Step 4.
|
||||
return result;
|
||||
}
|
||||
|
||||
// ES6 draft specification, section 21.1.3.27, version 2013-09-27.
|
||||
function String_iterator() {
|
||||
RequireObjectCoercible(this);
|
||||
|
||||
@@ -41,10 +41,21 @@ function TypedArrayContentTypeIsBigIntMethod() {
|
||||
return IsBigInt64TypedArray(this) || IsBigUint64TypedArray(this);
|
||||
}
|
||||
|
||||
function ThrowIfTypedArrayOutOfBounds(tarray) {
|
||||
if (TypedArrayIsOutOfBounds(tarray))
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
}
|
||||
|
||||
function ThrowIfPossiblyWrappedTypedArrayOutOfBounds(tarray) {
|
||||
if (PossiblyWrappedTypedArrayIsOutOfBounds(tarray))
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
}
|
||||
|
||||
function GetAttachedArrayBuffer(tarray) {
|
||||
var buffer = ViewedArrayBufferIfReified(tarray);
|
||||
if (IsDetachedBuffer(buffer))
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
|
||||
ThrowIfTypedArrayOutOfBounds(tarray);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@@ -67,6 +78,7 @@ function IsTypedArrayEnsuringArrayBuffer(arg) {
|
||||
if (IsObject(arg) && IsPossiblyWrappedTypedArray(arg)) {
|
||||
if (PossiblyWrappedTypedArrayHasDetachedBuffer(arg))
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
|
||||
ThrowIfPossiblyWrappedTypedArrayOutOfBounds(arg);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -89,6 +101,7 @@ function ValidateTypedArray(obj, error) {
|
||||
if (IsPossiblyWrappedTypedArray(obj)) {
|
||||
if (PossiblyWrappedTypedArrayHasDetachedBuffer(obj))
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
|
||||
ThrowIfPossiblyWrappedTypedArrayOutOfBounds(obj);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1130,12 +1143,18 @@ function TypedArraySet(overloaded, offset = 0) {
|
||||
// Steps 9-10.
|
||||
var targetBuffer = GetAttachedArrayBuffer(target);
|
||||
|
||||
ThrowIfTypedArrayOutOfBounds(target);
|
||||
|
||||
// Step 11.
|
||||
var targetLength = TypedArrayLength(target);
|
||||
|
||||
// Steps 12 et seq.
|
||||
if (IsPossiblyWrappedTypedArray(overloaded))
|
||||
if (IsPossiblyWrappedTypedArray(overloaded)) {
|
||||
if (PossiblyWrappedTypedArrayHasDetachedBuffer(overloaded))
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
|
||||
ThrowIfPossiblyWrappedTypedArrayOutOfBounds(overloaded);
|
||||
return SetFromTypedArray(target, overloaded, targetOffset, targetLength);
|
||||
}
|
||||
|
||||
return SetFromNonTypedArray(target, overloaded, targetOffset, targetLength, targetBuffer);
|
||||
}
|
||||
@@ -1472,6 +1491,8 @@ function TypedArraySubarray(begin, end) {
|
||||
"TypedArraySubarray");
|
||||
}
|
||||
|
||||
GetAttachedArrayBuffer(obj);
|
||||
|
||||
// Steps 4-6.
|
||||
var buffer = TypedArrayBuffer(obj);
|
||||
var srcLength = TypedArrayLength(obj);
|
||||
@@ -1952,7 +1973,10 @@ function ArrayBufferSlice(start, end) {
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
|
||||
|
||||
// Steps 19-21.
|
||||
ArrayBufferCopyData(new_, 0, O, first | 0, newLen | 0, isWrapped);
|
||||
var currentLen = ArrayBufferByteLength(O);
|
||||
var copyLen = first >= currentLen ? 0 : std_Math_min(newLen, currentLen - first);
|
||||
if (copyLen > 0)
|
||||
ArrayBufferCopyData(new_, 0, O, first | 0, copyLen | 0, isWrapped);
|
||||
|
||||
// Step 22.
|
||||
return new_;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "jsapi.h"
|
||||
#include "jscntxt.h"
|
||||
|
||||
#include "builtin/WeakRefObject.h"
|
||||
#include "vm/SelfHosting.h"
|
||||
|
||||
#include "vm/Interpreter-inl.h"
|
||||
@@ -21,21 +22,80 @@ IsWeakMap(HandleValue v)
|
||||
return v.isObject() && v.toObject().is<WeakMapObject>();
|
||||
}
|
||||
|
||||
struct WeakMapObject::Data
|
||||
{
|
||||
ObjectValueMap objectMap;
|
||||
SymbolValueMap symbolMap;
|
||||
|
||||
Data(JSContext* cx, JSObject* owner)
|
||||
: objectMap(cx, owner),
|
||||
symbolMap(cx, owner)
|
||||
{}
|
||||
|
||||
bool init() {
|
||||
return objectMap.init() && symbolMap.init();
|
||||
}
|
||||
};
|
||||
|
||||
ObjectValueMap*
|
||||
WeakMapObject::getMap()
|
||||
{
|
||||
Data* data = getData();
|
||||
return data ? &data->objectMap : nullptr;
|
||||
}
|
||||
|
||||
SymbolValueMap*
|
||||
WeakMapObject::getSymbolMap()
|
||||
{
|
||||
Data* data = getData();
|
||||
return data ? &data->symbolMap : nullptr;
|
||||
}
|
||||
|
||||
static bool
|
||||
EnsureWeakMapData(JSContext* cx, Handle<WeakMapObject*> mapObj)
|
||||
{
|
||||
if (mapObj->getData())
|
||||
return true;
|
||||
|
||||
auto data = cx->make_unique<WeakMapObject::Data>(cx, mapObj.get());
|
||||
if (!data)
|
||||
return false;
|
||||
|
||||
if (!data->init()) {
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
mapObj->setPrivate(data.release());
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
WeakMap_has_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsWeakMap(args.thisv()));
|
||||
|
||||
if (!args.get(0).isObject()) {
|
||||
if (!CanBeHeldWeakly(args.get(0))) {
|
||||
args.rval().setBoolean(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
|
||||
JSObject* key = &args[0].toObject();
|
||||
if (map->has(key)) {
|
||||
args.rval().setBoolean(true);
|
||||
return true;
|
||||
WeakMapObject& weakMap = args.thisv().toObject().as<WeakMapObject>();
|
||||
if (args.get(0).isObject()) {
|
||||
if (ObjectValueMap* map = weakMap.getMap()) {
|
||||
JSObject* key = &args[0].toObject();
|
||||
if (map->has(key)) {
|
||||
args.rval().setBoolean(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (SymbolValueMap* map = weakMap.getSymbolMap()) {
|
||||
JS::Symbol* key = args[0].toSymbol();
|
||||
if (map->has(key)) {
|
||||
args.rval().setBoolean(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,16 +115,27 @@ WeakMap_get_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsWeakMap(args.thisv()));
|
||||
|
||||
if (!args.get(0).isObject()) {
|
||||
if (!CanBeHeldWeakly(args.get(0))) {
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
|
||||
JSObject* key = &args[0].toObject();
|
||||
if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
|
||||
args.rval().set(ptr->value());
|
||||
return true;
|
||||
WeakMapObject& weakMap = args.thisv().toObject().as<WeakMapObject>();
|
||||
if (args.get(0).isObject()) {
|
||||
if (ObjectValueMap* map = weakMap.getMap()) {
|
||||
JSObject* key = &args[0].toObject();
|
||||
if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
|
||||
args.rval().set(ptr->value());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (SymbolValueMap* map = weakMap.getSymbolMap()) {
|
||||
JS::Symbol* key = args[0].toSymbol();
|
||||
if (SymbolValueMap::Ptr ptr = map->lookup(key)) {
|
||||
args.rval().set(ptr->value());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,17 +155,29 @@ WeakMap_delete_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsWeakMap(args.thisv()));
|
||||
|
||||
if (!args.get(0).isObject()) {
|
||||
if (!CanBeHeldWeakly(args.get(0))) {
|
||||
args.rval().setBoolean(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
|
||||
JSObject* key = &args[0].toObject();
|
||||
if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
|
||||
map->remove(ptr);
|
||||
args.rval().setBoolean(true);
|
||||
return true;
|
||||
WeakMapObject& weakMap = args.thisv().toObject().as<WeakMapObject>();
|
||||
if (args.get(0).isObject()) {
|
||||
if (ObjectValueMap* map = weakMap.getMap()) {
|
||||
JSObject* key = &args[0].toObject();
|
||||
if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
|
||||
map->remove(ptr);
|
||||
args.rval().setBoolean(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (SymbolValueMap* map = weakMap.getSymbolMap()) {
|
||||
JS::Symbol* key = args[0].toSymbol();
|
||||
if (SymbolValueMap::Ptr ptr = map->lookup(key)) {
|
||||
map->remove(ptr);
|
||||
args.rval().setBoolean(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,22 +209,13 @@ TryPreserveReflector(JSContext* cx, HandleObject obj)
|
||||
return true;
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE bool
|
||||
SetWeakMapEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
|
||||
HandleObject key, HandleValue value)
|
||||
static bool
|
||||
SetWeakMapObjectEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
|
||||
HandleObject key, HandleValue value)
|
||||
{
|
||||
if (!EnsureWeakMapData(cx, mapObj))
|
||||
return false;
|
||||
ObjectValueMap* map = mapObj->getMap();
|
||||
if (!map) {
|
||||
auto newMap = cx->make_unique<ObjectValueMap>(cx, mapObj.get());
|
||||
if (!newMap)
|
||||
return false;
|
||||
if (!newMap->init()) {
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
map = newMap.release();
|
||||
mapObj->setPrivate(map);
|
||||
}
|
||||
|
||||
// Preserve wrapped native keys to prevent wrapper optimization.
|
||||
if (!TryPreserveReflector(cx, key))
|
||||
@@ -162,13 +236,28 @@ SetWeakMapEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
WeakMap_set_impl(JSContext* cx, const CallArgs& args)
|
||||
static bool
|
||||
SetWeakMapSymbolEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
|
||||
Handle<JS::Symbol*> key, HandleValue value)
|
||||
{
|
||||
MOZ_ASSERT(IsWeakMap(args.thisv()));
|
||||
if (!EnsureWeakMapData(cx, mapObj))
|
||||
return false;
|
||||
SymbolValueMap* map = mapObj->getSymbolMap();
|
||||
|
||||
if (!args.get(0).isObject()) {
|
||||
UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), nullptr);
|
||||
MOZ_ASSERT_IF(value.isObject(), value.toObject().compartment() == mapObj->compartment());
|
||||
if (!map->put(key, value)) {
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE bool
|
||||
SetWeakMapEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
|
||||
HandleValue key, HandleValue value)
|
||||
{
|
||||
if (!CanBeHeldWeakly(key)) {
|
||||
UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, key, nullptr);
|
||||
if (!bytes)
|
||||
return false;
|
||||
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
|
||||
@@ -176,11 +265,24 @@ WeakMap_set_impl(JSContext* cx, const CallArgs& args)
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedObject key(cx, &args[0].toObject());
|
||||
if (key.isObject()) {
|
||||
RootedObject objectKey(cx, &key.toObject());
|
||||
return SetWeakMapObjectEntryInternal(cx, mapObj, objectKey, value);
|
||||
}
|
||||
|
||||
Rooted<JS::Symbol*> symbolKey(cx, key.toSymbol());
|
||||
return SetWeakMapSymbolEntryInternal(cx, mapObj, symbolKey, value);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
WeakMap_set_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsWeakMap(args.thisv()));
|
||||
|
||||
Rooted<JSObject*> thisObj(cx, &args.thisv().toObject());
|
||||
Rooted<WeakMapObject*> map(cx, &thisObj->as<WeakMapObject>());
|
||||
|
||||
if (!SetWeakMapEntryInternal(cx, map, key, args.get(1)))
|
||||
if (!SetWeakMapEntryInternal(cx, map, args.get(0), args.get(1)))
|
||||
return false;
|
||||
args.rval().set(args.thisv());
|
||||
return true;
|
||||
@@ -193,6 +295,13 @@ js::WeakMap_set(JSContext* cx, unsigned argc, Value* vp)
|
||||
return CallNonGenericMethod<IsWeakMap, WeakMap_set_impl>(cx, args);
|
||||
}
|
||||
|
||||
bool
|
||||
js::SetWeakMapEntryValue(JSContext* cx, HandleObject mapObj, HandleValue key, HandleValue val)
|
||||
{
|
||||
Rooted<WeakMapObject*> rootedMap(cx, &mapObj->as<WeakMapObject>());
|
||||
return SetWeakMapEntryInternal(cx, rootedMap, key, val);
|
||||
}
|
||||
|
||||
JS_FRIEND_API(bool)
|
||||
JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHandleObject ret)
|
||||
{
|
||||
@@ -205,11 +314,11 @@ JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHan
|
||||
RootedObject arr(cx, NewDenseEmptyArray(cx));
|
||||
if (!arr)
|
||||
return false;
|
||||
ObjectValueMap* map = obj->as<WeakMapObject>().getMap();
|
||||
if (map) {
|
||||
WeakMapObject::Data* data = obj->as<WeakMapObject>().getData();
|
||||
if (data) {
|
||||
// Prevent GC from mutating the weakmap while iterating.
|
||||
AutoSuppressGC suppress(cx);
|
||||
for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) {
|
||||
for (ObjectValueMap::Base::Range r = data->objectMap.all(); !r.empty(); r.popFront()) {
|
||||
JS::ExposeObjectToActiveJS(r.front().key());
|
||||
RootedObject key(cx, r.front().key());
|
||||
if (!cx->compartment()->wrap(cx, &key))
|
||||
@@ -217,6 +326,14 @@ JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHan
|
||||
if (!NewbornArrayPush(cx, arr, ObjectValue(*key)))
|
||||
return false;
|
||||
}
|
||||
for (SymbolValueMap::Base::Range r = data->symbolMap.all(); !r.empty(); r.popFront()) {
|
||||
gc::ExposeGCThingToActiveJS(JS::GCCellPtr(r.front().key().get()));
|
||||
RootedValue key(cx, SymbolValue(r.front().key()));
|
||||
if (!cx->compartment()->wrap(cx, &key))
|
||||
return false;
|
||||
if (!NewbornArrayPush(cx, arr, key))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ret.set(arr);
|
||||
return true;
|
||||
@@ -225,21 +342,23 @@ JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHan
|
||||
static void
|
||||
WeakMap_mark(JSTracer* trc, JSObject* obj)
|
||||
{
|
||||
if (ObjectValueMap* map = obj->as<WeakMapObject>().getMap())
|
||||
map->trace(trc);
|
||||
if (WeakMapObject::Data* data = obj->as<WeakMapObject>().getData()) {
|
||||
data->objectMap.trace(trc);
|
||||
data->symbolMap.trace(trc);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
WeakMap_finalize(FreeOp* fop, JSObject* obj)
|
||||
{
|
||||
MOZ_ASSERT(fop->maybeOffMainThread());
|
||||
if (ObjectValueMap* map = obj->as<WeakMapObject>().getMap()) {
|
||||
if (WeakMapObject::Data* data = obj->as<WeakMapObject>().getData()) {
|
||||
#ifdef DEBUG
|
||||
map->~ObjectValueMap();
|
||||
memset(static_cast<void*>(map), 0xdc, sizeof(*map));
|
||||
fop->free_(map);
|
||||
data->~Data();
|
||||
memset(static_cast<void*>(data), 0xdc, sizeof(*data));
|
||||
fop->free_(data);
|
||||
#else
|
||||
fop->delete_(map);
|
||||
fop->delete_(data);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -282,7 +401,8 @@ JS::SetWeakMapEntry(JSContext* cx, HandleObject mapObj, HandleObject key,
|
||||
CHECK_REQUEST(cx);
|
||||
assertSameCompartment(cx, key, val);
|
||||
Rooted<WeakMapObject*> rootedMap(cx, &mapObj->as<WeakMapObject>());
|
||||
return SetWeakMapEntryInternal(cx, rootedMap, key, val);
|
||||
RootedValue keyValue(cx, ObjectValue(*key));
|
||||
return SetWeakMapEntryInternal(cx, rootedMap, keyValue, val);
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -386,4 +506,3 @@ js::InitBareWeakMapCtor(JSContext* cx, HandleObject obj)
|
||||
{
|
||||
return InitWeakMapClass(cx, obj, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,11 @@ class WeakMapObject : public NativeObject
|
||||
public:
|
||||
static const Class class_;
|
||||
|
||||
ObjectValueMap* getMap() { return static_cast<ObjectValueMap*>(getPrivate()); }
|
||||
struct Data;
|
||||
|
||||
Data* getData() { return static_cast<Data*>(getPrivate()); }
|
||||
ObjectValueMap* getMap();
|
||||
SymbolValueMap* getSymbolMap();
|
||||
};
|
||||
|
||||
} // namespace js
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
#include "gc/Nursery.h"
|
||||
#include "gc/Tracer.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/Symbol.h"
|
||||
|
||||
#include "jswrapper.h"
|
||||
#include "jsobjinlines.h"
|
||||
|
||||
#include "vm/Interpreter-inl.h"
|
||||
@@ -31,17 +33,64 @@ IsWeakRef(HandleValue v)
|
||||
return v.isObject() && v.toObject().is<WeakRefObject>();
|
||||
}
|
||||
|
||||
static bool
|
||||
GetPrototypeFromWeakRefConstructor(JSContext* cx, HandleObject newTarget,
|
||||
MutableHandleObject proto)
|
||||
{
|
||||
if (!GetPrototypeFromConstructor(cx, newTarget, proto))
|
||||
return false;
|
||||
if (proto)
|
||||
return true;
|
||||
|
||||
RootedObject realmObject(cx, CheckedUnwrap(newTarget, /* stopAtWindowProxy = */ false));
|
||||
if (!realmObject)
|
||||
return false;
|
||||
|
||||
{
|
||||
JSAutoCompartment ac(cx, realmObject);
|
||||
Rooted<GlobalObject*> global(cx, &realmObject->global());
|
||||
if (!GlobalObject::ensureConstructor(cx, global, JSProto_WeakRef))
|
||||
return false;
|
||||
proto.set(&global->getPrototype(JSProto_WeakRef).toObject());
|
||||
}
|
||||
|
||||
return cx->compartment()->wrap(cx, proto);
|
||||
}
|
||||
|
||||
static bool
|
||||
WeakRef_deref_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsWeakRef(args.thisv()));
|
||||
|
||||
WeakRefObject::Referent* data = GetReferent(&args.thisv().toObject());
|
||||
JSObject* target = data ? data->target.get() : nullptr;
|
||||
if (target)
|
||||
args.rval().setObject(*target);
|
||||
else
|
||||
if (!data) {
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data->isObject()) {
|
||||
JSObject* target = data->objectTarget.get();
|
||||
if (target) {
|
||||
RootedValue kept(cx, ObjectValue(*target));
|
||||
if (!cx->runtime()->addWeakRefKeptObject(cx, kept))
|
||||
return false;
|
||||
args.rval().set(kept);
|
||||
} else {
|
||||
args.rval().setUndefined();
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(data->isSymbol());
|
||||
JS::Symbol* target = data->symbolTarget.get();
|
||||
if (target) {
|
||||
RootedValue kept(cx, SymbolValue(target));
|
||||
if (!cx->runtime()->addWeakRefKeptObject(cx, kept))
|
||||
return false;
|
||||
args.rval().set(kept);
|
||||
} else {
|
||||
args.rval().setUndefined();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -83,13 +132,18 @@ InitWeakRefClass(JSContext* cx, HandleObject obj, bool defineMembers)
|
||||
}
|
||||
|
||||
/* static */ WeakRefObject*
|
||||
WeakRefObject::create(JSContext* cx, HandleObject target, HandleObject proto /* = nullptr */)
|
||||
WeakRefObject::create(JSContext* cx, HandleValue target, HandleObject proto /* = nullptr */)
|
||||
{
|
||||
Rooted<WeakRefObject*> obj(cx, NewObjectWithClassProto<WeakRefObject>(cx, proto));
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
Referent* data = cx->new_<Referent>(target, cx->options().weakRefs());
|
||||
Referent* data;
|
||||
if (target.isObject())
|
||||
data = cx->new_<Referent>(&target.toObject());
|
||||
else
|
||||
data = cx->new_<Referent>(target.toSymbol());
|
||||
|
||||
if (!data)
|
||||
return nullptr;
|
||||
|
||||
@@ -97,6 +151,18 @@ WeakRefObject::create(JSContext* cx, HandleObject target, HandleObject proto /*
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool
|
||||
js::CanBeHeldWeakly(HandleValue target)
|
||||
{
|
||||
if (target.isObject())
|
||||
return true;
|
||||
|
||||
if (target.isSymbol())
|
||||
return target.toSymbol()->code() != JS::SymbolCode::InSymbolRegistry;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
WeakRefObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -105,28 +171,25 @@ WeakRefObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
if (!ThrowIfNotConstructing(cx, args, "WeakRef"))
|
||||
return false;
|
||||
|
||||
if (!args.get(0).isObject()) {
|
||||
UniqueChars bytes =
|
||||
DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), nullptr);
|
||||
if (!bytes)
|
||||
return false;
|
||||
|
||||
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
|
||||
bytes.get());
|
||||
if (!CanBeHeldWeakly(args.get(0))) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_WEAKREF_TARGET);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedObject target(cx, &args[0].toObject());
|
||||
RootedValue target(cx, args[0]);
|
||||
|
||||
RootedObject proto(cx);
|
||||
RootedObject newTarget(cx, &args.newTarget().toObject());
|
||||
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
|
||||
if (!GetPrototypeFromWeakRefConstructor(cx, newTarget, &proto))
|
||||
return false;
|
||||
|
||||
Rooted<WeakRefObject*> obj(cx, WeakRefObject::create(cx, target, proto));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
if (!cx->runtime()->addWeakRefKeptObject(cx, target))
|
||||
return false;
|
||||
|
||||
args.rval().setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
@@ -142,19 +205,23 @@ WeakRefObject::deref(JSContext* cx, unsigned argc, Value* vp)
|
||||
WeakRefObject::trace(JSTracer* trc, JSObject* obj)
|
||||
{
|
||||
if (Referent* data = GetReferent(obj)) {
|
||||
JSObject* target = data->target.unbarrieredGet();
|
||||
if (!target)
|
||||
return;
|
||||
if (data->isObject()) {
|
||||
JSObject* target = data->objectTarget.unbarrieredGet();
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
// When pref-disabled, keep referent alive via strong trace so deref()
|
||||
// stays usable as a stub without touching GC internals.
|
||||
if (!data->enabled) {
|
||||
TraceManuallyBarrieredEdge(trc, data->target.unsafeGet(), "WeakRef stub referent");
|
||||
} else if (IsInsideNursery(target)) {
|
||||
// Weak edges must be tenured; trace strongly while referent is in the nursery.
|
||||
TraceManuallyBarrieredEdge(trc, data->target.unsafeGet(), "WeakRef nursery referent");
|
||||
if (IsInsideNursery(target)) {
|
||||
// Weak edges must be tenured; trace strongly while referent is in the nursery.
|
||||
TraceManuallyBarrieredEdge(trc, data->objectTarget.unsafeGet(),
|
||||
"WeakRef nursery referent");
|
||||
} else {
|
||||
TraceWeakEdge(trc, &data->objectTarget, "WeakRef referent");
|
||||
}
|
||||
} else {
|
||||
TraceWeakEdge(trc, &data->target, "WeakRef referent");
|
||||
MOZ_ASSERT(data->isSymbol());
|
||||
JS::Symbol* target = data->symbolTarget.unbarrieredGet();
|
||||
if (target)
|
||||
TraceWeakEdge(trc, &data->symbolTarget, "WeakRef symbol referent");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +229,6 @@ WeakRefObject::trace(JSTracer* trc, JSObject* obj)
|
||||
/* static */ void
|
||||
WeakRefObject::finalize(FreeOp* fop, JSObject* obj)
|
||||
{
|
||||
MOZ_ASSERT(!fop->maybeOffMainThread());
|
||||
if (Referent* data = GetReferent(obj))
|
||||
fop->delete_(data);
|
||||
}
|
||||
|
||||
@@ -11,20 +11,34 @@
|
||||
|
||||
namespace js {
|
||||
|
||||
bool CanBeHeldWeakly(HandleValue target);
|
||||
|
||||
class WeakRefObject : public NativeObject
|
||||
{
|
||||
public:
|
||||
struct Referent {
|
||||
explicit Referent(JSObject* obj, bool enabled)
|
||||
: target(obj), enabled(enabled) {}
|
||||
WeakRef<JSObject*> target;
|
||||
bool enabled;
|
||||
enum class Kind {
|
||||
Object,
|
||||
Symbol
|
||||
};
|
||||
|
||||
explicit Referent(JSObject* obj)
|
||||
: kind(Kind::Object), objectTarget(obj), symbolTarget(nullptr) {}
|
||||
explicit Referent(JS::Symbol* sym)
|
||||
: kind(Kind::Symbol), objectTarget(nullptr), symbolTarget(sym) {}
|
||||
|
||||
bool isObject() const { return kind == Kind::Object; }
|
||||
bool isSymbol() const { return kind == Kind::Symbol; }
|
||||
|
||||
Kind kind;
|
||||
WeakRef<JSObject*> objectTarget;
|
||||
WeakRef<JS::Symbol*> symbolTarget;
|
||||
};
|
||||
|
||||
static const Class class_;
|
||||
|
||||
static JSObject* initClass(JSContext* cx, HandleObject obj);
|
||||
static WeakRefObject* create(JSContext* cx, HandleObject target, HandleObject proto = nullptr);
|
||||
static WeakRefObject* create(JSContext* cx, HandleValue target, HandleObject proto = nullptr);
|
||||
|
||||
static void trace(JSTracer* trc, JSObject* obj);
|
||||
static void finalize(FreeOp* fop, JSObject* obj);
|
||||
@@ -37,7 +51,8 @@ class WeakRefObject : public NativeObject
|
||||
|
||||
WeakRef<JSObject*>& target() {
|
||||
MOZ_ASSERT(getData());
|
||||
return getData()->target;
|
||||
MOZ_ASSERT(getData()->isObject());
|
||||
return getData()->objectTarget;
|
||||
}
|
||||
|
||||
static const JSPropertySpec properties[];
|
||||
|
||||
@@ -32,7 +32,7 @@ function WeakSet_add(value) {
|
||||
ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "add", typeof S);
|
||||
|
||||
// Step 5.
|
||||
if (!IsObject(value))
|
||||
if (!CanBeHeldWeakly(value))
|
||||
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, DecompileArg(0, value));
|
||||
|
||||
// Steps 7-8.
|
||||
@@ -55,7 +55,7 @@ function WeakSet_delete(value) {
|
||||
ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "delete", typeof S);
|
||||
|
||||
// Step 5.
|
||||
if (!IsObject(value))
|
||||
if (!CanBeHeldWeakly(value))
|
||||
return false;
|
||||
|
||||
// Steps 7-8.
|
||||
@@ -75,7 +75,7 @@ function WeakSet_has(value) {
|
||||
ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "has", typeof S);
|
||||
|
||||
// Step 6.
|
||||
if (!IsObject(value))
|
||||
if (!CanBeHeldWeakly(value))
|
||||
return false;
|
||||
|
||||
// Steps 7-8.
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "builtin/MapObject.h"
|
||||
#include "builtin/SelfHostingDefines.h"
|
||||
#include "builtin/WeakMapObject.h"
|
||||
#include "builtin/WeakRefObject.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/SelfHosting.h"
|
||||
|
||||
@@ -108,7 +109,6 @@ WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
|
||||
if (optimized) {
|
||||
RootedValue keyVal(cx);
|
||||
RootedObject keyObject(cx);
|
||||
RootedValue placeholder(cx, BooleanValue(true));
|
||||
RootedObject map(cx, &obj->getReservedSlot(WEAKSET_MAP_SLOT).toObject());
|
||||
RootedArrayObject array(cx, &iterable.toObject().as<ArrayObject>());
|
||||
@@ -116,7 +116,7 @@ WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
keyVal.set(array->getDenseElement(index));
|
||||
MOZ_ASSERT(!keyVal.isMagic(JS_ELEMENTS_HOLE));
|
||||
|
||||
if (keyVal.isPrimitive()) {
|
||||
if (!CanBeHeldWeakly(keyVal)) {
|
||||
UniqueChars bytes =
|
||||
DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, keyVal, nullptr);
|
||||
if (!bytes)
|
||||
@@ -126,8 +126,7 @@ WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
return false;
|
||||
}
|
||||
|
||||
keyObject = &keyVal.toObject();
|
||||
if (!SetWeakMapEntry(cx, map, keyObject, placeholder))
|
||||
if (!SetWeakMapEntryValue(cx, map, keyVal, placeholder))
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
||||
+19
-3
@@ -13,6 +13,7 @@
|
||||
#include "jsgc.h"
|
||||
|
||||
#include "gc/Heap.h"
|
||||
#include "gc/IdleGC.h"
|
||||
#include "gc/Nursery.h"
|
||||
#include "gc/Statistics.h"
|
||||
#include "gc/StoreBuffer.h"
|
||||
@@ -650,6 +651,13 @@ class GCRuntime
|
||||
void onOutOfMallocMemory();
|
||||
void onOutOfMallocMemory(const AutoLockGC& lock);
|
||||
|
||||
/* Idle-time GC notifications. */
|
||||
void notifyJSExecutionStart();
|
||||
void notifyJSExecutionEnd();
|
||||
IdleGCManager& idleGCMgr() {
|
||||
return idleGC;
|
||||
}
|
||||
|
||||
size_t maxMallocBytesAllocated() { return maxMallocBytes; }
|
||||
|
||||
uint64_t nextCellUniqueId() {
|
||||
@@ -939,6 +947,8 @@ class GCRuntime
|
||||
IncrementalProgress drainMarkStack(SliceBudget& sliceBudget, gcstats::Phase phase);
|
||||
template <class CompartmentIterT> void markWeakReferences(gcstats::Phase phase);
|
||||
void markWeakReferencesInCurrentGroup(gcstats::Phase phase);
|
||||
template <class ZoneIterT> void traceFinalizationRegistryWeakRefs();
|
||||
void traceFinalizationRegistryWeakRefsInCurrentGroup();
|
||||
template <class ZoneIterT, class CompartmentIterT> void markGrayReferences(gcstats::Phase phase);
|
||||
void markBufferedGrayRoots(JS::Zone* zone);
|
||||
void markGrayReferencesInCurrentGroup(gcstats::Phase phase);
|
||||
@@ -951,6 +961,7 @@ class GCRuntime
|
||||
void getNextZoneGroup();
|
||||
void endMarkingZoneGroup();
|
||||
void beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock);
|
||||
void sweepFinalizationRegistries();
|
||||
bool shouldReleaseObservedTypes();
|
||||
void endSweepingZoneGroup();
|
||||
IncrementalProgress performSweepActions(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock);
|
||||
@@ -966,6 +977,8 @@ class GCRuntime
|
||||
void sweepZones(FreeOp* fop, bool lastGC);
|
||||
void decommitAllWithoutUnlocking(const AutoLockGC& lock);
|
||||
void startDecommit();
|
||||
bool sweepBackgroundFinalizePhaseInParallel(ZoneList& zones, const FinalizePhase& phase,
|
||||
Arena** emptyArenas);
|
||||
void queueZonesForBackgroundSweep(ZoneList& zones);
|
||||
void sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks);
|
||||
void assertBackgroundSweepingFinished();
|
||||
@@ -976,10 +989,10 @@ class GCRuntime
|
||||
void endCompactPhase(JS::gcreason::Reason reason);
|
||||
void sweepTypesAfterCompacting(Zone* zone);
|
||||
void sweepZoneAfterCompacting(Zone* zone);
|
||||
MOZ_MUST_USE bool relocateArenas(Zone* zone, JS::gcreason::Reason reason,
|
||||
Arena*& relocatedListOut, SliceBudget& sliceBudget);
|
||||
[[nodiscard]] bool relocateArenas(Zone* zone, JS::gcreason::Reason reason,
|
||||
Arena*& relocatedListOut, SliceBudget& sliceBudget);
|
||||
void updateTypeDescrObjects(MovingTracer* trc, Zone* zone);
|
||||
void updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds, size_t bgTaskCount);
|
||||
void updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds);
|
||||
void updateAllCellPointers(MovingTracer* trc, Zone* zone);
|
||||
void updatePointersToRelocatedCells(Zone* zone, AutoLockForExclusiveAccess& lock);
|
||||
void protectAndHoldArenas(Arena* arenaList);
|
||||
@@ -1019,6 +1032,9 @@ class GCRuntime
|
||||
GCSchedulingTunables tunables;
|
||||
GCSchedulingState schedulingState;
|
||||
|
||||
/* Idle-time garbage collection manager. */
|
||||
IdleGCManager idleGC;
|
||||
|
||||
MemProfiler mMemProfiler;
|
||||
|
||||
private:
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* 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/. */
|
||||
|
||||
/**
|
||||
* IDLE-TIME GARBAGE COLLECTION SYSTEM
|
||||
*
|
||||
* Overview
|
||||
* --------
|
||||
* This system implements idle-time-only garbage collection, deferring GC work
|
||||
* to periods when the JavaScript engine is not actively processing code.
|
||||
* This improves perceived responsiveness by avoiding GC pauses during critical
|
||||
* execution windows.
|
||||
*
|
||||
* Architecture
|
||||
* -----------
|
||||
*
|
||||
* The system consists of several key components:
|
||||
*
|
||||
* 1. IdleGCManager (gc/IdleGC.h, gc/IdleGC.cpp)
|
||||
* - Tracks when the JS engine is executing vs. idle
|
||||
* - Maintains timestamp of last execution activity
|
||||
* - Checks if sufficient idle time has passed
|
||||
* - Configurable idle threshold (default: 100ms)
|
||||
* - Can determine which GC reasons should bypass idle checks
|
||||
*
|
||||
* 2. GCRuntime Integration (gc/GCRuntime.h)
|
||||
* - Contains an IdleGCManager instance
|
||||
* - Provides public methods: notifyJSExecutionStart/End()
|
||||
* - Modified checkIfGCAllowedInCurrentState() to check idle status
|
||||
*
|
||||
* 3. Activity Tracking (vm/Runtime.cpp)
|
||||
* - triggerActivityCallback() now notifies IdleGCManager
|
||||
* - Called when JS enters/exits request (execution boundary)
|
||||
* - Updated in jsapi.cpp's StartRequest/StopRequest functions
|
||||
*
|
||||
* 4. Public API (jsapi.h, jsapi.cpp)
|
||||
* - JS_SetIdleGCEnabled() / JS_IsIdleGCEnabled()
|
||||
* - JS_SetIdleGCThreshold() / JS_GetIdleGCThreshold()
|
||||
* - JS_GetIdleTimeSinceLastExecution()
|
||||
* - Allows embedders to configure idle GC behavior
|
||||
*
|
||||
* Behavior
|
||||
* --------
|
||||
*
|
||||
* Normal GC Trigger (Idle GC Enabled):
|
||||
*
|
||||
* 1. JS code executes → notifyJSExecutionStart() called
|
||||
* 2. JS code finishes → notifyJSExecutionEnd() called, timestamp recorded
|
||||
* 3. GC needed → checkIfGCAllowedInCurrentState() checks idle status
|
||||
* 4a. If idle >= threshold → GC proceeds normally
|
||||
* 4b. If still executing/idle < threshold → GC is deferred
|
||||
* 5. Once idle threshold is met, next GC request proceeds
|
||||
*
|
||||
* Critical GC Triggers (Always Proceed):
|
||||
*
|
||||
* The following GC reasons bypass idle checking:
|
||||
* - OUT_OF_MEMORY: Memory pressure conditions
|
||||
* - ALLOC_TRIGGER: Allocation threshold exceeded
|
||||
* - MALLOC_PRESSURE: Malloc pressure from OS
|
||||
* - EAGER_ALLOC_TRIGGER: Eager allocation trigger
|
||||
* - API: Explicit JS API calls
|
||||
* - DETERMINISTIC: Deterministic tests
|
||||
* - EVICT_NURSERY: Nursery eviction
|
||||
* - SHUTDOWN_CC, DESTROY_RUNTIME, LAST_DITCH: Shutdown GCs
|
||||
* - DESTROY_ZONE, COMPARTMENT_REVOKED: Zone/compartment destruction
|
||||
*
|
||||
* Configuration
|
||||
* -------------
|
||||
*
|
||||
* Idle GC Enabled (default: true):
|
||||
* - Enables idle-time-only GC mode
|
||||
* - Can be toggled dynamically via JS_SetIdleGCEnabled()
|
||||
*
|
||||
* Idle Threshold (default: 100ms):
|
||||
* - Minimum idle time before GC is permitted
|
||||
* - Configurable via JS_SetIdleGCThreshold(ms)
|
||||
* - Typical values: 50-200ms depending on application
|
||||
*
|
||||
* Integration Examples
|
||||
* --------------------
|
||||
*
|
||||
* Browser Integration:
|
||||
*
|
||||
* // When browser loads configuration
|
||||
* JS_SetIdleGCEnabled(cx, true);
|
||||
* JS_SetIdleGCThreshold(cx, 100); // 100ms idle threshold
|
||||
*
|
||||
* // Monitor idle GC effectiveness (optional)
|
||||
* uint64_t idleTime = JS_GetIdleTimeSinceLastExecution(cx);
|
||||
* if (idleTime > JS_GetIdleGCThreshold(cx)) {
|
||||
* // System is idle, GC would be allowed if triggered
|
||||
* }
|
||||
*
|
||||
* Disabling for Specific Scenarios:
|
||||
*
|
||||
* // During initialization when nothing is "idle" yet
|
||||
* JS_SetIdleGCEnabled(cx, false);
|
||||
* // ... do initial setup ...
|
||||
* JS_SetIdleGCEnabled(cx, true); // Re-enable for normal operation
|
||||
*
|
||||
* Performance Considerations
|
||||
* --------------------------
|
||||
*
|
||||
* Benefits:
|
||||
* - Reduced jank during active JS execution
|
||||
* - GC pauses moved to idle periods where users won't notice
|
||||
* - Especially effective for interactive applications
|
||||
* - Improves Time-to-Interactive and First Input Delay metrics
|
||||
*
|
||||
* Tradeoffs:
|
||||
* - May accumulate more garbage before collection
|
||||
* - Requires predictable idle periods (not suitable for all workloads)
|
||||
* - Critical memory pressure GCs still proceed immediately
|
||||
*
|
||||
* Tuning:
|
||||
* - Lower threshold (50ms) = more frequent GC, less memory overhead
|
||||
* - Higher threshold (200ms) = less GC overhead, more memory usage
|
||||
* - Optimal value depends on application's execution pattern
|
||||
*
|
||||
* Testing
|
||||
* -------
|
||||
*
|
||||
* Unit Tests:
|
||||
* // Test idle detection
|
||||
* JS_SetIdleGCThreshold(cx, 100);
|
||||
* // Simulate JS execution
|
||||
* cx->runtime()->gc.notifyJSExecutionStart();
|
||||
* // ... wait 50ms ...
|
||||
* MOZ_ASSERT(!cx->runtime()->gc.idleGCMgr().isIdleEnough());
|
||||
* cx->runtime()->gc.notifyJSExecutionEnd();
|
||||
* // ... wait 150ms ...
|
||||
* MOZ_ASSERT(cx->runtime()->gc.idleGCMgr().isIdleEnough());
|
||||
*
|
||||
* Integration Tests:
|
||||
* - Verify GC is deferred during active execution
|
||||
* - Verify GC proceeds after idle period
|
||||
* - Verify critical GC reasons bypass idle check
|
||||
* - Measure latency improvements
|
||||
*
|
||||
* Implementation Notes
|
||||
* --------------------
|
||||
*
|
||||
* Thread Safety:
|
||||
* - IdleGCManager uses mozilla::Atomic for thread-safe state
|
||||
* - TimeStamp operations are atomic
|
||||
* - No additional locking needed beyond existing GC locks
|
||||
*
|
||||
* Compatibility:
|
||||
* - Works with both incremental and non-incremental GC
|
||||
* - Compatible with generational GC
|
||||
* - Works with zone GC and full GC
|
||||
* - Respects existing GC suppression mechanisms
|
||||
*
|
||||
* Future Enhancements
|
||||
* -------------------
|
||||
*
|
||||
* Potential improvements:
|
||||
* - Adaptive idle threshold based on historical GC times
|
||||
* - Per-zone idle configuration
|
||||
* - Integration with browser rendering idle callback API
|
||||
* - Metrics/telemetry for idle GC effectiveness
|
||||
* - Machine learning-based prediction of idle periods
|
||||
* - Cooperative GC scheduling with other subsystems
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gc/IdleGC.h"
|
||||
#include "jsapi.h"
|
||||
|
||||
namespace js {
|
||||
namespace gc {
|
||||
|
||||
IdleGCManager::IdleGCManager()
|
||||
: lastExecutionTime_(mozilla::TimeStamp::Now()),
|
||||
idleGCEnabled_(true),
|
||||
idleThresholdMs_(100), // 100ms default idle threshold
|
||||
isExecuting_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
IdleGCManager::notifyJSExecutionStart()
|
||||
{
|
||||
isExecuting_ = true;
|
||||
}
|
||||
|
||||
void
|
||||
IdleGCManager::notifyJSExecutionEnd()
|
||||
{
|
||||
isExecuting_ = false;
|
||||
lastExecutionTime_ = mozilla::TimeStamp::Now();
|
||||
}
|
||||
|
||||
bool
|
||||
IdleGCManager::isIdleEnough() const
|
||||
{
|
||||
if (!idleGCEnabled_) {
|
||||
return true; // If disabled, always consider idle
|
||||
}
|
||||
|
||||
if (isExecuting_) {
|
||||
return false; // Still executing, not idle
|
||||
}
|
||||
|
||||
uint64_t idleTime = idleTimeSinceLastExecution();
|
||||
return idleTime >= idleThresholdMs_;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
IdleGCManager::idleTimeSinceLastExecution() const
|
||||
{
|
||||
mozilla::TimeStamp now = mozilla::TimeStamp::Now();
|
||||
mozilla::TimeDuration idle = now - lastExecutionTime_;
|
||||
return idle.ToMilliseconds();
|
||||
}
|
||||
|
||||
bool
|
||||
IdleGCManager::shouldBypassIdleCheck(JS::gcreason::Reason reason)
|
||||
{
|
||||
// These reasons indicate urgent GC needs that should bypass idle checking
|
||||
switch (reason) {
|
||||
// Allocation and memory pressure conditions.
|
||||
case JS::gcreason::ALLOC_TRIGGER:
|
||||
case JS::gcreason::EAGER_ALLOC_TRIGGER:
|
||||
case JS::gcreason::TOO_MUCH_MALLOC:
|
||||
case JS::gcreason::MEM_PRESSURE:
|
||||
case JS::gcreason::LAST_DITCH:
|
||||
|
||||
// Nursery/store-buffer pressure.
|
||||
case JS::gcreason::OUT_OF_NURSERY:
|
||||
case JS::gcreason::EVICT_NURSERY:
|
||||
case JS::gcreason::FULL_STORE_BUFFER:
|
||||
case JS::gcreason::SHARED_MEMORY_LIMIT:
|
||||
|
||||
// Explicit API calls
|
||||
case JS::gcreason::API:
|
||||
case JS::gcreason::ABORT_GC:
|
||||
|
||||
// Shutdown and finalization
|
||||
case JS::gcreason::SHUTDOWN_CC:
|
||||
case JS::gcreason::DESTROY_RUNTIME:
|
||||
case JS::gcreason::NSJSCONTEXT_DESTROY:
|
||||
case JS::gcreason::XPCONNECT_SHUTDOWN:
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
IdleGCManager::reset()
|
||||
{
|
||||
lastExecutionTime_ = mozilla::TimeStamp::Now();
|
||||
isExecuting_ = false;
|
||||
}
|
||||
|
||||
} // namespace gc
|
||||
} // namespace js
|
||||
@@ -0,0 +1,118 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* 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 gc_IdleGC_h
|
||||
#define gc_IdleGC_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "js/GCAPI.h"
|
||||
|
||||
namespace js {
|
||||
namespace gc {
|
||||
|
||||
/*
|
||||
* Idle-Time Garbage Collection System
|
||||
* ====================================
|
||||
*
|
||||
* This system defers garbage collection to occur only during periods when
|
||||
* the browser is not actively processing JavaScript. This helps maintain
|
||||
* responsiveness by avoiding GC pauses during critical execution windows.
|
||||
*
|
||||
* When JavaScript execution is active, GC triggers are deferred. Once the
|
||||
* JS engine has been idle for a configurable threshold period, pending GC
|
||||
* work is performed immediately or incrementally as appropriate.
|
||||
*
|
||||
* Key characteristics:
|
||||
* - Tracks JavaScript activity via hooks in the execution engine
|
||||
* - Configurable idle time threshold (default: 100ms)
|
||||
* - Can be disabled per-zone or globally
|
||||
* - Works with both incremental and non-incremental GC modes
|
||||
* - Respects critical GC reasons that override idle checking
|
||||
*/
|
||||
|
||||
class IdleGCManager
|
||||
{
|
||||
public:
|
||||
// Initialize the idle GC manager
|
||||
IdleGCManager();
|
||||
|
||||
/*
|
||||
* Called when JavaScript execution begins. Marks the engine as active.
|
||||
*/
|
||||
void notifyJSExecutionStart();
|
||||
|
||||
/*
|
||||
* Called when JavaScript execution ends. Records the end time for
|
||||
* idle detection purposes.
|
||||
*/
|
||||
void notifyJSExecutionEnd();
|
||||
|
||||
/*
|
||||
* Check if the system has been idle for long enough to permit GC.
|
||||
* Returns true if sufficient idle time has passed since last JS execution.
|
||||
*/
|
||||
bool isIdleEnough() const;
|
||||
|
||||
/*
|
||||
* Get the amount of idle time since the last JS execution.
|
||||
* Returns time in milliseconds.
|
||||
*/
|
||||
uint64_t idleTimeSinceLastExecution() const;
|
||||
|
||||
/*
|
||||
* Set the idle threshold - minimum idle time before GC is permitted.
|
||||
* Time is in milliseconds. Default is 100ms.
|
||||
*/
|
||||
void setIdleThresholdMs(uint64_t thresholdMs) {
|
||||
idleThresholdMs_ = thresholdMs;
|
||||
}
|
||||
|
||||
uint64_t idleThresholdMs() const {
|
||||
return idleThresholdMs_;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable or disable idle-time-only GC mode.
|
||||
*/
|
||||
void setIdleGCEnabled(bool enabled) {
|
||||
idleGCEnabled_ = enabled;
|
||||
}
|
||||
|
||||
bool isIdleGCEnabled() const {
|
||||
return idleGCEnabled_;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if a GC reason should bypass idle checking.
|
||||
* Critical reasons (OOM-like pressure, nursery pressure, explicit requests)
|
||||
* always proceed.
|
||||
*/
|
||||
static bool shouldBypassIdleCheck(JS::gcreason::Reason reason);
|
||||
|
||||
/*
|
||||
* Reset idle tracking state (used during GC or at shutdown).
|
||||
*/
|
||||
void reset();
|
||||
|
||||
private:
|
||||
// Timestamp of the last JavaScript execution activity
|
||||
mozilla::TimeStamp lastExecutionTime_;
|
||||
|
||||
// Whether idle-time-only GC mode is enabled
|
||||
mozilla::Atomic<bool, mozilla::ReleaseAcquire> idleGCEnabled_;
|
||||
|
||||
// Minimum idle time (in milliseconds) before GC is permitted
|
||||
mozilla::Atomic<uint64_t, mozilla::ReleaseAcquire> idleThresholdMs_;
|
||||
|
||||
// Whether the JS engine is currently executing
|
||||
mozilla::Atomic<bool, mozilla::ReleaseAcquire> isExecuting_;
|
||||
};
|
||||
|
||||
} // namespace gc
|
||||
} // namespace js
|
||||
|
||||
#endif // gc_IdleGC_h
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "jscntxt.h"
|
||||
|
||||
#include "ds/SplayTree.h"
|
||||
#include "gc/Barrier.h"
|
||||
#include "gc/FindSCCs.h"
|
||||
#include "gc/GCRuntime.h"
|
||||
#include "js/GCHashTable.h"
|
||||
@@ -325,6 +326,10 @@ struct Zone : public JS::shadow::Zone,
|
||||
using WeakEdges = js::Vector<js::gc::TenuredCell**, 0, js::SystemAllocPolicy>;
|
||||
WeakEdges gcWeakRefs;
|
||||
|
||||
// FinalizationRegistry objects with weakly-held target cells in this zone.
|
||||
using FinalizationRegistryVector = js::Vector<js::WeakRef<JSObject*>, 0, js::SystemAllocPolicy>;
|
||||
FinalizationRegistryVector finalizationRegistries;
|
||||
|
||||
// List of non-ephemeron weak containers to sweep during beginSweepingZoneGroup.
|
||||
mozilla::LinkedList<WeakCache<void*>> weakCaches_;
|
||||
void registerWeakCache(WeakCache<void*>* cachep) {
|
||||
|
||||
@@ -1464,6 +1464,12 @@ TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_
|
||||
res.isNumber() &&
|
||||
!TypedArrayGetElemStubExists(stub, obj))
|
||||
{
|
||||
if (obj->is<TypedArrayObject>() &&
|
||||
obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!cx->runtime()->jitSupportsFloatingPoint &&
|
||||
(TypedThingRequiresFloatingPoint(obj) || rhs.isDouble()))
|
||||
{
|
||||
@@ -2154,6 +2160,8 @@ ICGetElem_TypedArray::Compiler::generateStubCode(MacroAssembler& masm)
|
||||
Register obj = masm.extractObject(R0, ExtractTemp0);
|
||||
masm.loadPtr(Address(ICStubReg, ICGetElem_TypedArray::offsetOfShape()), scratchReg);
|
||||
masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
|
||||
if (layout_ == Layout_TypedArray)
|
||||
GuardResizableOrGrowableTypedArray(masm, obj, scratchReg, &failure);
|
||||
|
||||
// Ensure the index is an integer.
|
||||
if (cx->runtime()->jitSupportsFloatingPoint) {
|
||||
@@ -2625,6 +2633,9 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_
|
||||
bool expectOutOfBounds;
|
||||
double idx = index.toNumber();
|
||||
if (obj->is<TypedArrayObject>()) {
|
||||
if (obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
|
||||
return true;
|
||||
|
||||
expectOutOfBounds = (idx < 0 || idx >= double(obj->as<TypedArrayObject>().length()));
|
||||
} else {
|
||||
// Typed objects throw on out of bounds accesses. Don't attach
|
||||
@@ -3215,6 +3226,8 @@ ICSetElem_TypedArray::Compiler::generateStubCode(MacroAssembler& masm)
|
||||
Register obj = masm.extractObject(R0, ExtractTemp0);
|
||||
masm.loadPtr(Address(ICStubReg, ICSetElem_TypedArray::offsetOfShape()), scratchReg);
|
||||
masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
|
||||
if (layout_ == Layout_TypedArray)
|
||||
GuardResizableOrGrowableTypedArray(masm, obj, scratchReg, &failure);
|
||||
|
||||
// Ensure the index is an integer.
|
||||
if (cx->runtime()->jitSupportsFloatingPoint) {
|
||||
|
||||
@@ -8889,9 +8889,17 @@ CodeGenerator::branchIfNotEmptyObjectElements(Register obj, Label* target)
|
||||
Address(obj, NativeObject::offsetOfElements()),
|
||||
ImmPtr(js::emptyObjectElements),
|
||||
&emptyObj);
|
||||
masm.branchPtr(Assembler::NotEqual,
|
||||
masm.branchPtr(Assembler::Equal,
|
||||
Address(obj, NativeObject::offsetOfElements()),
|
||||
ImmPtr(js::emptyObjectElementsShared),
|
||||
&emptyObj);
|
||||
masm.branchPtr(Assembler::Equal,
|
||||
Address(obj, NativeObject::offsetOfElements()),
|
||||
ImmPtr(js::emptyObjectElementsResizableOrGrowable),
|
||||
&emptyObj);
|
||||
masm.branchPtr(Assembler::NotEqual,
|
||||
Address(obj, NativeObject::offsetOfElements()),
|
||||
ImmPtr(js::emptyObjectElementsSharedResizableOrGrowable),
|
||||
target);
|
||||
masm.bind(&emptyObj);
|
||||
}
|
||||
|
||||
@@ -535,8 +535,7 @@ class CodeGenerator final : public CodeGeneratorSpecific
|
||||
Label* ifDoesntEmulateUndefined,
|
||||
Register scratch, OutOfLineTestObject* ool);
|
||||
|
||||
// Branch to target unless obj has an emptyObjectElements or emptyObjectElementsShared
|
||||
// elements pointer.
|
||||
// Branch to target unless obj has one of the empty elements pointers.
|
||||
void branchIfNotEmptyObjectElements(Register obj, Label* target);
|
||||
|
||||
void emitStoreElementTyped(const LAllocation* value, MIRType valueType, MIRType elementType,
|
||||
|
||||
@@ -1245,6 +1245,7 @@ GenerateTypedArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAtta
|
||||
masm.branchPtr(Assembler::AboveOrEqual, tmpReg,
|
||||
ImmPtr(&TypedArrayObject::classes[Scalar::MaxTypedArrayViewType]),
|
||||
failures);
|
||||
GuardResizableOrGrowableTypedArray(masm, object, tmpReg, failures);
|
||||
|
||||
// Load length.
|
||||
masm.loadTypedOrValue(Address(object, TypedArrayObject::lengthOffset()), output);
|
||||
@@ -1644,6 +1645,9 @@ GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript
|
||||
if (!JSID_IS_ATOM(id, cx->names().length))
|
||||
return true;
|
||||
|
||||
if (obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
|
||||
return true;
|
||||
|
||||
if (hasTypedArrayLengthStub(obj))
|
||||
return true;
|
||||
|
||||
@@ -4038,6 +4042,12 @@ GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& i
|
||||
if (!obj->is<TypedArrayObject>() && !obj->is<UnboxedArrayObject>())
|
||||
return false;
|
||||
|
||||
if (obj->is<TypedArrayObject>() &&
|
||||
obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(idval.isInt32() || idval.isString());
|
||||
|
||||
// Don't emit a stub if the access is out of bounds. We make to make
|
||||
@@ -4092,6 +4102,9 @@ GenerateGetTypedOrUnboxedArrayElement(JSContext* cx, MacroAssembler& masm,
|
||||
// Decide to what type index the stub should be optimized
|
||||
Register tmpReg = output.scratchReg().gpr();
|
||||
MOZ_ASSERT(tmpReg != InvalidReg);
|
||||
if (array->is<TypedArrayObject>())
|
||||
GuardResizableOrGrowableTypedArray(masm, object, tmpReg, &failures);
|
||||
|
||||
Register indexReg = tmpReg;
|
||||
if (idval.isString()) {
|
||||
MOZ_ASSERT(GetIndexFromString(idval.toString()) != UINT32_MAX);
|
||||
@@ -4575,6 +4588,7 @@ GenerateSetTypedArrayElement(JSContext* cx, MacroAssembler& masm, IonCache::Stub
|
||||
if (!shape)
|
||||
return false;
|
||||
masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
|
||||
GuardResizableOrGrowableTypedArray(masm, object, temp, &failures);
|
||||
|
||||
// Ensure the index is an int32.
|
||||
Register indexReg;
|
||||
@@ -4661,6 +4675,9 @@ SetPropertyIC::tryAttachTypedArrayElement(JSContext* cx, HandleScript outerScrip
|
||||
if (!IsTypedArrayElementSetInlineable(obj, idval, val))
|
||||
return true;
|
||||
|
||||
if (obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
|
||||
return true;
|
||||
|
||||
*emitted = true;
|
||||
|
||||
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
|
||||
|
||||
@@ -365,13 +365,9 @@ IonBuilder::inlineNativeGetter(CallInfo& callInfo, JSFunction* target)
|
||||
|
||||
// Try to optimize typed array lengths.
|
||||
if (TypedArrayObject::isOriginalLengthGetter(native)) {
|
||||
Scalar::Type type = thisTypes->getTypedArrayType(constraints());
|
||||
if (type == Scalar::MaxTypedArrayViewType)
|
||||
return InliningStatus_NotInlined;
|
||||
|
||||
MInstruction* length = addTypedArrayLength(thisArg);
|
||||
current->push(length);
|
||||
return InliningStatus_Inlined;
|
||||
// RAB/GSAB views can have dynamic length or temporarily become
|
||||
// out-of-bounds. Let the property IC/VM path handle the getter.
|
||||
return InliningStatus_NotInlined;
|
||||
}
|
||||
|
||||
// Try to optimize RegExp getters.
|
||||
@@ -2480,21 +2476,10 @@ IsTypedArrayObject(CompilerConstraintList* constraints, MDefinition* def)
|
||||
IonBuilder::InliningStatus
|
||||
IonBuilder::inlinePossiblyWrappedTypedArrayLength(CallInfo& callInfo)
|
||||
{
|
||||
MOZ_ASSERT(!callInfo.constructing());
|
||||
MOZ_ASSERT(callInfo.argc() == 1);
|
||||
if (callInfo.getArg(0)->type() != MIRType::Object)
|
||||
return InliningStatus_NotInlined;
|
||||
if (getInlineReturnType() != MIRType::Int32)
|
||||
return InliningStatus_NotInlined;
|
||||
(void) callInfo;
|
||||
|
||||
if (!IsTypedArrayObject(constraints(), callInfo.getArg(0)))
|
||||
return InliningStatus_NotInlined;
|
||||
|
||||
MInstruction* length = addTypedArrayLength(callInfo.getArg(0));
|
||||
current->push(length);
|
||||
|
||||
callInfo.setImplicitlyUsedUnchecked();
|
||||
return InliningStatus_Inlined;
|
||||
// RAB/GSAB views require dynamic length semantics.
|
||||
return InliningStatus_NotInlined;
|
||||
}
|
||||
|
||||
IonBuilder::InliningStatus
|
||||
@@ -3251,45 +3236,14 @@ bool
|
||||
IonBuilder::atomicsMeetsPreconditions(CallInfo& callInfo, Scalar::Type* arrayType,
|
||||
bool* requiresTagCheck, AtomicCheckResult checkResult)
|
||||
{
|
||||
if (!JitSupportsAtomics())
|
||||
return false;
|
||||
(void) callInfo;
|
||||
(void) arrayType;
|
||||
(void) requiresTagCheck;
|
||||
(void) checkResult;
|
||||
|
||||
if (callInfo.getArg(0)->type() != MIRType::Object)
|
||||
return false;
|
||||
|
||||
if (callInfo.getArg(1)->type() != MIRType::Int32)
|
||||
return false;
|
||||
|
||||
// Ensure that the first argument is a TypedArray that maps shared
|
||||
// memory.
|
||||
//
|
||||
// Then check both that the element type is something we can
|
||||
// optimize and that the return type is suitable for that element
|
||||
// type.
|
||||
|
||||
TemporaryTypeSet* arg0Types = callInfo.getArg(0)->resultTypeSet();
|
||||
if (!arg0Types)
|
||||
return false;
|
||||
|
||||
TemporaryTypeSet::TypedArraySharedness sharedness;
|
||||
*arrayType = arg0Types->getTypedArrayType(constraints(), &sharedness);
|
||||
*requiresTagCheck = sharedness != TemporaryTypeSet::KnownShared;
|
||||
switch (*arrayType) {
|
||||
case Scalar::Int8:
|
||||
case Scalar::Uint8:
|
||||
case Scalar::Int16:
|
||||
case Scalar::Uint16:
|
||||
case Scalar::Int32:
|
||||
return checkResult == DontCheckAtomicResult || getInlineReturnType() == MIRType::Int32;
|
||||
case Scalar::Uint32:
|
||||
// Bug 1077305: it would be attractive to allow inlining even
|
||||
// if the inline return type is Int32, which it will frequently
|
||||
// be.
|
||||
return checkResult == DontCheckAtomicResult || getInlineReturnType() == MIRType::Double;
|
||||
default:
|
||||
// Excludes floating types and Uint8Clamped.
|
||||
return false;
|
||||
}
|
||||
// Atomics bounds checks through addTypedArrayLengthAndData assume stable
|
||||
// SharedArrayBuffer lengths. Avoid this path now that growable SAB exists.
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
+8
-18
@@ -5505,25 +5505,15 @@ jit::ElementAccessIsTypedArray(CompilerConstraintList* constraints,
|
||||
MDefinition* obj, MDefinition* id,
|
||||
Scalar::Type* arrayType)
|
||||
{
|
||||
if (obj->mightBeType(MIRType::String))
|
||||
return false;
|
||||
(void) constraints;
|
||||
(void) obj;
|
||||
(void) id;
|
||||
(void) arrayType;
|
||||
|
||||
if (id->type() != MIRType::Int32 && id->type() != MIRType::Double)
|
||||
return false;
|
||||
|
||||
TemporaryTypeSet* types = obj->resultTypeSet();
|
||||
if (!types)
|
||||
return false;
|
||||
|
||||
*arrayType = types->getTypedArrayType(constraints);
|
||||
|
||||
// FIXME: https://bugzil.la/1536699
|
||||
if (*arrayType == Scalar::MaxTypedArrayViewType ||
|
||||
Scalar::isBigIntType(*arrayType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
// Resizable ArrayBuffer and growable SharedArrayBuffer views need dynamic
|
||||
// length/out-of-bounds handling. This older MIR path assumes stable
|
||||
// typed-array length/data slots, so keep element accesses on IC/VM paths.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -3608,6 +3608,17 @@ CheckForTypedObjectWithDetachedStorage(JSContext* cx, MacroAssembler& masm, Labe
|
||||
masm.branch32(Assembler::NotEqual, AbsoluteAddress(address), Imm32(0), failure);
|
||||
}
|
||||
|
||||
void
|
||||
GuardResizableOrGrowableTypedArray(MacroAssembler& masm, Register obj, Register scratch,
|
||||
Label* failure)
|
||||
{
|
||||
masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
|
||||
masm.branchTest32(Assembler::NonZero,
|
||||
Address(scratch, ObjectElements::offsetOfFlags()),
|
||||
Imm32(ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER),
|
||||
failure);
|
||||
}
|
||||
|
||||
void
|
||||
LoadTypedThingData(MacroAssembler& masm, TypedThingLayout layout, Register obj, Register result)
|
||||
{
|
||||
|
||||
@@ -2302,6 +2302,10 @@ CheckDOMProxyExpandoDoesNotShadow(JSContext* cx, MacroAssembler& masm, Register
|
||||
void
|
||||
CheckForTypedObjectWithDetachedStorage(JSContext* cx, MacroAssembler& masm, Label* failure);
|
||||
|
||||
void
|
||||
GuardResizableOrGrowableTypedArray(MacroAssembler& masm, Register obj, Register scratch,
|
||||
Label* failure);
|
||||
|
||||
MOZ_MUST_USE bool
|
||||
DoCallNativeGetter(JSContext* cx, HandleFunction callee, HandleObject obj,
|
||||
MutableHandleValue result);
|
||||
|
||||
@@ -81,6 +81,10 @@ MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE, 0, JSEXN_TYPEERR, "reduce of empty array
|
||||
MSG_DEF(JSMSG_UNEXPECTED_TYPE, 2, JSEXN_TYPEERR, "{0} is {1}")
|
||||
MSG_DEF(JSMSG_MISSING_FUN_ARG, 2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}")
|
||||
MSG_DEF(JSMSG_NOT_NONNULL_OBJECT, 1, JSEXN_TYPEERR, "{0} is not a non-null object")
|
||||
MSG_DEF(JSMSG_NOT_WEAKREF_TARGET, 0, JSEXN_TYPEERR, "WeakRef target must be an object or a non-registered symbol")
|
||||
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_TARGET, 0, JSEXN_TYPEERR, "FinalizationRegistry target must be an object or a non-registered symbol")
|
||||
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_HELD_VALUE, 0, JSEXN_TYPEERR, "FinalizationRegistry held value must not be the target")
|
||||
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_TOKEN, 0, JSEXN_TYPEERR, "FinalizationRegistry unregister token must be an object or a non-registered symbol")
|
||||
MSG_DEF(JSMSG_SET_NON_OBJECT_RECEIVER, 1, JSEXN_TYPEERR, "can't assign to properties of {0}: not an object")
|
||||
MSG_DEF(JSMSG_INVALID_DESCRIPTOR, 0, JSEXN_TYPEERR, "property descriptors must not specify a value or be writable when a getter or setter has been specified")
|
||||
MSG_DEF(JSMSG_OBJECT_NOT_EXTENSIBLE, 1, JSEXN_TYPEERR, "{0}: Object is not extensible")
|
||||
@@ -548,6 +552,8 @@ MSG_DEF(JSMSG_TOO_LONG_ARRAY, 0, JSEXN_TYPEERR, "Too long array")
|
||||
|
||||
// Typed array
|
||||
MSG_DEF(JSMSG_BAD_INDEX, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
|
||||
MSG_DEF(JSMSG_ARRAYBUFFER_NOT_RESIZABLE, 0, JSEXN_TYPEERR, "ArrayBuffer is not resizable")
|
||||
MSG_DEF(JSMSG_ARRAYBUFFER_CANNOT_DETACH, 0, JSEXN_TYPEERR, "ArrayBuffer cannot be detached")
|
||||
MSG_DEF(JSMSG_NON_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected ArrayBuffer, but species constructor returned non-ArrayBuffer")
|
||||
MSG_DEF(JSMSG_SAME_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected different ArrayBuffer, but species constructor returned same ArrayBuffer")
|
||||
MSG_DEF(JSMSG_SHORT_ARRAY_BUFFER_RETURNED, 2, JSEXN_TYPEERR, "expected ArrayBuffer with at least {0} bytes, but species constructor returns ArrayBuffer with {1} bytes")
|
||||
@@ -555,12 +561,15 @@ MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS, 0, JSEXN_TYPEERR, "invalid arguments")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG,1, JSEXN_RANGEERR, "argument {0} must be >= 0")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_DETACHED, 0, JSEXN_TYPEERR, "attempting to access detached ArrayBuffer")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS, 0, JSEXN_RANGEERR, "attempting to construct out-of-bounds TypedArray on ArrayBuffer")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS, 0, JSEXN_TYPEERR, "attempting to access out-of-bounds TypedArray")
|
||||
MSG_DEF(JSMSG_DATA_VIEW_OUT_OF_BOUNDS, 0, JSEXN_TYPEERR, "attempting to access out-of-bounds DataView")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_CALL_OR_CONSTRUCT, 1, JSEXN_TYPEERR, "cannot directly {0} builtin %TypedArray%")
|
||||
MSG_DEF(JSMSG_NON_TYPED_ARRAY_RETURNED, 0, JSEXN_TYPEERR, "constructor didn't return TypedArray object")
|
||||
MSG_DEF(JSMSG_SHORT_TYPED_ARRAY_RETURNED, 2, JSEXN_TYPEERR, "expected TypedArray of at least length {0}, but constructor returned TypedArray of length {1}")
|
||||
|
||||
// Shared array buffer
|
||||
MSG_DEF(JSMSG_SHARED_ARRAY_BAD_LENGTH, 0, JSEXN_RANGEERR, "length argument out of range")
|
||||
MSG_DEF(JSMSG_SHARED_ARRAY_NOT_GROWABLE, 0, JSEXN_TYPEERR, "SharedArrayBuffer is not growable")
|
||||
MSG_DEF(JSMSG_NON_SHARED_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected SharedArrayBuffer, but species constructor returned non-SharedArrayBuffer")
|
||||
MSG_DEF(JSMSG_SAME_SHARED_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected different SharedArrayBuffer, but species constructor returned same SharedArrayBuffer")
|
||||
MSG_DEF(JSMSG_SHORT_SHARED_ARRAY_BUFFER_RETURNED, 2, JSEXN_TYPEERR, "expected SharedArrayBuffer with at least {0} bytes, but species constructor returns SharedArrayBuffer with {1} bytes")
|
||||
|
||||
@@ -21,6 +21,64 @@ struct MyHeap
|
||||
|
||||
BEGIN_TEST(testGCWeakRef)
|
||||
{
|
||||
CHECK(cx->options().weakRefs());
|
||||
cx->options().setWeakRefs(false);
|
||||
CHECK(cx->options().weakRefs());
|
||||
|
||||
JS::RootedValue v(cx);
|
||||
EXEC("var weakRefTarget = { x: 42 };\n"
|
||||
"var weakRef = new WeakRef(weakRefTarget);\n"
|
||||
"weakRefTarget = null;\n");
|
||||
|
||||
// The constructor keeps the target alive until the host clears kept
|
||||
// objects. This must remain true even after callers try the old disabled
|
||||
// option path above.
|
||||
JS_GC(cx);
|
||||
JS_GC(cx);
|
||||
EVAL("weakRef.deref()", &v);
|
||||
CHECK(v.isObject());
|
||||
|
||||
JS::ClearWeakRefKeptObjects(cx);
|
||||
v = JS::UndefinedValue();
|
||||
JS_GC(cx);
|
||||
JS_GC(cx);
|
||||
EVAL("weakRef.deref()", &v);
|
||||
CHECK(v.isUndefined());
|
||||
|
||||
EXEC("var weakRefSymbol = Symbol('weak-ref-symbol');\n"
|
||||
"var symbolRef = new WeakRef(weakRefSymbol);\n"
|
||||
"if (symbolRef.deref() !== weakRefSymbol)\n"
|
||||
" throw new Error('WeakRef must accept unique symbols');\n"
|
||||
"var registeredSymbolRejected = false;\n"
|
||||
"try {\n"
|
||||
" new WeakRef(Symbol.for('weak-ref-symbol'));\n"
|
||||
"} catch (e) {\n"
|
||||
" registeredSymbolRejected = e instanceof TypeError;\n"
|
||||
"}\n"
|
||||
"if (!registeredSymbolRejected)\n"
|
||||
" throw new Error('WeakRef must reject registered symbols');\n");
|
||||
|
||||
EXEC("var keptTarget = { y: 7 };\n"
|
||||
"var keptRef = new WeakRef(keptTarget);\n");
|
||||
JS::ClearWeakRefKeptObjects(cx);
|
||||
EXEC("var keptResult = keptRef.deref();\n"
|
||||
"if (keptResult !== keptTarget)\n"
|
||||
" throw new Error('WeakRef deref must return the target');\n"
|
||||
"keptTarget = null;\n"
|
||||
"keptResult = null;\n");
|
||||
|
||||
JS_GC(cx);
|
||||
JS_GC(cx);
|
||||
EVAL("keptRef.deref()", &v);
|
||||
CHECK(v.isObject());
|
||||
|
||||
JS::ClearWeakRefKeptObjects(cx);
|
||||
v = JS::UndefinedValue();
|
||||
JS_GC(cx);
|
||||
JS_GC(cx);
|
||||
EVAL("keptRef.deref()", &v);
|
||||
CHECK(v.isUndefined());
|
||||
|
||||
// Create an object and add a property to it so that we can read the
|
||||
// property back later to verify that object internals are not garbage.
|
||||
JS::RootedObject obj(cx, JS_NewPlainObject(cx));
|
||||
@@ -38,7 +96,6 @@ BEGIN_TEST(testGCWeakRef)
|
||||
// references.
|
||||
CHECK(heap.get().weak.unbarrieredGet() != nullptr);
|
||||
obj = heap.get().weak;
|
||||
JS::RootedValue v(cx);
|
||||
CHECK(JS_GetProperty(cx, obj, "x", &v));
|
||||
CHECK(v.isInt32());
|
||||
CHECK(v.toInt32() == 42);
|
||||
@@ -61,4 +118,3 @@ BEGIN_TEST(testGCWeakRef)
|
||||
return true;
|
||||
}
|
||||
END_TEST(testGCWeakRef)
|
||||
|
||||
|
||||
@@ -627,6 +627,15 @@ JS::InitSelfHostedCode(JSContext* cx)
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS::ClearWeakRefKeptObjects(JSContext* cx)
|
||||
{
|
||||
MOZ_ASSERT(cx);
|
||||
MOZ_ASSERT(!cx->runtime()->isHeapBusy());
|
||||
|
||||
cx->runtime()->clearWeakRefKeptObjects();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(const char*)
|
||||
JS_GetImplementationVersion(void)
|
||||
{
|
||||
@@ -1478,6 +1487,36 @@ JS_SetGCParametersBasedOnAvailableMemory(JSContext* cx, uint32_t availMem)
|
||||
JS_SetGCParameter(cx, config[i].key, config[i].value);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS_SetIdleGCEnabled(JSContext* cx, bool enabled)
|
||||
{
|
||||
cx->gc.idleGCMgr().setIdleGCEnabled(enabled);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(bool)
|
||||
JS_IsIdleGCEnabled(JSContext* cx)
|
||||
{
|
||||
return cx->gc.idleGCMgr().isIdleGCEnabled();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS_SetIdleGCThreshold(JSContext* cx, uint64_t milliseconds)
|
||||
{
|
||||
cx->gc.idleGCMgr().setIdleThresholdMs(milliseconds);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(uint64_t)
|
||||
JS_GetIdleGCThreshold(JSContext* cx)
|
||||
{
|
||||
return cx->gc.idleGCMgr().idleThresholdMs();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(uint64_t)
|
||||
JS_GetIdleTimeSinceLastExecution(JSContext* cx)
|
||||
{
|
||||
return cx->gc.idleGCMgr().idleTimeSinceLastExecution();
|
||||
}
|
||||
|
||||
|
||||
JS_PUBLIC_API(JSString*)
|
||||
JS_NewExternalString(JSContext* cx, const char16_t* chars, size_t length,
|
||||
|
||||
+44
-5
@@ -999,7 +999,7 @@ class JS_PUBLIC_API(ContextOptions) {
|
||||
strictMode_(false),
|
||||
extraWarnings_(false),
|
||||
arrayProtoValues_(true),
|
||||
weakRefs_(false)
|
||||
streams_(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1139,13 +1139,12 @@ class JS_PUBLIC_API(ContextOptions) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool weakRefs() const { return weakRefs_; }
|
||||
bool weakRefs() const { return true; }
|
||||
ContextOptions& setWeakRefs(bool flag) {
|
||||
weakRefs_ = flag;
|
||||
(void) flag;
|
||||
return *this;
|
||||
}
|
||||
ContextOptions& toggleWeakRefs() {
|
||||
weakRefs_ = !weakRefs_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -1166,7 +1165,6 @@ class JS_PUBLIC_API(ContextOptions) {
|
||||
bool extraWarnings_ : 1;
|
||||
bool arrayProtoValues_ : 1;
|
||||
bool streams_ : 1;
|
||||
bool weakRefs_ : 1;
|
||||
};
|
||||
|
||||
JS_PUBLIC_API(ContextOptions&)
|
||||
@@ -1187,6 +1185,13 @@ InitSelfHostedCode(JSContext* cx);
|
||||
JS_PUBLIC_API(void)
|
||||
AssertObjectBelongsToCurrentThread(JSObject* obj);
|
||||
|
||||
/**
|
||||
* Clear objects and symbols that WeakRef.prototype.deref kept alive for the
|
||||
* current synchronous JavaScript execution.
|
||||
*/
|
||||
JS_PUBLIC_API(void)
|
||||
ClearWeakRefKeptObjects(JSContext* cx);
|
||||
|
||||
} /* namespace JS */
|
||||
|
||||
extern JS_PUBLIC_API(const char*)
|
||||
@@ -1719,6 +1724,40 @@ JS_GetGCParameter(JSContext* cx, JSGCParamKey key);
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_SetGCParametersBasedOnAvailableMemory(JSContext* cx, uint32_t availMem);
|
||||
|
||||
/*
|
||||
* Idle-time garbage collection control.
|
||||
* These functions allow control over when GC occurs - specifically, whether
|
||||
* GC should only run when the browser/application is not doing active JS work.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enable or disable idle-time-only GC mode.
|
||||
* When enabled, GC is deferred until the JS engine has been idle for the
|
||||
* configured threshold period (default: 100ms).
|
||||
*/
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_SetIdleGCEnabled(JSContext* cx, bool enabled);
|
||||
|
||||
extern JS_PUBLIC_API(bool)
|
||||
JS_IsIdleGCEnabled(JSContext* cx);
|
||||
|
||||
/**
|
||||
* Set the minimum idle time (in milliseconds) before GC is permitted.
|
||||
* Use this to configure how long the JS engine must be inactive before
|
||||
* pending GC work can proceed.
|
||||
*/
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_SetIdleGCThreshold(JSContext* cx, uint64_t milliseconds);
|
||||
|
||||
extern JS_PUBLIC_API(uint64_t)
|
||||
JS_GetIdleGCThreshold(JSContext* cx);
|
||||
|
||||
/**
|
||||
* Get the current idle time since the last JS execution.
|
||||
*/
|
||||
extern JS_PUBLIC_API(uint64_t)
|
||||
JS_GetIdleTimeSinceLastExecution(JSContext* cx);
|
||||
|
||||
/**
|
||||
* Create a new JSString whose chars member refers to external memory, i.e.,
|
||||
* memory requiring application-specific finalization.
|
||||
|
||||
@@ -1748,6 +1748,9 @@ JS_IsFloat64Array(JSObject* obj);
|
||||
extern JS_FRIEND_API(bool)
|
||||
JS_GetTypedArraySharedness(JSObject* obj);
|
||||
|
||||
extern JS_FRIEND_API(uint32_t)
|
||||
JS_GetTypedArrayLength(JSObject* obj);
|
||||
|
||||
/*
|
||||
* Test for specific typed array types (ArrayBufferView subtypes) and return
|
||||
* the unwrapped object if so, else nullptr. Never throws.
|
||||
@@ -1814,8 +1817,7 @@ inline void \
|
||||
Get ## Type ## ArrayLengthAndData(JSObject* obj, uint32_t* length, bool* isSharedMemory, type** data) \
|
||||
{ \
|
||||
MOZ_ASSERT(GetObjectClass(obj) == detail::Type ## ArrayClassPtr); \
|
||||
const JS::Value& lenSlot = GetReservedSlot(obj, detail::TypedArrayLengthSlot); \
|
||||
*length = mozilla::AssertedCast<uint32_t>(lenSlot.toInt32()); \
|
||||
*length = JS_GetTypedArrayLength(obj); \
|
||||
*isSharedMemory = JS_GetTypedArraySharedness(obj); \
|
||||
*data = static_cast<type*>(GetObjectPrivate(obj)); \
|
||||
}
|
||||
|
||||
+329
-57
@@ -211,9 +211,11 @@
|
||||
# include "jswin.h"
|
||||
#endif
|
||||
|
||||
#include "builtin/FinalizationRegistryObject.h"
|
||||
#include "gc/FindSCCs.h"
|
||||
#include "gc/GCInternals.h"
|
||||
#include "gc/GCTrace.h"
|
||||
#include "gc/IdleGC.h"
|
||||
#include "gc/Marking.h"
|
||||
#include "gc/Memory.h"
|
||||
#include "gc/Policy.h"
|
||||
@@ -1570,7 +1572,7 @@ ArenaLists::prepareForIncrementalGC()
|
||||
{
|
||||
purge();
|
||||
for (auto i : AllAllocKinds()) {
|
||||
arenaLists[i].moveCursorToEnd();
|
||||
arenaLists[i].moveCursorToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2117,7 +2119,7 @@ ArenasToUpdate::next(AutoLockHelperThreadState& lock)
|
||||
// Find the next arena to update.
|
||||
//
|
||||
// This iterates through the GC thing kinds filtered by shouldProcessKind(),
|
||||
// and then through thea arenas of that kind. All state is held in the
|
||||
// and then through the arenas of that kind. All state is held in the
|
||||
// object and we just return when we find an arena.
|
||||
|
||||
for (; kind < AllocKind::LIMIT; kind = nextAllocKind(kind)) {
|
||||
@@ -2207,21 +2209,12 @@ UpdatePointersTask::run()
|
||||
} // namespace gc
|
||||
} // namespace js
|
||||
|
||||
static const size_t MinCellUpdateBackgroundTasks = 2;
|
||||
static const size_t MinCellUpdateBackgroundTasks = 1;
|
||||
static const size_t MaxCellUpdateBackgroundTasks = 8;
|
||||
|
||||
static size_t
|
||||
CellUpdateBackgroundTaskCount()
|
||||
{
|
||||
if (!CanUseExtraThreads())
|
||||
return 0;
|
||||
|
||||
size_t targetTaskCount = HelperThreadState().cpuCount / 2;
|
||||
return Min(Max(targetTaskCount, MinCellUpdateBackgroundTasks), MaxCellUpdateBackgroundTasks);
|
||||
}
|
||||
|
||||
static bool
|
||||
CanUpdateKindInBackground(AllocKind kind) {
|
||||
CanUpdateKindInBackground(AllocKind kind)
|
||||
{
|
||||
// We try to update as many GC things in parallel as we can, but there are
|
||||
// kinds for which this might not be safe:
|
||||
// - we assume JSObjects that are foreground finalized are not safe to
|
||||
@@ -2233,6 +2226,34 @@ CanUpdateKindInBackground(AllocKind kind) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t
|
||||
CountBackgroundUpdateArenas(Zone* zone, AllocKinds kinds)
|
||||
{
|
||||
size_t arenaCount = 0;
|
||||
for (AllocKind kind : kinds) {
|
||||
MOZ_ASSERT(CanUpdateKindInBackground(kind));
|
||||
for (Arena* arena = zone->arenas.getFirstArena(kind); arena; arena = arena->next)
|
||||
arenaCount++;
|
||||
}
|
||||
return arenaCount;
|
||||
}
|
||||
|
||||
static size_t
|
||||
CellUpdateBackgroundTaskCount(Zone* zone, AllocKinds kinds)
|
||||
{
|
||||
if (!CanUseExtraThreads() || kinds.isEmpty())
|
||||
return 0;
|
||||
|
||||
size_t arenaCount = CountBackgroundUpdateArenas(zone, kinds);
|
||||
if (arenaCount < UpdatePointersTask::MaxArenasToProcess * 2)
|
||||
return 0;
|
||||
|
||||
size_t targetTaskCount = HelperThreadState().cpuCount / 2;
|
||||
size_t workTaskCount = arenaCount / UpdatePointersTask::MaxArenasToProcess;
|
||||
targetTaskCount = Min(targetTaskCount, workTaskCount);
|
||||
return Min(Max(targetTaskCount, MinCellUpdateBackgroundTasks), MaxCellUpdateBackgroundTasks);
|
||||
}
|
||||
|
||||
static AllocKinds
|
||||
ForegroundUpdateKinds(AllocKinds kinds)
|
||||
{
|
||||
@@ -2253,10 +2274,15 @@ GCRuntime::updateTypeDescrObjects(MovingTracer* trc, Zone* zone)
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds, size_t bgTaskCount)
|
||||
GCRuntime::updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds)
|
||||
{
|
||||
AllocKinds fgKinds = bgTaskCount == 0 ? kinds : ForegroundUpdateKinds(kinds);
|
||||
MOZ_ASSERT(trc);
|
||||
|
||||
AllocKinds fgKinds = ForegroundUpdateKinds(kinds);
|
||||
AllocKinds bgKinds = kinds - fgKinds;
|
||||
size_t bgTaskCount = CellUpdateBackgroundTaskCount(zone, bgKinds);
|
||||
if (bgTaskCount == 0)
|
||||
fgKinds = kinds;
|
||||
|
||||
ArenasToUpdate fgArenas(zone, fgKinds);
|
||||
ArenasToUpdate bgArenas(zone, bgKinds);
|
||||
@@ -2351,15 +2377,13 @@ GCRuntime::updateAllCellPointers(MovingTracer* trc, Zone* zone)
|
||||
{
|
||||
AutoDisableProxyCheck noProxyCheck(rt); // These checks assert when run in parallel.
|
||||
|
||||
size_t bgTaskCount = CellUpdateBackgroundTaskCount();
|
||||
|
||||
updateCellPointers(trc, zone, UpdatePhaseMisc, bgTaskCount);
|
||||
updateCellPointers(trc, zone, UpdatePhaseMisc);
|
||||
|
||||
// Update TypeDescrs before all other objects as typed objects access these
|
||||
// objects when we trace them.
|
||||
updateTypeDescrObjects(trc, zone);
|
||||
|
||||
updateCellPointers(trc, zone, UpdatePhaseObjects, bgTaskCount);
|
||||
updateCellPointers(trc, zone, UpdatePhaseObjects);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2665,6 +2689,17 @@ ArenaLists::backgroundFinalize(FreeOp* fop, Arena* listHead, Arena** empty)
|
||||
lists->backgroundFinalizeState[thingKind] = BFS_DONE;
|
||||
}
|
||||
|
||||
void
|
||||
ArenaLists::backgroundFinalizePhase(FreeOp* fop, const FinalizePhase& phase, Arena** empty)
|
||||
{
|
||||
for (auto kind : phase.kinds) {
|
||||
Arena* arenas = arenaListsToSweep[kind];
|
||||
MOZ_RELEASE_ASSERT(uintptr_t(arenas) != uintptr_t(-1));
|
||||
if (arenas)
|
||||
backgroundFinalize(fop, arenas, empty);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArenaLists::queueForegroundObjectsForSweep(FreeOp* fop)
|
||||
{
|
||||
@@ -3003,6 +3038,138 @@ js::gc::BackgroundDecommitTask::run()
|
||||
}
|
||||
}
|
||||
|
||||
class BackgroundFinalizeTask : public GCParallelTaskHelper<BackgroundFinalizeTask>
|
||||
{
|
||||
Zone* zone_;
|
||||
const FinalizePhase* phase_;
|
||||
Arena* emptyArenas_;
|
||||
|
||||
BackgroundFinalizeTask(const BackgroundFinalizeTask&) = delete;
|
||||
|
||||
public:
|
||||
BackgroundFinalizeTask(Zone* zone, const FinalizePhase* phase)
|
||||
: zone_(zone),
|
||||
phase_(phase),
|
||||
emptyArenas_(nullptr)
|
||||
{}
|
||||
|
||||
BackgroundFinalizeTask(BackgroundFinalizeTask&& other)
|
||||
: GCParallelTaskHelper(mozilla::Move(other)),
|
||||
zone_(other.zone_),
|
||||
phase_(other.phase_),
|
||||
emptyArenas_(other.emptyArenas_)
|
||||
{
|
||||
other.emptyArenas_ = nullptr;
|
||||
}
|
||||
|
||||
void run() {
|
||||
AutoSetThreadIsSweeping threadIsSweeping;
|
||||
finalize();
|
||||
}
|
||||
|
||||
void runAlreadySweeping() {
|
||||
#ifdef DEBUG
|
||||
MOZ_ASSERT(CurrentThreadIsGCSweeping());
|
||||
#endif
|
||||
finalize();
|
||||
}
|
||||
|
||||
private:
|
||||
void finalize() {
|
||||
FreeOp fop(nullptr);
|
||||
zone_->arenas.backgroundFinalizePhase(&fop, *phase_, &emptyArenas_);
|
||||
}
|
||||
|
||||
public:
|
||||
Arena* takeEmptyArenas() {
|
||||
Arena* empty = emptyArenas_;
|
||||
emptyArenas_ = nullptr;
|
||||
return empty;
|
||||
}
|
||||
};
|
||||
|
||||
using BackgroundFinalizeTaskVector =
|
||||
Vector<BackgroundFinalizeTask, 0, SystemAllocPolicy>;
|
||||
|
||||
static size_t
|
||||
IdleHelperThreadCount(const AutoLockHelperThreadState&)
|
||||
{
|
||||
if (!HelperThreadState().threads)
|
||||
return 0;
|
||||
|
||||
size_t idle = 0;
|
||||
for (const auto& thread : *HelperThreadState().threads) {
|
||||
if (thread.idle())
|
||||
idle++;
|
||||
}
|
||||
return idle;
|
||||
}
|
||||
|
||||
static void
|
||||
PrependArenaList(Arena** head, Arena* arenas)
|
||||
{
|
||||
if (!arenas)
|
||||
return;
|
||||
|
||||
Arena* tail = arenas;
|
||||
while (tail->next)
|
||||
tail = tail->next;
|
||||
tail->next = *head;
|
||||
*head = arenas;
|
||||
}
|
||||
|
||||
bool
|
||||
GCRuntime::sweepBackgroundFinalizePhaseInParallel(ZoneList& zones, const FinalizePhase& phase,
|
||||
Arena** emptyArenas)
|
||||
{
|
||||
if (!CanUseExtraThreads())
|
||||
return false;
|
||||
|
||||
size_t zoneCount = 0;
|
||||
for (Zone* zone = zones.front(); zone; zone = zone->nextZone())
|
||||
zoneCount++;
|
||||
|
||||
if (zoneCount < 2)
|
||||
return false;
|
||||
|
||||
BackgroundFinalizeTaskVector tasks;
|
||||
if (!tasks.reserve(zoneCount))
|
||||
return false;
|
||||
|
||||
for (Zone* zone = zones.front(); zone; zone = zone->nextZone())
|
||||
tasks.infallibleEmplaceBack(zone, &phase);
|
||||
|
||||
size_t tasksStarted = 0;
|
||||
|
||||
{
|
||||
AutoLockHelperThreadState helperLock;
|
||||
|
||||
// sweepBackgroundThings() itself runs as a GC helper task. Do not queue
|
||||
// nested GC parallel tasks unless at least one other helper is idle.
|
||||
if (IdleHelperThreadCount(helperLock) == 0)
|
||||
return false;
|
||||
|
||||
for (; tasksStarted < tasks.length(); tasksStarted++) {
|
||||
if (!tasks[tasksStarted].startWithLockHeld(helperLock))
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
AutoUnlockHelperThreadState unlock(helperLock);
|
||||
for (size_t i = tasksStarted; i < tasks.length(); i++)
|
||||
tasks[i].runAlreadySweeping();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < tasksStarted; i++)
|
||||
tasks[i].joinWithLockHeld(helperLock);
|
||||
}
|
||||
|
||||
for (auto& task : tasks)
|
||||
PrependArenaList(emptyArenas, task.takeEmptyArenas());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks)
|
||||
{
|
||||
@@ -3015,14 +3182,12 @@ GCRuntime::sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks)
|
||||
Arena* emptyArenas = nullptr;
|
||||
FreeOp fop(nullptr);
|
||||
for (unsigned phase = 0 ; phase < ArrayLength(BackgroundFinalizePhases) ; ++phase) {
|
||||
for (Zone* zone = zones.front(); zone; zone = zone->nextZone()) {
|
||||
for (auto kind : BackgroundFinalizePhases[phase].kinds) {
|
||||
Arena* arenas = zone->arenas.arenaListsToSweep[kind];
|
||||
MOZ_RELEASE_ASSERT(uintptr_t(arenas) != uintptr_t(-1));
|
||||
if (arenas)
|
||||
ArenaLists::backgroundFinalize(&fop, arenas, &emptyArenas);
|
||||
}
|
||||
}
|
||||
const FinalizePhase& finalizePhase = BackgroundFinalizePhases[phase];
|
||||
if (sweepBackgroundFinalizePhaseInParallel(zones, finalizePhase, &emptyArenas))
|
||||
continue;
|
||||
|
||||
for (Zone* zone = zones.front(); zone; zone = zone->nextZone())
|
||||
zone->arenas.backgroundFinalizePhase(&fop, finalizePhase, &emptyArenas);
|
||||
}
|
||||
|
||||
AutoLockGC lock(rt);
|
||||
@@ -3581,14 +3746,14 @@ ShouldCollectZone(Zone* zone, JS::gcreason::Reason reason)
|
||||
// Normally we collect all scheduled zones.
|
||||
if (reason != JS::gcreason::COMPARTMENT_REVIVED)
|
||||
return zone->isGCScheduled();
|
||||
|
||||
|
||||
// If we are repeating a GC because we noticed dead compartments haven't
|
||||
// been collected, then only collect zones containing those compartments.
|
||||
for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
|
||||
if (comp->scheduledForDestruction)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3654,7 +3819,7 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
|
||||
* on. If the value of keepAtoms() changes between GC slices, then we'll
|
||||
* cancel the incremental GC. See IsIncrementalGCSafe.
|
||||
*/
|
||||
|
||||
|
||||
if (isFull && !rt->keepAtoms()) {
|
||||
Zone* atomsZone = rt->atomsCompartment(lock)->zone();
|
||||
if (atomsZone->isGCScheduled()) {
|
||||
@@ -3670,7 +3835,7 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
|
||||
|
||||
/*
|
||||
* Ensure that after the start of a collection we don't allocate into any
|
||||
* existing arenas, as this can cause unreachable things to be marked.
|
||||
* existing arenas, as this can cause unreachable things to be marked.
|
||||
*/
|
||||
if (isIncremental) {
|
||||
for (GCZonesIter zone(rt); !zone.done(); zone.next())
|
||||
@@ -3762,7 +3927,7 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
|
||||
bufferGrayRoots();
|
||||
markCompartments();
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3801,9 +3966,9 @@ GCRuntime::markCompartments()
|
||||
*/
|
||||
|
||||
/* Propagate the maybeAlive flag via cross-compartment edges. */
|
||||
|
||||
|
||||
Vector<JSCompartment*, 0, js::SystemAllocPolicy> workList;
|
||||
|
||||
|
||||
for (CompartmentsIter comp(rt, SkipAtoms); !comp.done(); comp.next()) {
|
||||
if (comp->maybeAlive) {
|
||||
if (!workList.append(comp))
|
||||
@@ -3824,9 +3989,9 @@ GCRuntime::markCompartments()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Set scheduleForDestruction based on maybeAlive. */
|
||||
|
||||
|
||||
for (GCCompartmentsIter comp(rt); !comp.done(); comp.next()) {
|
||||
MOZ_ASSERT(!comp->scheduledForDestruction);
|
||||
if (!comp->maybeAlive && !rt->isAtomsCompartment(comp))
|
||||
@@ -3865,6 +4030,8 @@ GCRuntime::markWeakReferences(gcstats::Phase phase)
|
||||
}
|
||||
MOZ_ASSERT(marker.isDrained());
|
||||
|
||||
traceFinalizationRegistryWeakRefs<ZoneIterT>();
|
||||
|
||||
marker.leaveWeakMarkingMode();
|
||||
}
|
||||
|
||||
@@ -3874,6 +4041,33 @@ GCRuntime::markWeakReferencesInCurrentGroup(gcstats::Phase phase)
|
||||
markWeakReferences<GCZoneGroupIter>(phase);
|
||||
}
|
||||
|
||||
template <class ZoneIterT>
|
||||
void
|
||||
GCRuntime::traceFinalizationRegistryWeakRefs()
|
||||
{
|
||||
for (ZoneIterT zone(rt); !zone.done(); zone.next()) {
|
||||
for (size_t i = 0; i < zone->finalizationRegistries.length(); i++) {
|
||||
WeakRef<JSObject*>& registry = zone->finalizationRegistries[i];
|
||||
if (registry.unbarrieredGet())
|
||||
TraceWeakEdge(&marker, ®istry, "FinalizationRegistry registry");
|
||||
}
|
||||
}
|
||||
|
||||
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
|
||||
for (size_t i = 0; i < zone->finalizationRegistries.length(); i++) {
|
||||
JSObject* registry = zone->finalizationRegistries[i].unbarrieredGet();
|
||||
if (registry && registry->is<FinalizationRegistryObject>())
|
||||
registry->as<FinalizationRegistryObject>().traceWeakEdgesForCollectedZones(&marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::traceFinalizationRegistryWeakRefsInCurrentGroup()
|
||||
{
|
||||
traceFinalizationRegistryWeakRefs<GCZoneGroupIter>();
|
||||
}
|
||||
|
||||
template <class ZoneIterT, class CompartmentIterT>
|
||||
void
|
||||
GCRuntime::markGrayReferences(gcstats::Phase phase)
|
||||
@@ -4429,7 +4623,9 @@ MAKE_GC_SWEEP_TASK(SweepBaseShapesTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepInitialShapesTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepObjectGroupsTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepRegExpsTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepMiscTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepSavedStacksTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepSelfHostingScriptSourceTask);
|
||||
MAKE_GC_SWEEP_TASK(SweepNativeIteratorsTask);
|
||||
#undef MAKE_GC_SWEEP_TASK
|
||||
|
||||
/* virtual */ void
|
||||
@@ -4462,15 +4658,27 @@ SweepRegExpsTask::run()
|
||||
}
|
||||
|
||||
/* virtual */ void
|
||||
SweepMiscTask::run()
|
||||
SweepSavedStacksTask::run()
|
||||
{
|
||||
for (GCCompartmentGroupIter c(runtime); !c.done(); c.next()) {
|
||||
c->sweepSavedStacks();
|
||||
c->sweepSelfHostingScriptSource();
|
||||
c->sweepNativeIterators();
|
||||
}
|
||||
}
|
||||
|
||||
/* virtual */ void
|
||||
SweepSelfHostingScriptSourceTask::run()
|
||||
{
|
||||
for (GCCompartmentGroupIter c(runtime); !c.done(); c.next())
|
||||
c->sweepSelfHostingScriptSource();
|
||||
}
|
||||
|
||||
/* virtual */ void
|
||||
SweepNativeIteratorsTask::run()
|
||||
{
|
||||
for (GCCompartmentGroupIter c(runtime); !c.done(); c.next())
|
||||
c->sweepNativeIterators();
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::startTask(GCParallelTask& task, gcstats::Phase phase,
|
||||
AutoLockHelperThreadState& locked)
|
||||
@@ -4509,7 +4717,7 @@ PrepareWeakCacheTasks(JSRuntime* rt)
|
||||
WeakCacheTaskVector out;
|
||||
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
|
||||
for (JS::WeakCache<void*>* cache : zone->weakCaches_) {
|
||||
if (!out.append(SweepWeakCacheTask(rt, *cache))) {
|
||||
if (!out.emplaceBack(rt, *cache)) {
|
||||
SweepWeakCachesFromMainThread(rt);
|
||||
return WeakCacheTaskVector();
|
||||
}
|
||||
@@ -4551,7 +4759,9 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
SweepCCWrappersTask sweepCCWrappersTask(rt);
|
||||
SweepObjectGroupsTask sweepObjectGroupsTask(rt);
|
||||
SweepRegExpsTask sweepRegExpsTask(rt);
|
||||
SweepMiscTask sweepMiscTask(rt);
|
||||
SweepSavedStacksTask sweepSavedStacksTask(rt);
|
||||
SweepSelfHostingScriptSourceTask sweepSelfHostingScriptSourceTask(rt);
|
||||
SweepNativeIteratorsTask sweepNativeIteratorsTask(rt);
|
||||
WeakCacheTaskVector sweepCacheTasks = PrepareWeakCacheTasks(rt);
|
||||
|
||||
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
|
||||
@@ -4569,6 +4779,8 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
oomUnsafe.crash("clearing weak keys in beginSweepingZoneGroup()");
|
||||
}
|
||||
|
||||
sweepFinalizationRegistries();
|
||||
|
||||
{
|
||||
gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_START);
|
||||
callFinalizeCallbacks(&fop, JSFINALIZE_GROUP_START);
|
||||
@@ -4599,7 +4811,9 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
startTask(sweepCCWrappersTask, gcstats::PHASE_SWEEP_CC_WRAPPER, helperLock);
|
||||
startTask(sweepObjectGroupsTask, gcstats::PHASE_SWEEP_TYPE_OBJECT, helperLock);
|
||||
startTask(sweepRegExpsTask, gcstats::PHASE_SWEEP_REGEXP, helperLock);
|
||||
startTask(sweepMiscTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
startTask(sweepSavedStacksTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
startTask(sweepSelfHostingScriptSourceTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
startTask(sweepNativeIteratorsTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
for (auto& task : sweepCacheTasks)
|
||||
startTask(task, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
}
|
||||
@@ -4618,7 +4832,6 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
c->sweepJitCompartment(&fop);
|
||||
c->sweepTemplateObjects();
|
||||
}
|
||||
|
||||
for (GCZoneGroupIter zone(rt); !zone.done(); zone.next())
|
||||
zone->sweepWeakMaps();
|
||||
|
||||
@@ -4678,7 +4891,9 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
joinTask(sweepCCWrappersTask, gcstats::PHASE_SWEEP_CC_WRAPPER, helperLock);
|
||||
joinTask(sweepObjectGroupsTask, gcstats::PHASE_SWEEP_TYPE_OBJECT, helperLock);
|
||||
joinTask(sweepRegExpsTask, gcstats::PHASE_SWEEP_REGEXP, helperLock);
|
||||
joinTask(sweepMiscTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
joinTask(sweepSavedStacksTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
joinTask(sweepSelfHostingScriptSourceTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
joinTask(sweepNativeIteratorsTask, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
for (auto& task : sweepCacheTasks)
|
||||
joinTask(task, gcstats::PHASE_SWEEP_MISC, helperLock);
|
||||
}
|
||||
@@ -4721,6 +4936,23 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::sweepFinalizationRegistries()
|
||||
{
|
||||
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
|
||||
for (size_t i = 0; i < zone->finalizationRegistries.length();) {
|
||||
JSObject* obj = zone->finalizationRegistries[i].unbarrieredGet();
|
||||
if (!obj || !obj->is<FinalizationRegistryObject>()) {
|
||||
zone->finalizationRegistries.erase(zone->finalizationRegistries.begin() + i);
|
||||
continue;
|
||||
}
|
||||
|
||||
obj->as<FinalizationRegistryObject>().sweepAfterGC(rt);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::endSweepingZoneGroup()
|
||||
{
|
||||
@@ -5005,7 +5237,7 @@ GCRuntime::performSweepActions(SliceBudget& budget, AutoLockForExclusiveAccess&
|
||||
|
||||
// Reset phase index.
|
||||
sweepPhaseIndex = 0;
|
||||
|
||||
|
||||
endSweepingZoneGroup();
|
||||
getNextZoneGroup();
|
||||
if (!currentZoneGroup)
|
||||
@@ -5107,6 +5339,7 @@ GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
|
||||
gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT);
|
||||
|
||||
Arena* relocatedArenas = nullptr;
|
||||
|
||||
while (!zonesToMaybeCompact.isEmpty()) {
|
||||
// TODO: JSScripts can move. If the sampler interrupts the GC in the
|
||||
// middle of relocating an arena, invalid JSScript pointers may be
|
||||
@@ -5558,7 +5791,7 @@ gc::AbortReason
|
||||
gc::IsIncrementalGCUnsafe(JSRuntime* rt)
|
||||
{
|
||||
MOZ_ASSERT(!rt->mainThread.suppressGC);
|
||||
|
||||
|
||||
if (rt->keepAtoms())
|
||||
return gc::AbortReason::KeepAtomsSet;
|
||||
|
||||
@@ -5579,7 +5812,7 @@ GCRuntime::budgetIncrementalGC(JS::gcreason::Reason reason, SliceBudget& budget,
|
||||
else if (mode != JSGC_MODE_INCREMENTAL)
|
||||
unsafeReason = gc::AbortReason::ModeChange;
|
||||
}
|
||||
|
||||
|
||||
if (unsafeReason != AbortReason::None) {
|
||||
resetIncrementalGC(unsafeReason, lock);
|
||||
budget.makeUnlimited();
|
||||
@@ -5587,7 +5820,7 @@ GCRuntime::budgetIncrementalGC(JS::gcreason::Reason reason, SliceBudget& budget,
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (isTooMuchMalloc()) {
|
||||
budget.makeUnlimited();
|
||||
@@ -5827,6 +6060,12 @@ GCRuntime::checkCanCallAPI()
|
||||
bool
|
||||
GCRuntime::checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason)
|
||||
{
|
||||
auto isTabCloseReason = [](JS::gcreason::Reason r) {
|
||||
return r == JS::gcreason::PAGE_HIDE ||
|
||||
r == JS::gcreason::POST_COMPARTMENT ||
|
||||
r == JS::gcreason::NSJSCONTEXT_DESTROY;
|
||||
};
|
||||
|
||||
if (rt->mainThread.suppressGC)
|
||||
return false;
|
||||
|
||||
@@ -5835,6 +6074,12 @@ GCRuntime::checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason)
|
||||
if (rt->isBeingDestroyed() && !IsShutdownGC(reason))
|
||||
return false;
|
||||
|
||||
// Allow fast tab-close cleanup even when the runtime is otherwise busy.
|
||||
if (!isTabCloseReason(reason) &&
|
||||
!IdleGCManager::shouldBypassIdleCheck(reason) &&
|
||||
!idleGC.isIdleEnough())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5842,15 +6087,15 @@ bool
|
||||
GCRuntime::shouldRepeatForDeadZone(JS::gcreason::Reason reason)
|
||||
{
|
||||
MOZ_ASSERT_IF(reason == JS::gcreason::COMPARTMENT_REVIVED, !isIncremental);
|
||||
|
||||
|
||||
if (!isIncremental || isIncrementalGCInProgress())
|
||||
return false;
|
||||
|
||||
|
||||
for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
|
||||
if (c->scheduledForDestruction)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5872,13 +6117,13 @@ GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::R
|
||||
do {
|
||||
poked = false;
|
||||
bool wasReset = gcCycle(nonincrementalByAPI, budget, reason);
|
||||
|
||||
|
||||
bool repeatForDeadZone = false;
|
||||
if (poked && cleanUpEverything) {
|
||||
/* Need to re-schedule all zones for GC. */
|
||||
JS::PrepareForFullGC(rt->contextFromMainThread());
|
||||
|
||||
|
||||
|
||||
} else if (shouldRepeatForDeadZone(reason) && !wasReset) {
|
||||
/*
|
||||
* This code makes an extra effort to collect compartments that we
|
||||
@@ -5888,7 +6133,7 @@ GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::R
|
||||
repeatForDeadZone = true;
|
||||
reason = JS::gcreason::COMPARTMENT_REVIVED;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* If we reset an existing GC, we need to start a new one. Also, we
|
||||
@@ -5899,6 +6144,13 @@ GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::R
|
||||
repeat = (poked && cleanUpEverything) || wasReset || repeatForDeadZone;
|
||||
} while (repeat);
|
||||
|
||||
if (rt->isBeingDestroyed()) {
|
||||
rt->clearFinalizationRegistryCleanupJobs();
|
||||
} else if (!rt->drainFinalizationRegistryCleanupJobs(rt->contextFromMainThread())) {
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
oomUnsafe.crash("draining FinalizationRegistry cleanup jobs");
|
||||
}
|
||||
|
||||
if (reason == JS::gcreason::COMPARTMENT_REVIVED)
|
||||
maybeDoCycleCollection();
|
||||
}
|
||||
@@ -5912,8 +6164,16 @@ js::AutoEnqueuePendingParseTasksAfterGC::~AutoEnqueuePendingParseTasksAfterGC()
|
||||
SliceBudget
|
||||
GCRuntime::defaultBudget(JS::gcreason::Reason reason, int64_t millis)
|
||||
{
|
||||
auto isTabCloseReason = [](JS::gcreason::Reason r) {
|
||||
return r == JS::gcreason::PAGE_HIDE ||
|
||||
r == JS::gcreason::POST_COMPARTMENT ||
|
||||
r == JS::gcreason::NSJSCONTEXT_DESTROY;
|
||||
};
|
||||
|
||||
if (millis == 0) {
|
||||
if (reason == JS::gcreason::ALLOC_TRIGGER)
|
||||
if (isTabCloseReason(reason))
|
||||
millis = 3;
|
||||
else if (reason == JS::gcreason::ALLOC_TRIGGER)
|
||||
millis = defaultSliceBudget();
|
||||
else if (schedulingState.inHighFrequencyGCMode() && tunables.isDynamicMarkSliceEnabled())
|
||||
millis = defaultSliceBudget() * IGC_MARK_SLICE_MULTIPLIER;
|
||||
@@ -6001,6 +6261,18 @@ GCRuntime::notifyDidPaint()
|
||||
interFrameGC = false;
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::notifyJSExecutionStart()
|
||||
{
|
||||
idleGC.notifyJSExecutionStart();
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::notifyJSExecutionEnd()
|
||||
{
|
||||
idleGC.notifyJSExecutionEnd();
|
||||
}
|
||||
|
||||
static bool
|
||||
ZonesSelected(JSRuntime* rt)
|
||||
{
|
||||
|
||||
@@ -804,6 +804,7 @@ class ArenaLists
|
||||
|
||||
bool foregroundFinalize(FreeOp* fop, AllocKind thingKind, SliceBudget& sliceBudget,
|
||||
SortedArenaList& sweepList);
|
||||
void backgroundFinalizePhase(FreeOp* fop, const FinalizePhase& phase, Arena** empty);
|
||||
static void backgroundFinalize(FreeOp* fop, Arena* listHead, Arena** empty);
|
||||
|
||||
// When finalizing arenas, whether to keep empty arenas on the list or
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
IF_SAB(real,imaginary)(SharedArrayBuffer, InitViaClassSpec, OCLASP(SharedArrayBuffer)) \
|
||||
IF_INTL(real,imaginary) (Intl, InitIntlClass, CLASP(Intl)) \
|
||||
IF_BDATA(real,imaginary)(TypedObject, InitTypedObjectModuleObject, OCLASP(TypedObjectModule)) \
|
||||
real(FinalizationRegistry, InitFinalizationRegistryClass, OCLASP(FinalizationRegistry)) \
|
||||
real(Reflect, InitReflect, nullptr) \
|
||||
real(WeakSet, InitWeakSetClass, OCLASP(WeakSet)) \
|
||||
real(TypedArray, InitViaClassSpec, &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \
|
||||
|
||||
@@ -3305,6 +3305,8 @@ static const JSFunctionSpec string_methods[] = {
|
||||
JS_SELF_HOSTED_FN("toLocaleUpperCase", "String_toLocaleUpperCase", 0,0),
|
||||
JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0),
|
||||
JS_SELF_HOSTED_FN("repeat", "String_repeat", 1,0),
|
||||
JS_SELF_HOSTED_FN("isWellFormed", "String_isWellFormed", 0,0),
|
||||
JS_SELF_HOSTED_FN("toWellFormed", "String_toWellFormed", 0,0),
|
||||
JS_FN("normalize", str_normalize, 0,0),
|
||||
|
||||
/* Perl-ish methods (search is actually Python-esque). */
|
||||
|
||||
@@ -16,11 +16,25 @@
|
||||
#include "gc/Marking.h"
|
||||
#include "gc/StoreBuffer.h"
|
||||
#include "js/HashTable.h"
|
||||
#include "vm/Symbol.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
class WeakMapBase;
|
||||
|
||||
template <>
|
||||
struct MovableCellHasher<JS::Symbol*>
|
||||
{
|
||||
using Key = JS::Symbol*;
|
||||
using Lookup = JS::Symbol*;
|
||||
|
||||
static bool hasHash(const Lookup& l) { return true; }
|
||||
static bool ensureHash(const Lookup& l) { return true; }
|
||||
static HashNumber hash(const Lookup& l) { return l->hash(); }
|
||||
static bool match(const Key& k, const Lookup& l) { return k == l; }
|
||||
static void rekey(Key& k, const Key& newKey) { k = newKey; }
|
||||
};
|
||||
|
||||
// A subclass template of js::HashMap whose keys and values may be garbage-collected. When
|
||||
// a key is collected, the table entry disappears, dropping its reference to the value.
|
||||
//
|
||||
@@ -296,9 +310,16 @@ class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JSObject* getDelegate(JS::Symbol* sym) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
void exposeGCThingToActiveJS(const JS::Value& v) const { JS::ExposeValueToActiveJS(v); }
|
||||
void exposeGCThingToActiveJS(JSObject* obj) const { JS::ExposeObjectToActiveJS(obj); }
|
||||
void exposeGCThingToActiveJS(JS::Symbol* sym) const {
|
||||
gc::ExposeGCThingToActiveJS(JS::GCCellPtr(sym));
|
||||
}
|
||||
|
||||
bool keyNeedsMark(JSObject* key) const {
|
||||
JSObject* delegate = getDelegate(key);
|
||||
@@ -313,6 +334,10 @@ class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool keyNeedsMark(JS::Symbol* sym) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool findZoneEdges() override {
|
||||
// This is overridden by ObjectValueMap.
|
||||
return true;
|
||||
@@ -378,6 +403,9 @@ WeakMap_set(JSContext* cx, unsigned argc, Value* vp);
|
||||
extern bool
|
||||
WeakMap_delete(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
extern bool
|
||||
SetWeakMapEntryValue(JSContext* cx, HandleObject mapObj, HandleValue key, HandleValue val);
|
||||
|
||||
extern JSObject*
|
||||
InitWeakMapClass(JSContext* cx, HandleObject obj);
|
||||
|
||||
@@ -394,6 +422,16 @@ class ObjectValueMap : public WeakMap<HeapPtr<JSObject*>, HeapPtr<Value>,
|
||||
virtual bool findZoneEdges();
|
||||
};
|
||||
|
||||
class SymbolValueMap : public WeakMap<HeapPtr<JS::Symbol*>, HeapPtr<Value>,
|
||||
MovableCellHasher<HeapPtr<JS::Symbol*>>>
|
||||
{
|
||||
public:
|
||||
SymbolValueMap(JSContext* cx, JSObject* obj)
|
||||
: WeakMap<HeapPtr<JS::Symbol*>, HeapPtr<Value>,
|
||||
MovableCellHasher<HeapPtr<JS::Symbol*>>>(cx, obj)
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
// Generic weak map for mapping objects to other objects.
|
||||
class ObjectWeakMap
|
||||
|
||||
@@ -120,6 +120,7 @@ EXPORTS.js += [
|
||||
main_deunified_sources = [
|
||||
'builtin/AtomicsObject.cpp',
|
||||
'builtin/Eval.cpp',
|
||||
'builtin/FinalizationRegistryObject.cpp',
|
||||
'builtin/intl/Collator.cpp',
|
||||
'builtin/intl/CommonFunctions.cpp',
|
||||
'builtin/intl/DateTimeFormat.cpp',
|
||||
@@ -172,6 +173,7 @@ main_deunified_sources = [
|
||||
'gc/Allocator.cpp',
|
||||
'gc/Barrier.cpp',
|
||||
'gc/GCTrace.cpp',
|
||||
'gc/IdleGC.cpp',
|
||||
'gc/Iteration.cpp',
|
||||
'gc/Marking.cpp',
|
||||
'gc/Memory.cpp',
|
||||
|
||||
+16
-4
@@ -676,7 +676,9 @@ RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
|
||||
AnalyzeEntrainedVariables(cx, script);
|
||||
#endif
|
||||
if (!compileOnly) {
|
||||
if (!JS_ExecuteScript(cx, script))
|
||||
bool ok = JS_ExecuteScript(cx, script);
|
||||
JS::ClearWeakRefKeptObjects(cx);
|
||||
if (!ok)
|
||||
return false;
|
||||
int64_t t2 = PRMJ_Now() - t1;
|
||||
if (printTiming)
|
||||
@@ -857,6 +859,7 @@ DrainJobQueue(JSContext* cx)
|
||||
}
|
||||
sc->jobQueue.clear();
|
||||
sc->drainingJobQueue = false;
|
||||
JS::ClearWeakRefKeptObjects(cx);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3239,6 +3242,15 @@ static const JSClass sandbox_class = {
|
||||
&sandbox_classOps
|
||||
};
|
||||
|
||||
enum GlobalAppSlot {
|
||||
GlobalAppSlotModuleMetadataHook,
|
||||
GlobalAppSlotModuleDynamicImportHook,
|
||||
GlobalAppSlotCount
|
||||
};
|
||||
|
||||
static_assert(GlobalAppSlotCount <= JSCLASS_GLOBAL_APPLICATION_SLOTS,
|
||||
"global application slots overflow");
|
||||
|
||||
static void
|
||||
SetStandardCompartmentOptions(JS::CompartmentOptions& options)
|
||||
{
|
||||
@@ -4067,7 +4079,7 @@ ParseModule(JSContext* cx, unsigned argc, Value* vp)
|
||||
|
||||
const char16_t* chars = stableChars.twoByteRange().begin().get();
|
||||
JS::SourceBufferHolder srcBuf(chars, scriptContents->length(),
|
||||
SourceBufferHolder::NoOwnership);
|
||||
JS::SourceBufferHolder::NoOwnership);
|
||||
|
||||
RootedObject module(cx, frontend::CompileModule(cx, options, srcBuf));
|
||||
if (!module)
|
||||
@@ -4278,7 +4290,7 @@ AbortDynamicModuleImport(JSContext* cx, unsigned argc, Value* vp)
|
||||
RootedString specifier(cx, args[1].toString());
|
||||
Rooted<PromiseObject*> promise(cx, &args[2].toObject().as<PromiseObject>());
|
||||
|
||||
cx->setPendingException(args[3]);
|
||||
cx->setPendingException(args[3], nullptr);
|
||||
return js::FinishDynamicModuleImport(cx, args[0], specifier, promise);
|
||||
}
|
||||
|
||||
@@ -8287,7 +8299,7 @@ main(int argc, char** argv, char** envp)
|
||||
|
||||
JS::SetModuleResolveHook(cx->runtime(), ShellModuleResolveHook);
|
||||
JS::SetModuleDynamicImportHook(cx, ShellModuleDynamicImportHook);
|
||||
JS::SetModuleMetadataHook(cx, ShellModuleMetadataHook);
|
||||
JS::SetModuleMetadataHook(cx, CallModuleMetadataHook);
|
||||
|
||||
result = Shell(cx, &op, envp);
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
// Manual WeakRef smoke tests for browser console use.
|
||||
//
|
||||
// Usage:
|
||||
// 1) Optionally flip `javascript.options.weakrefs` in about:config and reload.
|
||||
// 2) Paste this file into the browser console (or load via file://) and run:
|
||||
// 1) Paste this file into the browser console (or load via file://) and run:
|
||||
// runWeakRefManual();
|
||||
// 3) Inspect the returned array of results; no throws or crashes are expected.
|
||||
// 2) Inspect the returned array of results; no throws or crashes are expected.
|
||||
//
|
||||
// Notes:
|
||||
// - When the pref is ON, deref() should return the target.
|
||||
// - When the pref is OFF, the stub traces strongly so deref() should also
|
||||
// return the target.
|
||||
// - WeakRef is always enabled. Once all strong references are gone, a full GC
|
||||
// may clear the referent and make deref() return undefined.
|
||||
|
||||
function runWeakRefManual() {
|
||||
const results = [];
|
||||
@@ -21,7 +19,7 @@ function runWeakRefManual() {
|
||||
log("initial deref tag", ref.deref()?.tag);
|
||||
log("repeat deref identity", ref.deref() === target);
|
||||
|
||||
// Clear the strong reference; the WeakRef should still be able to return it.
|
||||
// Clear the strong reference. A future full GC may clear the WeakRef.
|
||||
target = null;
|
||||
|
||||
const afterClear = ref.deref();
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// |reftest| skip-if(!ArrayBuffer.prototype.transfer)
|
||||
|
||||
var fixed = new ArrayBuffer(4);
|
||||
assertEq(fixed.byteLength, 4);
|
||||
assertEq(fixed.maxByteLength, 4);
|
||||
assertEq(fixed.resizable, false);
|
||||
assertEq(fixed.detached, false);
|
||||
new Uint8Array(fixed).set([1, 2, 3, 4]);
|
||||
fixed.extra = 17;
|
||||
assertEq(Array.from(new Uint8Array(fixed)).join(","), "1,2,3,4");
|
||||
assertThrowsInstanceOf(() => fixed.resize(2), TypeError);
|
||||
assertEq(ArrayBuffer.prototype.transfer.length, 0);
|
||||
assertEq(ArrayBuffer.prototype.transferToFixedLength.length, 0);
|
||||
var resizeArgumentConverted = false;
|
||||
assertThrowsInstanceOf(() => fixed.resize({ valueOf() { resizeArgumentConverted = true; return 1; } }),
|
||||
TypeError);
|
||||
assertEq(resizeArgumentConverted, false);
|
||||
|
||||
var resizable = new ArrayBuffer(4, { maxByteLength: 8 });
|
||||
assertEq(resizable.byteLength, 4);
|
||||
assertEq(resizable.maxByteLength, 8);
|
||||
assertEq(resizable.resizable, true);
|
||||
|
||||
var bytes = new Uint8Array(resizable);
|
||||
bytes[0] = 11;
|
||||
bytes[3] = 44;
|
||||
resizable.resize(6);
|
||||
assertEq(resizable.byteLength, 6);
|
||||
assertEq(new Uint8Array(resizable)[0], 11);
|
||||
assertEq(new Uint8Array(resizable)[3], 44);
|
||||
assertEq(new Uint8Array(resizable)[4], 0);
|
||||
assertThrowsInstanceOf(() => resizable.resize(9), RangeError);
|
||||
|
||||
var source = new ArrayBuffer(4);
|
||||
var sourceBytes = new Uint8Array(source);
|
||||
sourceBytes[0] = 1;
|
||||
sourceBytes[1] = 2;
|
||||
var sourceView = new Uint8Array(source);
|
||||
var moved = source.transfer(6);
|
||||
assertEq(source.detached, true);
|
||||
assertEq(source.byteLength, 0);
|
||||
assertEq(source.maxByteLength, 0);
|
||||
assertEq(sourceView.length, 0);
|
||||
assertEq(moved.byteLength, 6);
|
||||
assertEq(moved.resizable, false);
|
||||
assertEq(moved.maxByteLength, 6);
|
||||
assertEq(new Uint8Array(moved)[0], 1);
|
||||
assertEq(new Uint8Array(moved)[1], 2);
|
||||
assertEq(new Uint8Array(moved)[4], 0);
|
||||
|
||||
var resizableSource = new ArrayBuffer(4, { maxByteLength: 8 });
|
||||
new Uint8Array(resizableSource)[0] = 7;
|
||||
var resizableMoved = resizableSource.transfer();
|
||||
assertEq(resizableSource.detached, true);
|
||||
assertEq(resizableSource.resizable, true);
|
||||
assertEq(resizableMoved.byteLength, 4);
|
||||
assertEq(resizableMoved.maxByteLength, 8);
|
||||
assertEq(resizableMoved.resizable, true);
|
||||
assertEq(new Uint8Array(resizableMoved)[0], 7);
|
||||
|
||||
var fixedMoved = resizableMoved.transferToFixedLength(10);
|
||||
assertEq(resizableMoved.detached, true);
|
||||
assertEq(fixedMoved.byteLength, 10);
|
||||
assertEq(fixedMoved.maxByteLength, 10);
|
||||
assertEq(fixedMoved.resizable, false);
|
||||
assertEq(new Uint8Array(fixedMoved)[0], 7);
|
||||
|
||||
var sliceSource = new ArrayBuffer(8, { maxByteLength: 8 });
|
||||
var sliceSourceBytes = new Uint8Array(sliceSource);
|
||||
for (var i = 0; i < sliceSourceBytes.length; i++)
|
||||
sliceSourceBytes[i] = i + 1;
|
||||
|
||||
sliceSource.constructor = {
|
||||
[Symbol.species]: function(byteLength) {
|
||||
sliceSource.resize(4);
|
||||
return new ArrayBuffer(byteLength);
|
||||
}
|
||||
};
|
||||
|
||||
var sliced = sliceSource.slice(2, 8);
|
||||
var slicedBytes = new Uint8Array(sliced);
|
||||
assertEq(sliced.byteLength, 6);
|
||||
assertEq(slicedBytes[0], 3);
|
||||
assertEq(slicedBytes[1], 4);
|
||||
assertEq(slicedBytes[2], 0);
|
||||
assertEq(slicedBytes[5], 0);
|
||||
|
||||
sliceSource.resize(8);
|
||||
for (var i = 0; i < sliceSourceBytes.length; i++)
|
||||
sliceSourceBytes[i] = i + 1;
|
||||
var zeroCopied = sliceSource.slice(6, 8);
|
||||
assertEq(zeroCopied.byteLength, 2);
|
||||
assertEq(new Uint8Array(zeroCopied)[0], 0);
|
||||
|
||||
assertThrowsInstanceOf(() => new ArrayBuffer(4, { maxByteLength: 3 }), RangeError);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
||||
@@ -0,0 +1,218 @@
|
||||
// |reftest| skip-if(!this.SharedArrayBuffer)
|
||||
|
||||
var rab = new ArrayBuffer(4, { maxByteLength: 16 });
|
||||
var tracking = new Uint8Array(rab);
|
||||
var fixed = new Uint8Array(rab, 1, 2);
|
||||
var bytes = new Uint8Array(rab);
|
||||
|
||||
bytes[1] = 11;
|
||||
bytes[2] = 22;
|
||||
|
||||
assertEq(tracking.length, 4);
|
||||
assertEq(tracking.byteLength, 4);
|
||||
assertEq(tracking.byteOffset, 0);
|
||||
assertEq(fixed.length, 2);
|
||||
assertEq(fixed.byteLength, 2);
|
||||
assertEq(fixed.byteOffset, 1);
|
||||
|
||||
rab.resize(2);
|
||||
assertEq(tracking.length, 2);
|
||||
assertEq(tracking.byteLength, 2);
|
||||
assertEq(fixed.length, 0);
|
||||
assertEq(fixed.byteLength, 0);
|
||||
assertEq(fixed.byteOffset, 0);
|
||||
assertEq(fixed[0], undefined);
|
||||
|
||||
rab.resize(8);
|
||||
assertEq(tracking.length, 8);
|
||||
assertEq(tracking.byteLength, 8);
|
||||
assertEq(fixed.length, 2);
|
||||
assertEq(fixed.byteLength, 2);
|
||||
assertEq(fixed.byteOffset, 1);
|
||||
assertEq(fixed[0], 11);
|
||||
assertEq(fixed[1], 0);
|
||||
tracking[6] = 66;
|
||||
assertEq(new Uint8Array(rab)[6], 66);
|
||||
|
||||
var dv = new DataView(rab, 4);
|
||||
assertEq(dv.byteOffset, 4);
|
||||
assertEq(dv.byteLength, 4);
|
||||
dv.setUint8(0, 44);
|
||||
assertEq(tracking[4], 44);
|
||||
|
||||
rab.resize(3);
|
||||
assertThrowsInstanceOf(() => dv.byteOffset, TypeError);
|
||||
assertThrowsInstanceOf(() => dv.byteLength, TypeError);
|
||||
assertThrowsInstanceOf(() => dv.getUint8(0), TypeError);
|
||||
|
||||
rab.resize(6);
|
||||
assertEq(dv.byteOffset, 4);
|
||||
assertEq(dv.byteLength, 2);
|
||||
assertEq(dv.getUint8(0), 0);
|
||||
|
||||
var fixedDv = new DataView(rab, 4, 2);
|
||||
rab.resize(5);
|
||||
assertThrowsInstanceOf(() => fixedDv.byteOffset, TypeError);
|
||||
assertThrowsInstanceOf(() => fixedDv.byteLength, TypeError);
|
||||
assertThrowsInstanceOf(() => fixedDv.getUint8(0), TypeError);
|
||||
rab.resize(6);
|
||||
assertEq(fixedDv.byteOffset, 4);
|
||||
assertEq(fixedDv.byteLength, 2);
|
||||
|
||||
var methodRab = new ArrayBuffer(4, { maxByteLength: 8 });
|
||||
var methodFixed = new Uint8Array(methodRab, 2, 2);
|
||||
methodRab.resize(2);
|
||||
[
|
||||
() => methodFixed.at(0),
|
||||
() => methodFixed.copyWithin(0, 0),
|
||||
() => methodFixed.entries(),
|
||||
() => methodFixed.every(x => true),
|
||||
() => methodFixed.fill(1),
|
||||
() => methodFixed.filter(x => true),
|
||||
() => methodFixed.find(x => true),
|
||||
() => methodFixed.findIndex(x => true),
|
||||
() => methodFixed.findLast(x => true),
|
||||
() => methodFixed.findLastIndex(x => true),
|
||||
() => methodFixed.forEach(x => x),
|
||||
() => methodFixed.includes(0),
|
||||
() => methodFixed.indexOf(0),
|
||||
() => methodFixed.join(","),
|
||||
() => methodFixed.keys(),
|
||||
() => methodFixed.lastIndexOf(0),
|
||||
() => methodFixed.map(x => x),
|
||||
() => methodFixed.reduce((a, b) => a + b, 0),
|
||||
() => methodFixed.reduceRight((a, b) => a + b, 0),
|
||||
() => methodFixed.reverse(),
|
||||
() => methodFixed.set([1], 0),
|
||||
() => methodFixed.slice(),
|
||||
() => methodFixed.some(x => true),
|
||||
() => methodFixed.sort(),
|
||||
() => methodFixed.subarray(),
|
||||
() => methodFixed.toLocaleString(),
|
||||
() => methodFixed.toReversed(),
|
||||
() => methodFixed.toSorted(),
|
||||
() => methodFixed.toString(),
|
||||
() => methodFixed.values(),
|
||||
() => methodFixed.with(0, 1),
|
||||
() => methodFixed[Symbol.iterator](),
|
||||
].forEach(fn => assertThrowsInstanceOf(fn, TypeError));
|
||||
|
||||
methodRab.resize(4);
|
||||
assertEq(methodFixed.length, 2);
|
||||
|
||||
var sourceRab = new ArrayBuffer(4, { maxByteLength: 8 });
|
||||
var oobSource = new Uint8Array(sourceRab, 2, 2);
|
||||
sourceRab.resize(2);
|
||||
assertThrowsInstanceOf(() => new Uint8Array(oobSource), TypeError);
|
||||
assertThrowsInstanceOf(() => new Uint16Array(oobSource), TypeError);
|
||||
assertThrowsInstanceOf(() => new Uint8Array(4).set(oobSource), TypeError);
|
||||
sourceRab.resize(4);
|
||||
assertEq(new Uint8Array(oobSource).length, 2);
|
||||
|
||||
var ctorRab = new ArrayBuffer(8, { maxByteLength: 8 });
|
||||
var ShrinkingNewTarget = new Proxy(function() {}, {
|
||||
get(target, prop, receiver) {
|
||||
if (prop === "prototype") {
|
||||
ctorRab.resize(2);
|
||||
return DataView.prototype;
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
});
|
||||
assertThrowsInstanceOf(() => Reflect.construct(DataView, [ctorRab, 4], ShrinkingNewTarget),
|
||||
RangeError);
|
||||
|
||||
var fixedCtorRab = new ArrayBuffer(8, { maxByteLength: 8 });
|
||||
var FixedShrinkingNewTarget = new Proxy(function() {}, {
|
||||
get(target, prop, receiver) {
|
||||
if (prop === "prototype") {
|
||||
fixedCtorRab.resize(5);
|
||||
return DataView.prototype;
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
});
|
||||
assertThrowsInstanceOf(() => Reflect.construct(DataView, [fixedCtorRab, 4, 2],
|
||||
FixedShrinkingNewTarget),
|
||||
RangeError);
|
||||
|
||||
var gsab = new SharedArrayBuffer(4, { maxByteLength: 16 });
|
||||
var sharedTracking = new Uint8Array(gsab);
|
||||
assertEq(sharedTracking.length, 4);
|
||||
gsab.grow(8);
|
||||
assertEq(sharedTracking.length, 8);
|
||||
sharedTracking[6] = 33;
|
||||
assertEq(new Uint8Array(gsab)[6], 33);
|
||||
|
||||
var sharedDv = new DataView(gsab);
|
||||
assertEq(sharedDv.buffer, gsab);
|
||||
assertEq(sharedDv.byteOffset, 0);
|
||||
assertEq(sharedDv.byteLength, 8);
|
||||
sharedDv.setUint8(7, 99);
|
||||
assertEq(sharedTracking[7], 99);
|
||||
gsab.grow(12);
|
||||
assertEq(sharedDv.byteLength, 12);
|
||||
assertEq(sharedDv.getUint8(7), 99);
|
||||
|
||||
var fixedSharedDv = new DataView(gsab, 4, 2);
|
||||
assertEq(fixedSharedDv.byteOffset, 4);
|
||||
assertEq(fixedSharedDv.byteLength, 2);
|
||||
gsab.grow(16);
|
||||
assertEq(fixedSharedDv.byteOffset, 4);
|
||||
assertEq(fixedSharedDv.byteLength, 2);
|
||||
|
||||
function readLength(view) {
|
||||
return view.length;
|
||||
}
|
||||
|
||||
function readElement(view, index) {
|
||||
return view[index];
|
||||
}
|
||||
|
||||
function writeElement(view, index, value) {
|
||||
view[index] = value;
|
||||
}
|
||||
|
||||
var normal = new Uint8Array(4);
|
||||
normal[0] = 7;
|
||||
for (var i = 0; i < 2000; i++) {
|
||||
assertEq(readLength(normal), 4);
|
||||
assertEq(readElement(normal, 0), 7);
|
||||
writeElement(normal, 1, 8);
|
||||
}
|
||||
|
||||
var icRab = new ArrayBuffer(4, { maxByteLength: 8 });
|
||||
var icTracking = new Uint8Array(icRab);
|
||||
icTracking[0] = 9;
|
||||
assertEq(readLength(icTracking), 4);
|
||||
assertEq(readElement(icTracking, 0), 9);
|
||||
icRab.resize(0);
|
||||
assertEq(readLength(icTracking), 0);
|
||||
assertEq(readElement(icTracking, 0), undefined);
|
||||
writeElement(icTracking, 0, 1);
|
||||
icRab.resize(4);
|
||||
assertEq(readLength(icTracking), 4);
|
||||
assertEq(readElement(icTracking, 0), 0);
|
||||
writeElement(icTracking, 0, 12);
|
||||
assertEq(readElement(icTracking, 0), 12);
|
||||
|
||||
var icFixed = new Uint8Array(icRab, 1, 2);
|
||||
assertEq(readLength(icFixed), 2);
|
||||
icRab.resize(2);
|
||||
assertEq(readLength(icFixed), 0);
|
||||
assertEq(readElement(icFixed, 0), undefined);
|
||||
writeElement(icFixed, 0, 55);
|
||||
icRab.resize(4);
|
||||
assertEq(readLength(icFixed), 2);
|
||||
assertEq(readElement(icFixed, 0), 0);
|
||||
|
||||
var icGsab = new SharedArrayBuffer(4, { maxByteLength: 8 });
|
||||
var icSharedTracking = new Uint8Array(icGsab);
|
||||
assertEq(readLength(icSharedTracking), 4);
|
||||
icGsab.grow(8);
|
||||
assertEq(readLength(icSharedTracking), 8);
|
||||
writeElement(icSharedTracking, 5, 77);
|
||||
assertEq(readElement(icSharedTracking, 5), 77);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
@@ -0,0 +1,77 @@
|
||||
// |reftest| skip-if(!this.SharedArrayBuffer || !this.Atomics || !this.drainJobQueue)
|
||||
|
||||
if (typeof SharedArrayBuffer === "function" && typeof Atomics === "object" &&
|
||||
typeof drainJobQueue === "function") {
|
||||
const sab = new SharedArrayBuffer(4);
|
||||
const i32 = new Int32Array(sab);
|
||||
|
||||
let result = Atomics.waitAsync(i32, 0, 1, 10);
|
||||
assertEq(result.async, false);
|
||||
assertEq(result.value, "not-equal");
|
||||
|
||||
result = Atomics.waitAsync(i32, 0, 0, 0);
|
||||
assertEq(result.async, false);
|
||||
assertEq(result.value, "timed-out");
|
||||
|
||||
result = Atomics.waitAsync(i32, 0, 0);
|
||||
assertEq(result.async, true);
|
||||
let notified;
|
||||
result.value.then(value => {
|
||||
notified = value;
|
||||
});
|
||||
assertEq(Atomics.notify(i32, 0, 1), 1);
|
||||
drainJobQueue();
|
||||
assertEq(notified, "ok");
|
||||
|
||||
result = Atomics.waitAsync(i32, 0, 0, 1);
|
||||
assertEq(result.async, true);
|
||||
let timedOut;
|
||||
result.value.then(value => {
|
||||
timedOut = value;
|
||||
});
|
||||
drainJobQueue();
|
||||
assertEq(timedOut, "timed-out");
|
||||
|
||||
if (typeof BigInt64Array === "function") {
|
||||
const bigSab = new SharedArrayBuffer(16);
|
||||
const i64 = new BigInt64Array(bigSab);
|
||||
const u64 = new BigUint64Array(bigSab);
|
||||
|
||||
assertEq(Atomics.store(i64, 0, -1n), -1n);
|
||||
assertEq(Atomics.load(i64, 0), -1n);
|
||||
assertEq(Atomics.load(u64, 0), 18446744073709551615n);
|
||||
assertEq(Atomics.exchange(i64, 0, 7n), -1n);
|
||||
assertEq(Atomics.compareExchange(i64, 0, 7n, 10n), 7n);
|
||||
assertEq(Atomics.add(i64, 0, 5n), 10n);
|
||||
assertEq(Atomics.load(i64, 0), 15n);
|
||||
assertEq(Atomics.sub(i64, 0, 20n), 15n);
|
||||
assertEq(Atomics.load(i64, 0), -5n);
|
||||
assertEq(Atomics.and(u64, 0, 7n), 18446744073709551611n);
|
||||
assertEq(Atomics.or(u64, 0, 8n), 3n);
|
||||
assertEq(Atomics.xor(u64, 0, 15n), 11n);
|
||||
|
||||
assertThrowsInstanceOf(() => Atomics.waitAsync(u64, 0, 0n), TypeError);
|
||||
|
||||
result = Atomics.waitAsync(i64, 1, 1n, 10);
|
||||
assertEq(result.async, false);
|
||||
assertEq(result.value, "not-equal");
|
||||
|
||||
result = Atomics.waitAsync(i64, 1, 0n, 0);
|
||||
assertEq(result.async, false);
|
||||
assertEq(result.value, "timed-out");
|
||||
|
||||
let offsetView = new BigInt64Array(bigSab, 8);
|
||||
result = Atomics.waitAsync(offsetView, 0, 0n);
|
||||
assertEq(result.async, true);
|
||||
notified = undefined;
|
||||
result.value.then(value => {
|
||||
notified = value;
|
||||
});
|
||||
assertEq(Atomics.notify(i64, 1, 1), 1);
|
||||
drainJobQueue();
|
||||
assertEq(notified, "ok");
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
@@ -0,0 +1,31 @@
|
||||
// |reftest| skip-if(!Promise.withResolvers)
|
||||
|
||||
var desc = Object.getOwnPropertyDescriptor(Promise, "withResolvers");
|
||||
assertEq(desc.enumerable, false);
|
||||
assertEq(desc.configurable, true);
|
||||
assertEq(desc.writable, true);
|
||||
assertEq(Promise.withResolvers.length, 0);
|
||||
assertEq(Promise.withResolvers.name, "withResolvers");
|
||||
|
||||
var capability = Promise.withResolvers();
|
||||
assertEq(capability.promise instanceof Promise, true);
|
||||
assertEq(typeof capability.resolve, "function");
|
||||
assertEq(typeof capability.reject, "function");
|
||||
assertEqArray(Object.keys(capability), ["promise", "resolve", "reject"]);
|
||||
|
||||
capability.resolve(42);
|
||||
capability.promise.then(v => assertEq(v, 42));
|
||||
|
||||
class MyPromise extends Promise {}
|
||||
var subCapability = Promise.withResolvers.call(MyPromise);
|
||||
assertEq(subCapability.promise instanceof MyPromise, true);
|
||||
subCapability.reject("rejected");
|
||||
subCapability.promise.then(
|
||||
() => { throw new Error("expected rejection"); },
|
||||
reason => assertEq(reason, "rejected")
|
||||
);
|
||||
|
||||
assertThrowsInstanceOf(() => Promise.withResolvers.call({}), TypeError);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
||||
@@ -0,0 +1,40 @@
|
||||
// |reftest| skip-if(!this.SharedArrayBuffer)
|
||||
|
||||
if (typeof SharedArrayBuffer === "function") {
|
||||
const fixed = new SharedArrayBuffer(4);
|
||||
assertEq(fixed.byteLength, 4);
|
||||
assertEq(fixed.maxByteLength, 4);
|
||||
assertEq(fixed.growable, false);
|
||||
assertThrowsInstanceOf(() => fixed.grow(4), TypeError);
|
||||
|
||||
assertThrowsInstanceOf(() => new SharedArrayBuffer(-1), RangeError);
|
||||
assertThrowsInstanceOf(() => new SharedArrayBuffer(8, {maxByteLength: 4}), RangeError);
|
||||
|
||||
let optionGetterCalled = false;
|
||||
const growable = new SharedArrayBuffer(4, {
|
||||
get maxByteLength() {
|
||||
optionGetterCalled = true;
|
||||
return 16;
|
||||
}
|
||||
});
|
||||
assertEq(optionGetterCalled, true);
|
||||
assertEq(growable.byteLength, 4);
|
||||
assertEq(growable.maxByteLength, 16);
|
||||
assertEq(growable.growable, true);
|
||||
|
||||
const before = new Uint8Array(growable);
|
||||
before[0] = 37;
|
||||
assertEq(growable.grow(12), undefined);
|
||||
assertEq(growable.byteLength, 12);
|
||||
assertEq(growable.maxByteLength, 16);
|
||||
|
||||
const after = new Uint8Array(growable);
|
||||
assertEq(after.length, 12);
|
||||
assertEq(after[0], 37);
|
||||
|
||||
assertThrowsInstanceOf(() => growable.grow(11), RangeError);
|
||||
assertThrowsInstanceOf(() => growable.grow(17), RangeError);
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
@@ -0,0 +1,26 @@
|
||||
// |reftest| skip-if(!String.prototype.isWellFormed||!String.prototype.toWellFormed)
|
||||
|
||||
assertEq("".isWellFormed(), true);
|
||||
assertEq("abc".isWellFormed(), true);
|
||||
assertEq("\uD83D\uDE00".isWellFormed(), true);
|
||||
assertEq("\uD800".isWellFormed(), false);
|
||||
assertEq("\uDC00".isWellFormed(), false);
|
||||
assertEq("\uD800a".isWellFormed(), false);
|
||||
assertEq("a\uDC00".isWellFormed(), false);
|
||||
assertEq("\uD800\uD800\uDC00".isWellFormed(), false);
|
||||
|
||||
assertEq("abc".toWellFormed(), "abc");
|
||||
assertEq("\uD83D\uDE00".toWellFormed(), "\uD83D\uDE00");
|
||||
assertEq("\uD800".toWellFormed(), "\uFFFD");
|
||||
assertEq("\uDC00".toWellFormed(), "\uFFFD");
|
||||
assertEq("\uD800a\uDC00".toWellFormed(), "\uFFFDa\uFFFD");
|
||||
assertEq("\uD800\uD800\uDC00".toWellFormed(), "\uFFFD\uD800\uDC00");
|
||||
|
||||
assertEq(String.prototype.isWellFormed.call(123), true);
|
||||
assertEq(String.prototype.toWellFormed.call({ toString() { return "\uD800x"; } }), "\uFFFDx");
|
||||
|
||||
assertThrowsInstanceOf(() => String.prototype.isWellFormed.call(null), TypeError);
|
||||
assertThrowsInstanceOf(() => String.prototype.toWellFormed.call(undefined), TypeError);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
||||
@@ -0,0 +1,36 @@
|
||||
var key = Symbol("weak");
|
||||
var map = new WeakMap();
|
||||
assertEq(map.has(key), false);
|
||||
assertEq(map.get(key), undefined);
|
||||
assertEq(map.set(key, 13), map);
|
||||
assertEq(map.has(key), true);
|
||||
assertEq(map.get(key), 13);
|
||||
assertEq(map.delete(key), true);
|
||||
assertEq(map.has(key), false);
|
||||
|
||||
var constructedKey = Symbol("constructed");
|
||||
var constructed = new WeakMap([[constructedKey, 7]]);
|
||||
assertEq(constructed.get(constructedKey), 7);
|
||||
|
||||
var registered = Symbol.for("registered");
|
||||
assertEq(map.has(registered), false);
|
||||
assertEq(map.get(registered), undefined);
|
||||
assertEq(map.delete(registered), false);
|
||||
assertThrowsInstanceOf(() => map.set(registered, 1), TypeError);
|
||||
assertThrowsInstanceOf(() => new WeakMap([[registered, 1]]), TypeError);
|
||||
|
||||
var setKey = Symbol("set");
|
||||
var set = new WeakSet([setKey]);
|
||||
assertEq(set.has(setKey), true);
|
||||
assertEq(set.delete(setKey), true);
|
||||
assertEq(set.has(setKey), false);
|
||||
assertEq(set.add(setKey), set);
|
||||
assertEq(set.has(setKey), true);
|
||||
|
||||
assertEq(set.has(registered), false);
|
||||
assertEq(set.delete(registered), false);
|
||||
assertThrowsInstanceOf(() => set.add(registered), TypeError);
|
||||
assertThrowsInstanceOf(() => new WeakSet([registered]), TypeError);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
||||
@@ -0,0 +1,34 @@
|
||||
// |reftest| skip-if(!Object.groupBy||!Map.groupBy)
|
||||
|
||||
var objectGroups = Object.groupBy(["a", "bb", "c"], value => value.length);
|
||||
assertEq(Object.getPrototypeOf(objectGroups), null);
|
||||
assertEqArray(objectGroups["1"], ["a", "c"]);
|
||||
assertEqArray(objectGroups["2"], ["bb"]);
|
||||
|
||||
var symbol = Symbol();
|
||||
var symbolGroups = Object.groupBy([1, 2], value => value === 1 ? symbol : "__proto__");
|
||||
assertEqArray(symbolGroups[symbol], [1]);
|
||||
assertEqArray(symbolGroups.__proto__, [2]);
|
||||
|
||||
var indexes = [];
|
||||
var mapGroups = Map.groupBy(["a", "bb", "c"], (value, index) => {
|
||||
indexes.push(index);
|
||||
return value.length;
|
||||
});
|
||||
assertEq(mapGroups instanceof Map, true);
|
||||
assertEqArray(indexes, [0, 1, 2]);
|
||||
assertEqArray(mapGroups.get(1), ["a", "c"]);
|
||||
assertEqArray(mapGroups.get(2), ["bb"]);
|
||||
|
||||
var key = {};
|
||||
var objectKeyGroups = Map.groupBy([1, 2], value => value === 1 ? key : NaN);
|
||||
assertEqArray(objectKeyGroups.get(key), [1]);
|
||||
assertEqArray(objectKeyGroups.get(NaN), [2]);
|
||||
|
||||
assertThrowsInstanceOf(() => Object.groupBy(null, x => x), TypeError);
|
||||
assertThrowsInstanceOf(() => Map.groupBy(undefined, x => x), TypeError);
|
||||
assertThrowsInstanceOf(() => Object.groupBy([], null), TypeError);
|
||||
assertThrowsInstanceOf(() => Map.groupBy([], null), TypeError);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
||||
+375
-36
@@ -22,6 +22,7 @@
|
||||
# include <valgrind/memcheck.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include "jsapi.h"
|
||||
#include "jsarray.h"
|
||||
#include "jscntxt.h"
|
||||
@@ -45,6 +46,7 @@
|
||||
#include "vm/Interpreter.h"
|
||||
#include "vm/SelfHosting.h"
|
||||
#include "vm/SharedArrayObject.h"
|
||||
#include "vm/TypedArrayObject.h"
|
||||
#include "vm/WrapperObject.h"
|
||||
#include "wasm/WasmSignalHandlers.h"
|
||||
#include "wasm/WasmTypes.h"
|
||||
@@ -170,12 +172,18 @@ static const JSPropertySpec static_properties[] = {
|
||||
|
||||
|
||||
static const JSFunctionSpec prototype_functions[] = {
|
||||
JS_FN("resize", ArrayBufferObject::fun_resize, 1, 0),
|
||||
JS_SELF_HOSTED_FN("slice", "ArrayBufferSlice", 2, 0),
|
||||
JS_FN("transfer", ArrayBufferObject::fun_transfer, 0, 0),
|
||||
JS_FN("transferToFixedLength", ArrayBufferObject::fun_transferToFixedLength, 0, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
static const JSPropertySpec prototype_properties[] = {
|
||||
JS_PSG("byteLength", ArrayBufferObject::byteLengthGetter, 0),
|
||||
JS_PSG("detached", ArrayBufferObject::detachedGetter, 0),
|
||||
JS_PSG("maxByteLength", ArrayBufferObject::maxByteLengthGetter, 0),
|
||||
JS_PSG("resizable", ArrayBufferObject::resizableGetter, 0),
|
||||
JS_STRING_SYM_PS(toStringTag, "ArrayBuffer", JSPROP_READONLY),
|
||||
JS_PS_END
|
||||
};
|
||||
@@ -252,6 +260,52 @@ ArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
|
||||
return CallNonGenericMethod<IsArrayBuffer, byteLengthGetterImpl>(cx, args);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
ArrayBufferObject::detachedGetterImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
|
||||
args.rval().setBoolean(args.thisv().toObject().as<ArrayBufferObject>().isDetached());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::detachedGetter(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsArrayBuffer, detachedGetterImpl>(cx, args);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
ArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
|
||||
ArrayBufferObject& buffer = args.thisv().toObject().as<ArrayBufferObject>();
|
||||
args.rval().setInt32(buffer.isDetached() ? 0 : int32_t(buffer.maxByteLength()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsArrayBuffer, maxByteLengthGetterImpl>(cx, args);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
ArrayBufferObject::resizableGetterImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
|
||||
args.rval().setBoolean(args.thisv().toObject().as<ArrayBufferObject>().isResizable());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::resizableGetter(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsArrayBuffer, resizableGetterImpl>(cx, args);
|
||||
}
|
||||
|
||||
/*
|
||||
* ArrayBuffer.isView(obj); ES6 (Dec 2013 draft) 24.1.3.1
|
||||
*/
|
||||
@@ -264,6 +318,39 @@ ArrayBufferObject::fun_isView(JSContext* cx, unsigned argc, Value* vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
GetArrayBufferMaxByteLengthOption(JSContext* cx, HandleValue options,
|
||||
uint32_t byteLength, uint32_t* maxByteLength,
|
||||
bool* resizable)
|
||||
{
|
||||
*maxByteLength = byteLength;
|
||||
*resizable = false;
|
||||
|
||||
if (!options.isObject())
|
||||
return true;
|
||||
|
||||
RootedObject opts(cx, &options.toObject());
|
||||
RootedValue maxByteLengthValue(cx);
|
||||
if (!GetProperty(cx, opts, opts, cx->names().maxByteLength, &maxByteLengthValue))
|
||||
return false;
|
||||
|
||||
if (maxByteLengthValue.isUndefined())
|
||||
return true;
|
||||
|
||||
uint64_t max;
|
||||
if (!ToIndex(cx, maxByteLengthValue, &max))
|
||||
return false;
|
||||
|
||||
if (max > INT32_MAX || max < byteLength) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
*maxByteLength = uint32_t(max);
|
||||
*resizable = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ES2017 draft 24.1.2.1
|
||||
bool
|
||||
@@ -287,12 +374,22 @@ ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
|
||||
}
|
||||
|
||||
// Step 3.
|
||||
uint32_t maxByteLength;
|
||||
bool resizable;
|
||||
if (!GetArrayBufferMaxByteLengthOption(cx, args.get(1), uint32_t(byteLength),
|
||||
&maxByteLength, &resizable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4.
|
||||
RootedObject proto(cx);
|
||||
RootedObject newTarget(cx, &args.newTarget().toObject());
|
||||
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
|
||||
return false;
|
||||
|
||||
JSObject* bufobj = create(cx, uint32_t(byteLength), proto);
|
||||
JSObject* bufobj = create(cx, uint32_t(byteLength), BufferContents::createPlain(nullptr),
|
||||
OwnsData, proto, GenericObject, maxByteLength, resizable);
|
||||
if (!bufobj)
|
||||
return false;
|
||||
args.rval().setObject(*bufobj);
|
||||
@@ -302,13 +399,66 @@ ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
|
||||
static ArrayBufferObject::BufferContents
|
||||
AllocateArrayBufferContents(JSContext* cx, uint32_t nbytes)
|
||||
{
|
||||
uint8_t* p = cx->runtime()->pod_callocCanGC<uint8_t>(nbytes);
|
||||
uint8_t* p = cx->runtime()->pod_callocCanGC<uint8_t>(nbytes ? nbytes : 1);
|
||||
if (!p)
|
||||
ReportOutOfMemory(cx);
|
||||
|
||||
return ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(p);
|
||||
}
|
||||
|
||||
static bool
|
||||
ReportArrayBufferNotResizable(JSContext* cx)
|
||||
{
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARRAYBUFFER_NOT_RESIZABLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
ReportArrayBufferCannotDetach(JSContext* cx)
|
||||
{
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARRAYBUFFER_CANNOT_DETACH);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
ReportArrayBufferLengthOutOfRange(JSContext* cx)
|
||||
{
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
ArrayBufferViewFits(ArrayBufferViewObject* view, uint32_t newByteLength)
|
||||
{
|
||||
if (view->is<DataViewObject>()) {
|
||||
DataViewObject& dataView = view->as<DataViewObject>();
|
||||
uint32_t byteOffset = dataView.byteOffsetMaybeOutOfBounds();
|
||||
if (byteOffset > newByteLength)
|
||||
return false;
|
||||
uint32_t byteLength = dataView.isLengthTracking()
|
||||
? newByteLength - byteOffset
|
||||
: dataView.fixedByteLengthMaybeOutOfBounds();
|
||||
return byteOffset <= newByteLength && byteLength <= newByteLength - byteOffset;
|
||||
}
|
||||
|
||||
if (view->is<TypedArrayObject>()) {
|
||||
TypedArrayObject& typedArray = view->as<TypedArrayObject>();
|
||||
if (typedArray.isSharedMemory())
|
||||
return true;
|
||||
uint32_t byteOffset = typedArray.byteOffsetMaybeOutOfBounds();
|
||||
if (byteOffset > newByteLength)
|
||||
return false;
|
||||
uint32_t byteLength = typedArray.isLengthTracking()
|
||||
? newByteLength - byteOffset
|
||||
: typedArray.fixedLengthMaybeOutOfBounds() *
|
||||
typedArray.bytesPerElement();
|
||||
return byteOffset <= newByteLength && byteLength <= newByteLength - byteOffset;
|
||||
}
|
||||
|
||||
// Outline typed objects don't have a recoverable fixed byte range here.
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
NoteViewBufferWasDetached(ArrayBufferViewObject* view,
|
||||
ArrayBufferObject::BufferContents newContents,
|
||||
@@ -391,7 +541,8 @@ ArrayBufferObject::setNewData(FreeOp* fop, BufferContents newContents, OwnsState
|
||||
|
||||
void
|
||||
ArrayBufferObject::changeViewContents(JSContext* cx, ArrayBufferViewObject* view,
|
||||
uint8_t* oldDataPointer, BufferContents newContents)
|
||||
uint8_t* oldDataPointer, BufferContents newContents,
|
||||
uint32_t newByteLength)
|
||||
{
|
||||
MOZ_ASSERT(!view->isSharedMemory());
|
||||
|
||||
@@ -402,7 +553,18 @@ ArrayBufferObject::changeViewContents(JSContext* cx, ArrayBufferViewObject* view
|
||||
uint8_t* viewDataPointer = view->dataPointerUnshared(nogc);
|
||||
if (viewDataPointer) {
|
||||
MOZ_ASSERT(newContents);
|
||||
ptrdiff_t offset = viewDataPointer - oldDataPointer;
|
||||
uint32_t offset;
|
||||
if (view->is<DataViewObject>()) {
|
||||
offset = view->as<DataViewObject>().byteOffsetMaybeOutOfBounds();
|
||||
} else if (view->is<TypedArrayObject>()) {
|
||||
offset = view->as<TypedArrayObject>().byteOffsetMaybeOutOfBounds();
|
||||
} else {
|
||||
ptrdiff_t oldOffset = viewDataPointer - oldDataPointer;
|
||||
MOZ_ASSERT(oldOffset >= 0);
|
||||
offset = uint32_t(oldOffset);
|
||||
}
|
||||
if (offset > newByteLength)
|
||||
offset = 0;
|
||||
viewDataPointer = static_cast<uint8_t*>(newContents.data()) + offset;
|
||||
view->setDataPointerUnshared(viewDataPointer);
|
||||
}
|
||||
@@ -428,10 +590,180 @@ ArrayBufferObject::changeContents(JSContext* cx, BufferContents newContents,
|
||||
auto& innerViews = cx->compartment()->innerViews.get();
|
||||
if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(this)) {
|
||||
for (size_t i = 0; i < views->length(); i++)
|
||||
changeViewContents(cx, (*views)[i], oldDataPointer, newContents);
|
||||
changeViewContents(cx, (*views)[i], oldDataPointer, newContents, byteLength());
|
||||
}
|
||||
if (firstView())
|
||||
changeViewContents(cx, firstView(), oldDataPointer, newContents);
|
||||
changeViewContents(cx, firstView(), oldDataPointer, newContents, byteLength());
|
||||
}
|
||||
|
||||
void
|
||||
ArrayBufferObject::changeContentsForResize(JSContext* cx, BufferContents newContents,
|
||||
OwnsState ownsState, uint32_t newByteLength)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(!isWasm());
|
||||
MOZ_ASSERT(!forInlineTypedObject());
|
||||
|
||||
uint8_t* oldDataPointer = dataPointer();
|
||||
setNewData(cx->runtime()->defaultFreeOp(), newContents, ownsState);
|
||||
setByteLength(newByteLength);
|
||||
|
||||
auto& innerViews = cx->compartment()->innerViews.get();
|
||||
if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(this)) {
|
||||
for (size_t i = 0; i < views->length(); i++) {
|
||||
ArrayBufferViewObject* view = (*views)[i];
|
||||
if (view->is<DataViewObject>() || view->is<TypedArrayObject>())
|
||||
changeViewContents(cx, view, oldDataPointer, newContents, newByteLength);
|
||||
else if (ArrayBufferViewFits(view, newByteLength))
|
||||
changeViewContents(cx, view, oldDataPointer, newContents, newByteLength);
|
||||
else
|
||||
NoteViewBufferWasDetached(view, newContents, cx);
|
||||
}
|
||||
}
|
||||
|
||||
if (firstView()) {
|
||||
if (firstView()->is<DataViewObject>() || firstView()->is<TypedArrayObject>())
|
||||
changeViewContents(cx, firstView(), oldDataPointer, newContents, newByteLength);
|
||||
else if (ArrayBufferViewFits(firstView(), newByteLength))
|
||||
changeViewContents(cx, firstView(), oldDataPointer, newContents, newByteLength);
|
||||
else
|
||||
NoteViewBufferWasDetached(firstView(), newContents, cx);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
ResizeArrayBuffer(JSContext* cx, Handle<ArrayBufferObject*> buffer, uint32_t newByteLength)
|
||||
{
|
||||
if (!buffer->isResizable())
|
||||
return ReportArrayBufferNotResizable(cx);
|
||||
|
||||
if (buffer->isDetached()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newByteLength > buffer->maxByteLength())
|
||||
return ReportArrayBufferLengthOutOfRange(cx);
|
||||
|
||||
if (!buffer->isPlain() || buffer->isPreparedForAsmJS() || buffer->forInlineTypedObject())
|
||||
return ReportArrayBufferCannotDetach(cx);
|
||||
|
||||
if (newByteLength == buffer->byteLength())
|
||||
return true;
|
||||
|
||||
ArrayBufferObject::BufferContents newContents = AllocateArrayBufferContents(cx, newByteLength);
|
||||
if (!newContents)
|
||||
return false;
|
||||
|
||||
uint32_t copyLength = std::min(newByteLength, buffer->byteLength());
|
||||
if (copyLength > 0)
|
||||
memcpy(newContents.data(), buffer->dataPointer(), copyLength);
|
||||
|
||||
buffer->changeContentsForResize(cx, newContents, ArrayBufferObject::OwnsData, newByteLength);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::fun_resize_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
|
||||
|
||||
Rooted<ArrayBufferObject*> buffer(cx, &args.thisv().toObject().as<ArrayBufferObject>());
|
||||
if (!buffer->isResizable())
|
||||
return ReportArrayBufferNotResizable(cx);
|
||||
|
||||
uint64_t newByteLength;
|
||||
if (!ToIndex(cx, args.get(0), &newByteLength))
|
||||
return false;
|
||||
if (newByteLength > INT32_MAX)
|
||||
return ReportArrayBufferLengthOutOfRange(cx);
|
||||
|
||||
if (!ResizeArrayBuffer(cx, buffer, uint32_t(newByteLength)))
|
||||
return false;
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::fun_resize(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsArrayBuffer, fun_resize_impl>(cx, args);
|
||||
}
|
||||
|
||||
static bool
|
||||
ArrayBufferTransfer(JSContext* cx, const CallArgs& args, bool preserveResizability)
|
||||
{
|
||||
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
|
||||
Rooted<ArrayBufferObject*> buffer(cx, &args.thisv().toObject().as<ArrayBufferObject>());
|
||||
|
||||
uint32_t newByteLength = buffer->byteLength();
|
||||
if (args.hasDefined(0)) {
|
||||
uint64_t newLength;
|
||||
if (!ToIndex(cx, args.get(0), &newLength))
|
||||
return false;
|
||||
if (newLength > INT32_MAX)
|
||||
return ReportArrayBufferLengthOutOfRange(cx);
|
||||
newByteLength = uint32_t(newLength);
|
||||
}
|
||||
|
||||
if (buffer->isDetached()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer->isWasm() || buffer->isPreparedForAsmJS())
|
||||
return ReportArrayBufferCannotDetach(cx);
|
||||
|
||||
bool newResizable = preserveResizability && buffer->isResizable();
|
||||
uint32_t newMaxByteLength = newResizable ? buffer->maxByteLength() : newByteLength;
|
||||
if (newResizable && newByteLength > newMaxByteLength)
|
||||
return ReportArrayBufferLengthOutOfRange(cx);
|
||||
|
||||
Rooted<ArrayBufferObject*> newBuffer(cx,
|
||||
ArrayBufferObject::create(cx, newByteLength, ArrayBufferObject::BufferContents::createPlain(nullptr),
|
||||
ArrayBufferObject::OwnsData, nullptr, GenericObject,
|
||||
newMaxByteLength, newResizable));
|
||||
if (!newBuffer)
|
||||
return false;
|
||||
|
||||
uint32_t copyLength = std::min(newByteLength, buffer->byteLength());
|
||||
if (copyLength > 0)
|
||||
memcpy(newBuffer->dataPointer(), buffer->dataPointer(), copyLength);
|
||||
|
||||
ArrayBufferObject::BufferContents detachedContents =
|
||||
buffer->hasStealableContents() ? ArrayBufferObject::BufferContents::createPlain(nullptr)
|
||||
: buffer->contents();
|
||||
ArrayBufferObject::detach(cx, buffer, detachedContents);
|
||||
|
||||
args.rval().setObject(*newBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::fun_transfer_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
return ArrayBufferTransfer(cx, args, true);
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::fun_transfer(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsArrayBuffer, fun_transfer_impl>(cx, args);
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::fun_transferToFixedLength_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
return ArrayBufferTransfer(cx, args, false);
|
||||
}
|
||||
|
||||
bool
|
||||
ArrayBufferObject::fun_transferToFixedLength(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsArrayBuffer, fun_transferToFixedLength_impl>(cx, args);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -887,6 +1219,14 @@ ArrayBufferObject::byteLength() const
|
||||
return getSlot(BYTE_LENGTH_SLOT).toInt32();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ArrayBufferObject::maxByteLength() const
|
||||
{
|
||||
if (!isResizable())
|
||||
return byteLength();
|
||||
return getSlot(MAX_BYTE_LENGTH_SLOT).toInt32();
|
||||
}
|
||||
|
||||
void
|
||||
ArrayBufferObject::setByteLength(uint32_t length)
|
||||
{
|
||||
@@ -929,7 +1269,7 @@ js::WasmArrayBufferMaxSize(const ArrayBufferObjectMaybeShared* buf)
|
||||
if (buf->is<ArrayBufferObject>())
|
||||
return buf->as<ArrayBufferObject>().wasmMaxSize();
|
||||
|
||||
return Some(buf->as<SharedArrayBufferObject>().byteLength());
|
||||
return Some(buf->as<SharedArrayBufferObject>().maxByteLength());
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
@@ -1035,9 +1375,12 @@ ArrayBufferObject*
|
||||
ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents contents,
|
||||
OwnsState ownsState /* = OwnsData */,
|
||||
HandleObject proto /* = nullptr */,
|
||||
NewObjectKind newKind /* = GenericObject */)
|
||||
NewObjectKind newKind /* = GenericObject */,
|
||||
uint32_t maxByteLength /* = 0 */,
|
||||
bool resizable /* = false */)
|
||||
{
|
||||
MOZ_ASSERT_IF(contents.kind() == MAPPED, contents);
|
||||
MOZ_ASSERT_IF(resizable, maxByteLength >= nbytes);
|
||||
|
||||
// 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
|
||||
// Refuse to allocate too large buffers, currently limited to ~2 GiB.
|
||||
@@ -1046,10 +1389,6 @@ ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents content
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If we need to allocate data, try to use a larger object size class so
|
||||
// that the array buffer's data can be allocated inline with the object.
|
||||
// The extra space will be left unused by the object's fixed slots and
|
||||
// available for the buffer's data, see NewObject().
|
||||
size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&class_);
|
||||
|
||||
size_t nslots = reservedSlots;
|
||||
@@ -1066,18 +1405,10 @@ ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents content
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(ownsState == OwnsData);
|
||||
size_t usableSlots = NativeObject::MAX_FIXED_SLOTS - reservedSlots;
|
||||
if (nbytes <= usableSlots * sizeof(Value)) {
|
||||
int newSlots = (nbytes - 1) / sizeof(Value) + 1;
|
||||
MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));
|
||||
nslots = reservedSlots + newSlots;
|
||||
contents = BufferContents::createPlain(nullptr);
|
||||
} else {
|
||||
contents = AllocateArrayBufferContents(cx, nbytes);
|
||||
if (!contents)
|
||||
return nullptr;
|
||||
allocated = true;
|
||||
}
|
||||
contents = AllocateArrayBufferContents(cx, nbytes);
|
||||
if (!contents)
|
||||
return nullptr;
|
||||
allocated = true;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!(class_.flags & JSCLASS_HAS_PRIVATE));
|
||||
@@ -1098,9 +1429,10 @@ ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents content
|
||||
if (!contents) {
|
||||
void* data = obj->inlineDataPointer();
|
||||
memset(data, 0, nbytes);
|
||||
obj->initialize(nbytes, BufferContents::createPlain(data), DoesntOwnData);
|
||||
obj->initialize(nbytes, BufferContents::createPlain(data), DoesntOwnData,
|
||||
maxByteLength, resizable);
|
||||
} else {
|
||||
obj->initialize(nbytes, contents, ownsState);
|
||||
obj->initialize(nbytes, contents, ownsState, maxByteLength, resizable);
|
||||
}
|
||||
|
||||
return obj;
|
||||
@@ -1130,25 +1462,28 @@ ArrayBufferObject::createEmpty(JSContext* cx)
|
||||
bool
|
||||
ArrayBufferObject::createDataViewForThisImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
|
||||
MOZ_ASSERT(IsAnyArrayBuffer(args.thisv()));
|
||||
|
||||
/*
|
||||
* This method is only called for |DataView(alienBuf, ...)| which calls
|
||||
* this as |createDataViewForThis.call(alienBuf, byteOffset, byteLength,
|
||||
* DataView.prototype)|,
|
||||
* ergo there must be exactly 3 arguments.
|
||||
* DataView.prototype, lengthTracking)|,
|
||||
* ergo there must be exactly 4 arguments.
|
||||
*/
|
||||
MOZ_ASSERT(args.length() == 3);
|
||||
MOZ_ASSERT(args.length() == 4);
|
||||
|
||||
uint32_t byteOffset = args[0].toPrivateUint32();
|
||||
uint32_t byteLength = args[1].toPrivateUint32();
|
||||
Rooted<ArrayBufferObject*> buffer(cx, &args.thisv().toObject().as<ArrayBufferObject>());
|
||||
bool lengthTracking = args[3].toBoolean();
|
||||
Rooted<ArrayBufferObjectMaybeShared*> buffer(cx,
|
||||
&args.thisv().toObject().as<ArrayBufferObjectMaybeShared>());
|
||||
|
||||
/*
|
||||
* Pop off the passed-along prototype and delegate to normal DataViewObject
|
||||
* construction.
|
||||
*/
|
||||
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, &args[2].toObject());
|
||||
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer,
|
||||
&args[2].toObject(), lengthTracking);
|
||||
if (!obj)
|
||||
return false;
|
||||
args.rval().setObject(*obj);
|
||||
@@ -1159,7 +1494,7 @@ bool
|
||||
ArrayBufferObject::createDataViewForThis(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsArrayBuffer, createDataViewForThisImpl>(cx, args);
|
||||
return CallNonGenericMethod<IsAnyArrayBuffer, createDataViewForThisImpl>(cx, args);
|
||||
}
|
||||
|
||||
/* static */ ArrayBufferObject::BufferContents
|
||||
@@ -1528,6 +1863,8 @@ ArrayBufferViewObject::trace(JSTracer* trc, JSObject* objArg)
|
||||
// The data may or may not be inline with the buffer. The buffer
|
||||
// can only move during a compacting GC, in which case its
|
||||
// objectMoved hook has already updated the buffer's data pointer.
|
||||
if (offset > buf.byteLength())
|
||||
offset = 0;
|
||||
obj->initPrivate(buf.dataPointer() + offset);
|
||||
}
|
||||
}
|
||||
@@ -1578,6 +1915,8 @@ ArrayBufferViewObject::dataPointerUnshared(const JS::AutoRequireNoGC& nogc)
|
||||
bool
|
||||
ArrayBufferViewObject::isSharedMemory()
|
||||
{
|
||||
if (is<DataViewObject>())
|
||||
return as<DataViewObject>().isSharedMemory();
|
||||
if (is<TypedArrayObject>())
|
||||
return as<TypedArrayObject>().isSharedMemory();
|
||||
return false;
|
||||
@@ -1609,7 +1948,7 @@ ArrayBufferViewObject::bufferObject(JSContext* cx, Handle<ArrayBufferViewObject*
|
||||
return thisObject->as<TypedArrayObject>().bufferEither();
|
||||
}
|
||||
MOZ_ASSERT(thisObject->is<DataViewObject>());
|
||||
return &thisObject->as<DataViewObject>().arrayBuffer();
|
||||
return &thisObject->as<DataViewObject>().arrayBufferEither();
|
||||
}
|
||||
|
||||
/* JS Friend API */
|
||||
@@ -1860,7 +2199,7 @@ JS_GetArrayBufferViewData(JSObject* obj, bool* isSharedMemory, const JS::AutoChe
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
if (obj->is<DataViewObject>()) {
|
||||
*isSharedMemory = false;
|
||||
*isSharedMemory = obj->as<DataViewObject>().isSharedMemory();
|
||||
return obj->as<DataViewObject>().dataPointer();
|
||||
}
|
||||
TypedArrayObject& ta = obj->as<TypedArrayObject>();
|
||||
@@ -1930,7 +2269,7 @@ js::GetArrayBufferViewLengthAndData(JSObject* obj, uint32_t* length, bool* isSha
|
||||
: obj->as<TypedArrayObject>().byteLength();
|
||||
|
||||
if (obj->is<DataViewObject>()) {
|
||||
*isSharedMemory = false;
|
||||
*isSharedMemory = obj->as<DataViewObject>().isSharedMemory();
|
||||
*data = static_cast<uint8_t*>(obj->as<DataViewObject>().dataPointer());
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -82,7 +82,7 @@ ArrayBufferObjectMaybeShared& AsAnyArrayBuffer(HandleValue val);
|
||||
class ArrayBufferObjectMaybeShared : public NativeObject
|
||||
{
|
||||
public:
|
||||
uint32_t byteLength() {
|
||||
uint32_t byteLength() const {
|
||||
return AnyArrayBufferByteLength(this);
|
||||
}
|
||||
|
||||
@@ -128,15 +128,22 @@ typedef MutableHandle<ArrayBufferObjectMaybeShared*> MutableHandleArrayBufferObj
|
||||
class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
{
|
||||
static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);
|
||||
static bool maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args);
|
||||
static bool resizableGetterImpl(JSContext* cx, const CallArgs& args);
|
||||
static bool detachedGetterImpl(JSContext* cx, const CallArgs& args);
|
||||
static bool fun_slice_impl(JSContext* cx, const CallArgs& args);
|
||||
static bool fun_resize_impl(JSContext* cx, const CallArgs& args);
|
||||
static bool fun_transfer_impl(JSContext* cx, const CallArgs& args);
|
||||
static bool fun_transferToFixedLength_impl(JSContext* cx, const CallArgs& args);
|
||||
|
||||
public:
|
||||
static const uint8_t DATA_SLOT = 0;
|
||||
static const uint8_t BYTE_LENGTH_SLOT = 1;
|
||||
static const uint8_t FIRST_VIEW_SLOT = 2;
|
||||
static const uint8_t FLAGS_SLOT = 3;
|
||||
static const uint8_t MAX_BYTE_LENGTH_SLOT = 4;
|
||||
|
||||
static const uint8_t RESERVED_SLOTS = 4;
|
||||
static const uint8_t RESERVED_SLOTS = 5;
|
||||
|
||||
static const size_t ARRAY_BUFFER_ALIGNMENT = 8;
|
||||
|
||||
@@ -189,7 +196,11 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
|
||||
// This PLAIN or WASM buffer has been prepared for asm.js and cannot
|
||||
// henceforth be transferred/detached.
|
||||
FOR_ASMJS = 0x40
|
||||
FOR_ASMJS = 0x40,
|
||||
|
||||
// This buffer was created with [[ArrayBufferMaxByteLength]] and can
|
||||
// be resized up to that maximum.
|
||||
RESIZABLE = 0x80
|
||||
};
|
||||
|
||||
static_assert(JS_ARRAYBUFFER_DETACHED_FLAG == DETACHED,
|
||||
@@ -230,8 +241,14 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
static const Class class_;
|
||||
|
||||
static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool resizableGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool detachedGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
static bool fun_slice(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool fun_resize(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool fun_transfer(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool fun_transferToFixedLength(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
static bool fun_isView(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
@@ -243,7 +260,9 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
BufferContents contents,
|
||||
OwnsState ownsState = OwnsData,
|
||||
HandleObject proto = nullptr,
|
||||
NewObjectKind newKind = GenericObject);
|
||||
NewObjectKind newKind = GenericObject,
|
||||
uint32_t maxByteLength = 0,
|
||||
bool resizable = false);
|
||||
static ArrayBufferObject* create(JSContext* cx, uint32_t nbytes,
|
||||
HandleObject proto = nullptr,
|
||||
NewObjectKind newKind = GenericObject);
|
||||
@@ -294,6 +313,8 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
|
||||
void setNewData(FreeOp* fop, BufferContents newContents, OwnsState ownsState);
|
||||
void changeContents(JSContext* cx, BufferContents newContents, OwnsState ownsState);
|
||||
void changeContentsForResize(JSContext* cx, BufferContents newContents,
|
||||
OwnsState ownsState, uint32_t newByteLength);
|
||||
|
||||
// Detach this buffer from its original memory. (This necessarily makes
|
||||
// views of this buffer unusable for modifying that original memory.)
|
||||
@@ -302,7 +323,8 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
|
||||
private:
|
||||
void changeViewContents(JSContext* cx, ArrayBufferViewObject* view,
|
||||
uint8_t* oldDataPointer, BufferContents newContents);
|
||||
uint8_t* oldDataPointer, BufferContents newContents,
|
||||
uint32_t newByteLength);
|
||||
void setFirstView(ArrayBufferViewObject* view);
|
||||
|
||||
uint8_t* inlineDataPointer() const;
|
||||
@@ -311,6 +333,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
uint8_t* dataPointer() const;
|
||||
SharedMem<uint8_t*> dataPointerShared() const;
|
||||
uint32_t byteLength() const;
|
||||
uint32_t maxByteLength() const;
|
||||
|
||||
BufferContents contents() const {
|
||||
return BufferContents(dataPointer(), bufferKind());
|
||||
@@ -334,6 +357,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
bool isWasm() const { return bufferKind() == WASM; }
|
||||
bool isMapped() const { return bufferKind() == MAPPED; }
|
||||
bool isDetached() const { return flags() & DETACHED; }
|
||||
bool isResizable() const { return flags() & RESIZABLE; }
|
||||
bool isPreparedForAsmJS() const { return flags() & FOR_ASMJS; }
|
||||
|
||||
// WebAssembly support:
|
||||
@@ -391,12 +415,17 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
|
||||
void setIsDetached() { setFlags(flags() | DETACHED); }
|
||||
void setIsPreparedForAsmJS() { setFlags(flags() | FOR_ASMJS); }
|
||||
void setIsResizable() { setFlags(flags() | RESIZABLE); }
|
||||
|
||||
void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState) {
|
||||
void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState,
|
||||
uint32_t maxByteLength = 0, bool resizable = false) {
|
||||
setByteLength(byteLength);
|
||||
setFlags(0);
|
||||
setFixedSlot(MAX_BYTE_LENGTH_SLOT, Int32Value(maxByteLength ? maxByteLength : byteLength));
|
||||
setFirstView(nullptr);
|
||||
setDataPointer(contents, ownsState);
|
||||
if (resizable)
|
||||
setIsResizable();
|
||||
}
|
||||
|
||||
// Note: initialize() may be called after initEmpty(); initEmpty() must
|
||||
@@ -404,6 +433,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
void initEmpty() {
|
||||
setByteLength(0);
|
||||
setFlags(0);
|
||||
setFixedSlot(MAX_BYTE_LENGTH_SLOT, Int32Value(0));
|
||||
setFirstView(nullptr);
|
||||
setDataPointer(BufferContents::createPlain(nullptr), DoesntOwnData);
|
||||
}
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
macro(lookupSetter, lookupSetter, "__lookupSetter__") \
|
||||
macro(MapConstructorInit, MapConstructorInit, "MapConstructorInit") \
|
||||
macro(MapIterator, MapIterator, "Map Iterator") \
|
||||
macro(maxByteLength, maxByteLength, "maxByteLength") \
|
||||
macro(maximumFractionDigits, maximumFractionDigits, "maximumFractionDigits") \
|
||||
macro(maximumSignificantDigits, maximumSignificantDigits, "maximumSignificantDigits") \
|
||||
macro(message, message, "message") \
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "builtin/AtomicsObject.h"
|
||||
#include "builtin/BigInt.h"
|
||||
#include "builtin/Eval.h"
|
||||
#include "builtin/FinalizationRegistryObject.h"
|
||||
#include "builtin/MapObject.h"
|
||||
#include "builtin/ModuleObject.h"
|
||||
#include "builtin/Object.h"
|
||||
@@ -538,6 +539,7 @@ GlobalObject::initSelfHostingBuiltins(JSContext* cx, Handle<GlobalObject*> globa
|
||||
InitBareBuiltinCtor(cx, global, JSProto_Uint8Array) &&
|
||||
InitBareBuiltinCtor(cx, global, JSProto_Int32Array) &&
|
||||
InitBareSymbolCtor(cx, global) &&
|
||||
InitBareFinalizationRegistryCtor(cx, global) &&
|
||||
InitBareWeakMapCtor(cx, global) &&
|
||||
InitBareWeakRefCtor(cx, global) &&
|
||||
InitStopIterationClass(cx, global) &&
|
||||
|
||||
+11
-10
@@ -478,7 +478,8 @@ js::CancelOffThreadParses(JSRuntime* rt)
|
||||
}
|
||||
|
||||
// Clean up any parse tasks which haven't been finished by the main thread.
|
||||
GlobalHelperThreadState::ParseTaskVector& finished = HelperThreadState().parseFinishedList(lock);
|
||||
GlobalHelperThreadState::ParseTaskVector& finished =
|
||||
HelperThreadState().parseFinishedList(lock);
|
||||
while (true) {
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < finished.length(); i++) {
|
||||
@@ -486,7 +487,8 @@ js::CancelOffThreadParses(JSRuntime* rt)
|
||||
if (task->runtimeMatches(rt)) {
|
||||
found = true;
|
||||
AutoUnlockHelperThreadState unlock(lock);
|
||||
HelperThreadState().cancelParseTask(rt->contextFromMainThread(), task->kind, task);
|
||||
HelperThreadState().cancelParseTask(rt->contextFromMainThread(),
|
||||
task->kind, task);
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
@@ -967,8 +969,7 @@ GlobalHelperThreadState::maxGCParallelThreads() const
|
||||
bool
|
||||
GlobalHelperThreadState::canStartWasmCompile(const AutoLockHelperThreadState& lock)
|
||||
{
|
||||
// Don't execute an wasm job if an earlier one failed.
|
||||
if (wasmWorklist(lock).empty() || numWasmFailedJobs)
|
||||
if (wasmWorklist(lock).empty())
|
||||
return false;
|
||||
|
||||
// Honor the maximum allowed threads to compile wasm jobs at once,
|
||||
@@ -1418,13 +1419,13 @@ HelperThread::handleWasmWorkload(AutoLockHelperThreadState& locked)
|
||||
success = wasm::CompileFunction(task);
|
||||
}
|
||||
|
||||
// On success, try to move work to the finished list.
|
||||
if (success)
|
||||
success = HelperThreadState().wasmFinishedList(locked).append(task);
|
||||
|
||||
// On failure, note the failure for harvesting by the parent.
|
||||
// Append the task to the finished queue owned by its module generator.
|
||||
if (!success)
|
||||
HelperThreadState().noteWasmFailure(locked);
|
||||
task->setFailed();
|
||||
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
if (!task->finishedList()->append(task))
|
||||
oomUnsafe.crash("HelperThread::handleWasmWorkload");
|
||||
|
||||
// Notify the main thread in case it's waiting.
|
||||
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
|
||||
|
||||
@@ -89,8 +89,8 @@ class GlobalHelperThreadState
|
||||
wasm::IonCompileTaskPtrVector wasmWorklist_, wasmFinishedList_;
|
||||
|
||||
public:
|
||||
// For now, only allow a single parallel wasm compilation to happen at a
|
||||
// time. This avoids race conditions on wasmWorklist/wasmFinishedList/etc.
|
||||
// Helper-thread initiated wasm compilations are serialized to avoid the
|
||||
// deadlock scenario described in WasmGenerator.cpp.
|
||||
mozilla::Atomic<bool> wasmCompilationInProgress;
|
||||
|
||||
private:
|
||||
|
||||
@@ -43,6 +43,22 @@ static const ObjectElements emptyElementsHeaderShared(0, 0, ObjectElements::Shar
|
||||
HeapSlot* const js::emptyObjectElementsShared =
|
||||
reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeaderShared) + sizeof(ObjectElements));
|
||||
|
||||
static const ObjectElements emptyElementsHeaderResizableOrGrowable(
|
||||
0, 0, ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER);
|
||||
|
||||
/* Objects with no elements share one empty set of elements. */
|
||||
HeapSlot* const js::emptyObjectElementsResizableOrGrowable =
|
||||
reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeaderResizableOrGrowable) +
|
||||
sizeof(ObjectElements));
|
||||
|
||||
static const ObjectElements emptyElementsHeaderSharedResizableOrGrowable(
|
||||
0, 0, ObjectElements::SHARED_MEMORY | ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER);
|
||||
|
||||
/* Objects with no elements share one empty set of elements. */
|
||||
HeapSlot* const js::emptyObjectElementsSharedResizableOrGrowable =
|
||||
reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeaderSharedResizableOrGrowable) +
|
||||
sizeof(ObjectElements));
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
@@ -61,10 +77,12 @@ ObjectElements::ConvertElementsToDoubles(JSContext* cx, uintptr_t elementsPtr)
|
||||
* This function is infallible, but has a fallible interface so that it can
|
||||
* be called directly from Ion code. Only arrays can have their dense
|
||||
* elements converted to doubles, and arrays never have empty elements.
|
||||
*/
|
||||
*/
|
||||
HeapSlot* elementsHeapPtr = (HeapSlot*) elementsPtr;
|
||||
MOZ_ASSERT(elementsHeapPtr != emptyObjectElements &&
|
||||
elementsHeapPtr != emptyObjectElementsShared);
|
||||
elementsHeapPtr != emptyObjectElementsShared &&
|
||||
elementsHeapPtr != emptyObjectElementsResizableOrGrowable &&
|
||||
elementsHeapPtr != emptyObjectElementsSharedResizableOrGrowable);
|
||||
|
||||
ObjectElements* header = ObjectElements::fromElements(elementsHeapPtr);
|
||||
MOZ_ASSERT(!header->shouldConvertDoubleElements());
|
||||
|
||||
@@ -185,6 +185,11 @@ class ObjectElements
|
||||
|
||||
// These elements are set to integrity level "frozen".
|
||||
FROZEN = 0x10,
|
||||
|
||||
// For TypedArrays only: this TypedArray views a resizable
|
||||
// ArrayBuffer or growable SharedArrayBuffer. JIT fast paths with
|
||||
// cached length/data assumptions must fall back for these objects.
|
||||
RESIZABLE_OR_GROWABLE_BUFFER = 0x20,
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -255,6 +260,10 @@ class ObjectElements
|
||||
: flags(SHARED_MEMORY), initializedLength(0), capacity(capacity), length(length)
|
||||
{}
|
||||
|
||||
constexpr ObjectElements(uint32_t capacity, uint32_t length, uint32_t flags)
|
||||
: flags(flags), initializedLength(0), capacity(capacity), length(length)
|
||||
{}
|
||||
|
||||
HeapSlot* elements() {
|
||||
return reinterpret_cast<HeapSlot*>(uintptr_t(this) + sizeof(ObjectElements));
|
||||
}
|
||||
@@ -269,6 +278,10 @@ class ObjectElements
|
||||
return flags & SHARED_MEMORY;
|
||||
}
|
||||
|
||||
bool hasResizableOrGrowableBuffer() const {
|
||||
return flags & RESIZABLE_OR_GROWABLE_BUFFER;
|
||||
}
|
||||
|
||||
GCPtrNativeObject& ownerObject() const {
|
||||
MOZ_ASSERT(isCopyOnWrite());
|
||||
return *(GCPtrNativeObject*)(&elements()[initializedLength]);
|
||||
@@ -326,6 +339,8 @@ static_assert(ObjectElements::VALUES_PER_HEADER * sizeof(HeapSlot) == sizeof(Obj
|
||||
*/
|
||||
extern HeapSlot* const emptyObjectElements;
|
||||
extern HeapSlot* const emptyObjectElementsShared;
|
||||
extern HeapSlot* const emptyObjectElementsResizableOrGrowable;
|
||||
extern HeapSlot* const emptyObjectElementsSharedResizableOrGrowable;
|
||||
|
||||
struct Class;
|
||||
class GCMarker;
|
||||
@@ -480,6 +495,12 @@ class NativeObject : public ShapedObject
|
||||
elements_ = emptyObjectElementsShared;
|
||||
}
|
||||
|
||||
void setHasResizableOrGrowableBuffer() {
|
||||
MOZ_ASSERT(elements_ == emptyObjectElements || elements_ == emptyObjectElementsShared);
|
||||
elements_ = isSharedMemory() ? emptyObjectElementsSharedResizableOrGrowable
|
||||
: emptyObjectElementsResizableOrGrowable;
|
||||
}
|
||||
|
||||
bool isInWholeCellBuffer() const {
|
||||
const gc::TenuredCell* cell = &asTenured();
|
||||
gc::ArenaCellSet* cells = cell->arena()->bufferedCells;
|
||||
@@ -1254,7 +1275,10 @@ class NativeObject : public ShapedObject
|
||||
}
|
||||
|
||||
inline bool hasEmptyElements() const {
|
||||
return elements_ == emptyObjectElements || elements_ == emptyObjectElementsShared;
|
||||
return elements_ == emptyObjectElements ||
|
||||
elements_ == emptyObjectElementsShared ||
|
||||
elements_ == emptyObjectElementsResizableOrGrowable ||
|
||||
elements_ == emptyObjectElementsSharedResizableOrGrowable;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -194,6 +194,8 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
|
||||
simulator_(nullptr),
|
||||
#endif
|
||||
scriptAndCountsVector(nullptr),
|
||||
weakRefKeptObjects(nullptr),
|
||||
finalizationRegistryCleanupJobs(nullptr),
|
||||
lcovOutput(),
|
||||
NaNValue(DoubleNaNValue()),
|
||||
negativeInfinityValue(DoubleValue(NegativeInfinity<double>())),
|
||||
@@ -372,6 +374,9 @@ JSRuntime::destroyRuntime()
|
||||
MOZ_ASSERT(!isHeapBusy());
|
||||
MOZ_ASSERT(childRuntimeCount == 0);
|
||||
|
||||
clearWeakRefKeptObjects();
|
||||
clearFinalizationRegistryCleanupJobs();
|
||||
|
||||
fx.destroyInstance();
|
||||
|
||||
sharedIntlData.destroyInstance();
|
||||
@@ -460,6 +465,129 @@ JSRuntime::destroyRuntime()
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool
|
||||
SameWeakRefKeptObject(const JS::Value& kept, JS::HandleValue target)
|
||||
{
|
||||
MOZ_ASSERT(kept.isObject() || kept.isSymbol());
|
||||
MOZ_ASSERT(target.isObject() || target.isSymbol());
|
||||
|
||||
if (kept.isObject())
|
||||
return target.isObject() && &kept.toObject() == &target.toObject();
|
||||
|
||||
return target.isSymbol() && kept.toSymbol() == target.toSymbol();
|
||||
}
|
||||
|
||||
bool
|
||||
JSRuntime::addWeakRefKeptObject(JSContext* cx, JS::HandleValue target)
|
||||
{
|
||||
MOZ_ASSERT(cx->runtime() == this);
|
||||
MOZ_ASSERT(target.isObject() || target.isSymbol());
|
||||
MOZ_ASSERT(!isHeapBusy());
|
||||
|
||||
if (!weakRefKeptObjects) {
|
||||
auto* keptObjects =
|
||||
cx->new_<JS::PersistentRooted<js::WeakRefKeptObjectVector>>(
|
||||
cx, js::WeakRefKeptObjectVector(js::SystemAllocPolicy()));
|
||||
if (!keptObjects)
|
||||
return false;
|
||||
|
||||
weakRefKeptObjects = keptObjects;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < weakRefKeptObjects->length(); i++) {
|
||||
const JS::Value& kept = (*weakRefKeptObjects)[i];
|
||||
if (SameWeakRefKeptObject(kept, target))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!weakRefKeptObjects->append(target.get())) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::clearWeakRefKeptObjects()
|
||||
{
|
||||
MOZ_ASSERT(!isHeapBusy());
|
||||
|
||||
if (!weakRefKeptObjects)
|
||||
return;
|
||||
|
||||
defaultFreeOp()->delete_(weakRefKeptObjects);
|
||||
weakRefKeptObjects = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
JSRuntime::enqueueFinalizationRegistryCleanupJob(JSContext* cx, JS::HandleObject job)
|
||||
{
|
||||
MOZ_ASSERT(cx->runtime() == this);
|
||||
MOZ_ASSERT(job);
|
||||
MOZ_ASSERT(job->is<JSFunction>());
|
||||
MOZ_ASSERT(isHeapBusy());
|
||||
|
||||
if (!finalizationRegistryCleanupJobs) {
|
||||
auto* cleanupJobs =
|
||||
cx->new_<JS::PersistentRooted<js::FinalizationRegistryCleanupJobVector>>(
|
||||
cx, js::FinalizationRegistryCleanupJobVector(js::SystemAllocPolicy()));
|
||||
if (!cleanupJobs)
|
||||
return false;
|
||||
|
||||
finalizationRegistryCleanupJobs = cleanupJobs;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < finalizationRegistryCleanupJobs->length(); i++) {
|
||||
if ((*finalizationRegistryCleanupJobs)[i] == job)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!finalizationRegistryCleanupJobs->append(job)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
JSRuntime::drainFinalizationRegistryCleanupJobs(JSContext* cx)
|
||||
{
|
||||
MOZ_ASSERT(cx->runtime() == this);
|
||||
MOZ_ASSERT(!isHeapBusy());
|
||||
|
||||
if (!finalizationRegistryCleanupJobs)
|
||||
return true;
|
||||
|
||||
if (!enqueuePromiseJobCallback) {
|
||||
finalizationRegistryCleanupJobs->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t length = finalizationRegistryCleanupJobs->length();
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
RootedFunction job(cx, &(*finalizationRegistryCleanupJobs)[i]->as<JSFunction>());
|
||||
if (!enqueuePromiseJob(cx, job, nullptr, nullptr))
|
||||
return false;
|
||||
}
|
||||
|
||||
finalizationRegistryCleanupJobs->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::clearFinalizationRegistryCleanupJobs()
|
||||
{
|
||||
MOZ_ASSERT(!isHeapBusy());
|
||||
|
||||
if (!finalizationRegistryCleanupJobs)
|
||||
return;
|
||||
|
||||
defaultFreeOp()->delete_(finalizationRegistryCleanupJobs);
|
||||
finalizationRegistryCleanupJobs = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes* rtSizes)
|
||||
{
|
||||
@@ -673,6 +801,12 @@ JSRuntime::traceSharedIntlData(JSTracer* trc)
|
||||
void
|
||||
JSRuntime::triggerActivityCallback(bool active)
|
||||
{
|
||||
if (active) {
|
||||
gc.notifyJSExecutionStart();
|
||||
} else {
|
||||
gc.notifyJSExecutionEnd();
|
||||
}
|
||||
|
||||
if (!activityCallback)
|
||||
return;
|
||||
|
||||
|
||||
@@ -360,6 +360,8 @@ class PerThreadData
|
||||
};
|
||||
|
||||
using ScriptAndCountsVector = GCVector<ScriptAndCounts, 0, SystemAllocPolicy>;
|
||||
using WeakRefKeptObjectVector = JS::GCVector<JS::Value, 0, SystemAllocPolicy>;
|
||||
using FinalizationRegistryCleanupJobVector = JS::GCVector<JSObject*, 0, SystemAllocPolicy>;
|
||||
|
||||
class AutoLockForExclusiveAccess;
|
||||
} // namespace js
|
||||
@@ -887,6 +889,19 @@ struct JSRuntime : public JS::shadow::Runtime,
|
||||
/* Strong references on scripts held for PCCount profiling API. */
|
||||
JS::PersistentRooted<js::ScriptAndCountsVector>* scriptAndCountsVector;
|
||||
|
||||
/* Strong references to live WeakRef targets kept until the next job boundary. */
|
||||
JS::PersistentRooted<js::WeakRefKeptObjectVector>* weakRefKeptObjects;
|
||||
|
||||
[[nodiscard]] bool addWeakRefKeptObject(JSContext* cx, JS::HandleValue target);
|
||||
void clearWeakRefKeptObjects();
|
||||
|
||||
/* Cleanup jobs produced by FinalizationRegistry sweeping, enqueued after GC. */
|
||||
JS::PersistentRooted<js::FinalizationRegistryCleanupJobVector>* finalizationRegistryCleanupJobs;
|
||||
|
||||
[[nodiscard]] bool enqueueFinalizationRegistryCleanupJob(JSContext* cx, JS::HandleObject job);
|
||||
[[nodiscard]] bool drainFinalizationRegistryCleanupJobs(JSContext* cx);
|
||||
void clearFinalizationRegistryCleanupJobs();
|
||||
|
||||
/* Code coverage output. */
|
||||
js::coverage::LCovRuntime lcovOutput;
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "builtin/SelfHostingDefines.h"
|
||||
#include "builtin/Stream.h"
|
||||
#include "builtin/TypedObject.h"
|
||||
#include "builtin/WeakRefObject.h"
|
||||
#include "builtin/WeakSetObject.h"
|
||||
#include "gc/Marking.h"
|
||||
#include "gc/Policy.h"
|
||||
@@ -105,6 +106,14 @@ intrinsic_IsObject(JSContext* cx, unsigned argc, Value* vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_CanBeHeldWeakly(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
args.rval().setBoolean(CanBeHeldWeakly(args.get(0)));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_IsArray(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -270,6 +279,20 @@ intrinsic_GetBuiltinConstructor(JSContext* cx, unsigned argc, Value* vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_NewMap(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 0);
|
||||
|
||||
Rooted<MapObject*> map(cx, MapObject::create(cx));
|
||||
if (!map)
|
||||
return false;
|
||||
|
||||
args.rval().setObject(*map);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_SubstringKernel(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -1287,6 +1310,36 @@ intrinsic_PossiblyWrappedTypedArrayHasDetachedBuffer(JSContext* cx, unsigned arg
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_TypedArrayIsOutOfBounds(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 1);
|
||||
|
||||
RootedObject obj(cx, &args[0].toObject());
|
||||
MOZ_ASSERT(obj->is<TypedArrayObject>());
|
||||
args.rval().setBoolean(obj->as<TypedArrayObject>().isOutOfBounds());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_PossiblyWrappedTypedArrayIsOutOfBounds(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 1);
|
||||
MOZ_ASSERT(args[0].isObject());
|
||||
|
||||
JSObject* obj = CheckedUnwrap(&args[0].toObject());
|
||||
if (!obj) {
|
||||
JS_ReportErrorASCII(cx, "Permission denied to access object");
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(obj->is<TypedArrayObject>());
|
||||
args.rval().setBoolean(obj->as<TypedArrayObject>().isOutOfBounds());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
intrinsic_MoveTypedArrayElements(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -1409,6 +1462,10 @@ intrinsic_SetFromTypedArrayApproach(JSContext* cx, unsigned argc, Value* vp)
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
if (unsafeTypedArrayCrossCompartment->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 21, 23.
|
||||
uint32_t unsafeSrcLengthCrossCompartment = unsafeTypedArrayCrossCompartment->length();
|
||||
@@ -2255,7 +2312,10 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
JS_INLINABLE_FN("std_Math_min", math_min, 2,0, MathMin),
|
||||
JS_INLINABLE_FN("std_Math_abs", math_abs, 1,0, MathAbs),
|
||||
|
||||
JS_FN("std_Map_create", intrinsic_NewMap, 0,0),
|
||||
JS_FN("std_Map_get", MapObject::get, 1,0),
|
||||
JS_FN("std_Map_has", MapObject::has, 1,0),
|
||||
JS_FN("std_Map_set", MapObject::set, 2,0),
|
||||
JS_FN("std_Map_iterator", MapObject::entries, 0,0),
|
||||
|
||||
JS_FN("std_Number_valueOf", num_valueOf, 0,0),
|
||||
@@ -2303,6 +2363,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
// Helper funtions after this point.
|
||||
JS_INLINABLE_FN("ToObject", intrinsic_ToObject, 1,0, IntrinsicToObject),
|
||||
JS_INLINABLE_FN("IsObject", intrinsic_IsObject, 1,0, IntrinsicIsObject),
|
||||
JS_FN("CanBeHeldWeakly", intrinsic_CanBeHeldWeakly, 1,0),
|
||||
JS_INLINABLE_FN("IsArray", intrinsic_IsArray, 1,0, ArrayIsArray),
|
||||
JS_INLINABLE_FN("IsWrappedArrayConstructor", intrinsic_IsWrappedArrayConstructor, 1,0,
|
||||
IntrinsicIsWrappedArrayConstructor),
|
||||
@@ -2462,6 +2523,10 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
1, 0, IntrinsicPossiblyWrappedTypedArrayLength),
|
||||
JS_FN("PossiblyWrappedTypedArrayHasDetachedBuffer",
|
||||
intrinsic_PossiblyWrappedTypedArrayHasDetachedBuffer, 1, 0),
|
||||
JS_FN("TypedArrayIsOutOfBounds",
|
||||
intrinsic_TypedArrayIsOutOfBounds, 1, 0),
|
||||
JS_FN("PossiblyWrappedTypedArrayIsOutOfBounds",
|
||||
intrinsic_PossiblyWrappedTypedArrayIsOutOfBounds, 1, 0),
|
||||
|
||||
JS_FN("MoveTypedArrayElements", intrinsic_MoveTypedArrayElements, 4,0),
|
||||
JS_FN("SetFromTypedArrayApproach",intrinsic_SetFromTypedArrayApproach, 4, 0),
|
||||
|
||||
+156
-15
@@ -8,6 +8,7 @@
|
||||
#include "mozilla/Atomics.h"
|
||||
|
||||
#include "jsfriendapi.h"
|
||||
#include "jsnum.h"
|
||||
#include "jsprf.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
@@ -104,15 +105,18 @@ SharedArrayAllocSize(uint32_t length)
|
||||
}
|
||||
|
||||
SharedArrayRawBuffer*
|
||||
SharedArrayRawBuffer::New(JSContext* cx, uint32_t length)
|
||||
SharedArrayRawBuffer::New(JSContext* cx, uint32_t length, uint32_t maxLength, bool growable)
|
||||
{
|
||||
// The value (uint32_t)-1 is used as a signal in various places,
|
||||
// so guard against it on principle.
|
||||
MOZ_ASSERT(length != (uint32_t)-1);
|
||||
MOZ_ASSERT(maxLength != (uint32_t)-1);
|
||||
MOZ_ASSERT(maxLength >= length);
|
||||
|
||||
// Add a page for the header and round to a page boundary.
|
||||
uint32_t allocSize = SharedArrayAllocSize(length);
|
||||
if (allocSize <= length)
|
||||
uint32_t allocationLength = growable ? maxLength : length;
|
||||
uint32_t allocSize = SharedArrayAllocSize(allocationLength);
|
||||
if (allocSize <= allocationLength)
|
||||
return nullptr;
|
||||
|
||||
// Test >= to guard against the case where multiple extant runtimes
|
||||
@@ -127,7 +131,8 @@ SharedArrayRawBuffer::New(JSContext* cx, uint32_t length)
|
||||
}
|
||||
}
|
||||
|
||||
bool preparedForAsmJS = jit::JitOptions.asmJSAtomicsEnable && IsValidAsmJSHeapLength(length);
|
||||
bool preparedForAsmJS =
|
||||
!growable && jit::JitOptions.asmJSAtomicsEnable && IsValidAsmJSHeapLength(length);
|
||||
|
||||
void* p = nullptr;
|
||||
if (preparedForAsmJS) {
|
||||
@@ -161,8 +166,9 @@ SharedArrayRawBuffer::New(JSContext* cx, uint32_t length)
|
||||
|
||||
uint8_t* buffer = reinterpret_cast<uint8_t*>(p) + gc::SystemPageSize();
|
||||
uint8_t* base = buffer - sizeof(SharedArrayRawBuffer);
|
||||
SharedArrayRawBuffer* rawbuf = new (base) SharedArrayRawBuffer(buffer, length, preparedForAsmJS);
|
||||
MOZ_ASSERT(rawbuf->length == length); // Deallocation needs this
|
||||
SharedArrayRawBuffer* rawbuf =
|
||||
new (base) SharedArrayRawBuffer(buffer, length, maxLength, growable, preparedForAsmJS);
|
||||
MOZ_ASSERT(rawbuf->allocatedByteLength() == allocationLength); // Deallocation needs this.
|
||||
return rawbuf;
|
||||
}
|
||||
|
||||
@@ -201,7 +207,7 @@ SharedArrayRawBuffer::dropReference()
|
||||
MOZ_ASSERT(p.asValue() % gc::SystemPageSize() == 0);
|
||||
|
||||
uint8_t* address = p.unwrap(/*safe - only reference*/);
|
||||
uint32_t allocSize = SharedArrayAllocSize(this->length);
|
||||
uint32_t allocSize = SharedArrayAllocSize(this->allocatedByteLength());
|
||||
|
||||
if (this->preparedForAsmJS) {
|
||||
uint32_t mappedSize = SharedArrayMappedSize(allocSize);
|
||||
@@ -221,6 +227,23 @@ SharedArrayRawBuffer::dropReference()
|
||||
numLive--;
|
||||
}
|
||||
|
||||
bool
|
||||
SharedArrayRawBuffer::growTo(uint32_t newLength)
|
||||
{
|
||||
MOZ_ASSERT(growable);
|
||||
MOZ_ASSERT(newLength <= maxLength);
|
||||
|
||||
for (;;) {
|
||||
uint32_t oldLength = length;
|
||||
if (newLength < oldLength)
|
||||
return false;
|
||||
if (newLength == oldLength)
|
||||
return true;
|
||||
if (length.compareExchange(oldLength, newLength))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
SharedArrayBufferObject::byteLengthGetterImpl(JSContext* cx, const CallArgs& args)
|
||||
@@ -237,6 +260,112 @@ SharedArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc, Value* v
|
||||
return CallNonGenericMethod<IsSharedArrayBuffer, byteLengthGetterImpl>(cx, args);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
SharedArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
|
||||
args.rval().setInt32(args.thisv().toObject().as<SharedArrayBufferObject>().maxByteLength());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SharedArrayBufferObject::maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsSharedArrayBuffer, maxByteLengthGetterImpl>(cx, args);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
SharedArrayBufferObject::growableGetterImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
|
||||
args.rval().setBoolean(args.thisv().toObject().as<SharedArrayBufferObject>().isGrowable());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SharedArrayBufferObject::growableGetter(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsSharedArrayBuffer, growableGetterImpl>(cx, args);
|
||||
}
|
||||
|
||||
static bool
|
||||
ReportSharedArrayBufferNotGrowable(JSContext* cx)
|
||||
{
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHARED_ARRAY_NOT_GROWABLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
ReportSharedArrayBufferLengthOutOfRange(JSContext* cx)
|
||||
{
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHARED_ARRAY_BAD_LENGTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
GetSharedArrayBufferMaxByteLengthOption(JSContext* cx, HandleValue options,
|
||||
uint32_t byteLength, uint32_t* maxByteLength,
|
||||
bool* growable)
|
||||
{
|
||||
*maxByteLength = byteLength;
|
||||
*growable = false;
|
||||
|
||||
if (!options.isObject())
|
||||
return true;
|
||||
|
||||
RootedObject opts(cx, &options.toObject());
|
||||
RootedValue maxByteLengthValue(cx);
|
||||
if (!GetProperty(cx, opts, opts, cx->names().maxByteLength, &maxByteLengthValue))
|
||||
return false;
|
||||
|
||||
if (maxByteLengthValue.isUndefined())
|
||||
return true;
|
||||
|
||||
uint64_t max;
|
||||
if (!ToIndex(cx, maxByteLengthValue, &max))
|
||||
return false;
|
||||
|
||||
if (max > INT32_MAX || max < byteLength)
|
||||
return ReportSharedArrayBufferLengthOutOfRange(cx);
|
||||
|
||||
*maxByteLength = uint32_t(max);
|
||||
*growable = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
SharedArrayBufferObject::fun_grow_impl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
|
||||
|
||||
Rooted<SharedArrayBufferObject*> buffer(cx,
|
||||
&args.thisv().toObject().as<SharedArrayBufferObject>());
|
||||
if (!buffer->isGrowable())
|
||||
return ReportSharedArrayBufferNotGrowable(cx);
|
||||
|
||||
uint64_t newByteLength;
|
||||
if (!ToIndex(cx, args.get(0), &newByteLength))
|
||||
return false;
|
||||
if (newByteLength > INT32_MAX)
|
||||
return ReportSharedArrayBufferLengthOutOfRange(cx);
|
||||
|
||||
uint32_t newLength = uint32_t(newByteLength);
|
||||
if (newLength > buffer->maxByteLength() || !buffer->growTo(newLength))
|
||||
return ReportSharedArrayBufferLengthOutOfRange(cx);
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SharedArrayBufferObject::fun_grow(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsSharedArrayBuffer, fun_grow_impl>(cx, args);
|
||||
}
|
||||
|
||||
bool
|
||||
SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
@@ -245,11 +374,19 @@ SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value*
|
||||
if (!ThrowIfNotConstructing(cx, args, "SharedArrayBuffer"))
|
||||
return false;
|
||||
|
||||
uint64_t length64;
|
||||
if (!ToIndex(cx, args.get(0), &length64))
|
||||
return false;
|
||||
// Bugs 1068458, 1161298: Limit length to 2^31-1.
|
||||
uint32_t length;
|
||||
bool overflow_unused;
|
||||
if (!ToLengthClamped(cx, args.get(0), &length, &overflow_unused) || length > INT32_MAX) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHARED_ARRAY_BAD_LENGTH);
|
||||
if (length64 > INT32_MAX)
|
||||
return ReportSharedArrayBufferLengthOutOfRange(cx);
|
||||
uint32_t length = uint32_t(length64);
|
||||
|
||||
uint32_t maxByteLength;
|
||||
bool growable;
|
||||
if (!GetSharedArrayBufferMaxByteLengthOption(cx, args.get(1), length,
|
||||
&maxByteLength, &growable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -258,7 +395,7 @@ SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value*
|
||||
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
|
||||
return false;
|
||||
|
||||
JSObject* bufobj = New(cx, length, proto);
|
||||
JSObject* bufobj = New(cx, length, maxByteLength, growable, proto);
|
||||
if (!bufobj)
|
||||
return false;
|
||||
args.rval().setObject(*bufobj);
|
||||
@@ -266,9 +403,10 @@ SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value*
|
||||
}
|
||||
|
||||
SharedArrayBufferObject*
|
||||
SharedArrayBufferObject::New(JSContext* cx, uint32_t length, HandleObject proto)
|
||||
SharedArrayBufferObject::New(JSContext* cx, uint32_t length, uint32_t maxLength, bool growable,
|
||||
HandleObject proto)
|
||||
{
|
||||
SharedArrayRawBuffer* buffer = SharedArrayRawBuffer::New(cx, length);
|
||||
SharedArrayRawBuffer* buffer = SharedArrayRawBuffer::New(cx, length, maxLength, growable);
|
||||
if (!buffer)
|
||||
return nullptr;
|
||||
|
||||
@@ -341,7 +479,7 @@ SharedArrayBufferObject::addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSi
|
||||
// just live with the risk.
|
||||
const SharedArrayBufferObject& buf = obj->as<SharedArrayBufferObject>();
|
||||
info->objectsNonHeapElementsShared +=
|
||||
buf.byteLength() / buf.rawBufferObject()->refcount();
|
||||
buf.rawBufferObject()->allocatedByteLength() / buf.rawBufferObject()->refcount();
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
@@ -409,12 +547,15 @@ static const JSPropertySpec static_properties[] = {
|
||||
};
|
||||
|
||||
static const JSFunctionSpec prototype_functions[] = {
|
||||
JS_FN("grow", SharedArrayBufferObject::fun_grow, 1, 0),
|
||||
JS_SELF_HOSTED_FN("slice", "SharedArrayBufferSlice", 2, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
static const JSPropertySpec prototype_properties[] = {
|
||||
JS_PSG("byteLength", SharedArrayBufferObject::byteLengthGetter, 0),
|
||||
JS_PSG("growable", SharedArrayBufferObject::growableGetter, 0),
|
||||
JS_PSG("maxByteLength", SharedArrayBufferObject::maxByteLengthGetter, 0),
|
||||
JS_STRING_SYM_PS(toStringTag, "SharedArrayBuffer", JSPROP_READONLY),
|
||||
JS_PS_END
|
||||
};
|
||||
|
||||
@@ -44,7 +44,9 @@ class SharedArrayRawBuffer
|
||||
{
|
||||
private:
|
||||
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> refcount_;
|
||||
uint32_t length;
|
||||
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> length;
|
||||
uint32_t maxLength;
|
||||
bool growable;
|
||||
bool preparedForAsmJS;
|
||||
|
||||
// A list of structures representing tasks waiting on some
|
||||
@@ -52,9 +54,12 @@ class SharedArrayRawBuffer
|
||||
FutexWaiter* waiters_;
|
||||
|
||||
protected:
|
||||
SharedArrayRawBuffer(uint8_t* buffer, uint32_t length, bool preparedForAsmJS)
|
||||
SharedArrayRawBuffer(uint8_t* buffer, uint32_t length, uint32_t maxLength, bool growable,
|
||||
bool preparedForAsmJS)
|
||||
: refcount_(1),
|
||||
length(length),
|
||||
maxLength(maxLength),
|
||||
growable(growable),
|
||||
preparedForAsmJS(preparedForAsmJS),
|
||||
waiters_(nullptr)
|
||||
{
|
||||
@@ -62,7 +67,11 @@ class SharedArrayRawBuffer
|
||||
}
|
||||
|
||||
public:
|
||||
static SharedArrayRawBuffer* New(JSContext* cx, uint32_t length);
|
||||
static SharedArrayRawBuffer* New(JSContext* cx, uint32_t length, uint32_t maxLength,
|
||||
bool growable);
|
||||
static SharedArrayRawBuffer* New(JSContext* cx, uint32_t length) {
|
||||
return New(cx, length, length, false);
|
||||
}
|
||||
|
||||
// This may be called from multiple threads. The caller must take
|
||||
// care of mutual exclusion.
|
||||
@@ -85,6 +94,20 @@ class SharedArrayRawBuffer
|
||||
return length;
|
||||
}
|
||||
|
||||
uint32_t maxByteLength() const {
|
||||
return growable ? maxLength : byteLength();
|
||||
}
|
||||
|
||||
uint32_t allocatedByteLength() const {
|
||||
return growable ? maxLength : byteLength();
|
||||
}
|
||||
|
||||
bool isGrowable() const {
|
||||
return growable;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool growTo(uint32_t newLength);
|
||||
|
||||
bool isPreparedForAsmJS() const {
|
||||
return preparedForAsmJS;
|
||||
}
|
||||
@@ -117,6 +140,9 @@ class SharedArrayRawBuffer
|
||||
class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
{
|
||||
static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);
|
||||
static bool maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args);
|
||||
static bool growableGetterImpl(JSContext* cx, const CallArgs& args);
|
||||
static bool fun_grow_impl(JSContext* cx, const CallArgs& args);
|
||||
|
||||
public:
|
||||
// RAWBUF_SLOT holds a pointer (as "private" data) to the
|
||||
@@ -128,13 +154,23 @@ class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
static const Class class_;
|
||||
|
||||
static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool growableGetter(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool fun_grow(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
// Create a SharedArrayBufferObject with a new SharedArrayRawBuffer.
|
||||
static SharedArrayBufferObject* New(JSContext* cx,
|
||||
uint32_t length,
|
||||
uint32_t maxLength,
|
||||
bool growable,
|
||||
HandleObject proto = nullptr);
|
||||
static SharedArrayBufferObject* New(JSContext* cx,
|
||||
uint32_t length,
|
||||
HandleObject proto = nullptr) {
|
||||
return New(cx, length, length, false, proto);
|
||||
}
|
||||
|
||||
// Create a SharedArrayBufferObject using an existing SharedArrayRawBuffer.
|
||||
static SharedArrayBufferObject* New(JSContext* cx,
|
||||
@@ -164,6 +200,15 @@ class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
uint32_t byteLength() const {
|
||||
return rawBufferObject()->byteLength();
|
||||
}
|
||||
uint32_t maxByteLength() const {
|
||||
return rawBufferObject()->maxByteLength();
|
||||
}
|
||||
bool isGrowable() const {
|
||||
return rawBufferObject()->isGrowable();
|
||||
}
|
||||
[[nodiscard]] bool growTo(uint32_t newLength) {
|
||||
return rawBufferObject()->growTo(newLength);
|
||||
}
|
||||
bool isPreparedForAsmJS() const {
|
||||
return rawBufferObject()->isPreparedForAsmJS();
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "builtin/MapObject.h"
|
||||
#include "js/Date.h"
|
||||
#include "js/GCHashTable.h"
|
||||
#include "vm/ArrayBufferObject-inl.h"
|
||||
#include "vm/SavedFrame.h"
|
||||
#include "vm/SharedArrayObject.h"
|
||||
#include "vm/TypedArrayObject.h"
|
||||
|
||||
@@ -761,16 +761,40 @@ class TypedArrayMethods
|
||||
if (!ToInt32(cx, args[1], &offset))
|
||||
return false;
|
||||
|
||||
if (offset < 0 || uint32_t(offset) > target->length()) {
|
||||
// the given offset is bogus
|
||||
if (offset < 0) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (target->hasDetachedBuffer()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
if (target->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t targetLength = target->length();
|
||||
if (uint32_t(offset) > targetLength) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedObject arg0(cx, &args[0].toObject());
|
||||
if (arg0->is<TypedArrayObject>()) {
|
||||
if (arg0->as<TypedArrayObject>().length() > target->length() - offset) {
|
||||
Rooted<TypedArrayObject*> source(cx, &arg0->as<TypedArrayObject>());
|
||||
if (source->hasDetachedBuffer()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
if (source->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (source->length() > targetLength - offset) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
|
||||
return false;
|
||||
}
|
||||
@@ -782,7 +806,7 @@ class TypedArrayMethods
|
||||
if (!GetLengthProperty(cx, arg0, &len))
|
||||
return false;
|
||||
|
||||
if (uint32_t(offset) > target->length() || len > target->length() - offset) {
|
||||
if (len > targetLength - offset) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
|
||||
return false;
|
||||
}
|
||||
@@ -795,13 +819,32 @@ class TypedArrayMethods
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
setFromTypedArray(JSContext* cx, Handle<SomeTypedArray*> target, HandleObject source,
|
||||
uint32_t offset = 0)
|
||||
{
|
||||
MOZ_ASSERT(source->is<TypedArrayObject>(), "use setFromNonTypedArray");
|
||||
static bool
|
||||
setFromTypedArray(JSContext* cx, Handle<SomeTypedArray*> target, HandleObject source,
|
||||
uint32_t offset = 0)
|
||||
{
|
||||
MOZ_ASSERT(source->is<TypedArrayObject>(), "use setFromNonTypedArray");
|
||||
|
||||
bool isShared = target->isSharedMemory() || source->as<TypedArrayObject>().isSharedMemory();
|
||||
if (target->hasDetachedBuffer()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
if (target->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<TypedArrayObject*> sourceArray(cx, &source->as<TypedArrayObject>());
|
||||
if (sourceArray->hasDetachedBuffer()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
if (sourceArray->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isShared = target->isSharedMemory() || sourceArray->isSharedMemory();
|
||||
|
||||
switch (target->type()) {
|
||||
case Scalar::Int8:
|
||||
|
||||
+102
-32
@@ -482,8 +482,9 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
}
|
||||
|
||||
static TypedArrayObject*
|
||||
makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, uint32_t byteOffset, uint32_t len,
|
||||
HandleObject proto)
|
||||
makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
|
||||
uint32_t byteOffset, uint32_t len, HandleObject proto,
|
||||
bool lengthTracking = false)
|
||||
{
|
||||
MOZ_ASSERT_IF(!buffer, byteOffset == 0);
|
||||
|
||||
@@ -508,12 +509,19 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
return nullptr;
|
||||
|
||||
bool isSharedMemory = buffer && IsSharedArrayBuffer(buffer.get());
|
||||
bool hasResizableOrGrowableBuffer =
|
||||
buffer &&
|
||||
((buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isResizable()) ||
|
||||
(buffer->is<SharedArrayBufferObject>() &&
|
||||
buffer->as<SharedArrayBufferObject>().isGrowable()));
|
||||
|
||||
obj->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectOrNullValue(buffer));
|
||||
// This is invariant. Self-hosting code that sets BUFFER_SLOT
|
||||
// (if it does) must maintain it, should it need to.
|
||||
if (isSharedMemory)
|
||||
obj->setIsSharedMemory();
|
||||
if (hasResizableOrGrowableBuffer)
|
||||
obj->setHasResizableOrGrowableBuffer();
|
||||
|
||||
if (buffer) {
|
||||
obj->initViewData(buffer->dataPointerEither() + byteOffset);
|
||||
@@ -547,7 +555,9 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
#endif
|
||||
}
|
||||
|
||||
obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(len));
|
||||
obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT,
|
||||
Int32Value(lengthTracking ? TypedArrayObject::LENGTH_TRACKING
|
||||
: int32_t(len)));
|
||||
obj->setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(byteOffset));
|
||||
|
||||
#ifdef DEBUG
|
||||
@@ -896,6 +906,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
return nullptr; // invalid byteOffset
|
||||
}
|
||||
|
||||
bool lengthTracking = false;
|
||||
uint32_t len;
|
||||
if (lengthInt == -1) {
|
||||
len = (buffer->byteLength() - byteOffset) / sizeof(NativeType);
|
||||
@@ -904,6 +915,12 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
|
||||
return nullptr; // given byte array doesn't map exactly to sizeof(NativeType) * N
|
||||
}
|
||||
if ((buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isResizable()) ||
|
||||
(buffer->is<SharedArrayBufferObject>() &&
|
||||
buffer->as<SharedArrayBufferObject>().isGrowable()))
|
||||
{
|
||||
lengthTracking = true;
|
||||
}
|
||||
} else {
|
||||
len = uint32_t(lengthInt);
|
||||
}
|
||||
@@ -922,7 +939,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
return nullptr; // byteOffset + len is too big for the arraybuffer
|
||||
}
|
||||
|
||||
return makeInstance(cx, buffer, byteOffset, len, proto);
|
||||
return makeInstance(cx, buffer, byteOffset, len, proto, lengthTracking);
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -1318,6 +1335,10 @@ TypedArrayObjectTemplate<T>::fromTypedArray(JSContext* cx, HandleObject other, b
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return nullptr;
|
||||
}
|
||||
if (srcArray->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 9.
|
||||
uint32_t elementLength = srcArray->length();
|
||||
@@ -1857,7 +1878,8 @@ DataViewNewObjectKind(JSContext* cx, uint32_t byteLength, JSObject* proto)
|
||||
|
||||
DataViewObject*
|
||||
DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
|
||||
Handle<ArrayBufferObject*> arrayBuffer, JSObject* protoArg)
|
||||
Handle<ArrayBufferObjectMaybeShared*> arrayBuffer, JSObject* protoArg,
|
||||
bool lengthTracking)
|
||||
{
|
||||
if (arrayBuffer->isDetached()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
@@ -1866,8 +1888,18 @@ DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
|
||||
|
||||
MOZ_ASSERT(byteOffset <= INT32_MAX);
|
||||
MOZ_ASSERT(byteLength <= INT32_MAX);
|
||||
MOZ_ASSERT(byteOffset + byteLength < UINT32_MAX);
|
||||
MOZ_ASSERT(!arrayBuffer || !arrayBuffer->is<SharedArrayBufferObject>());
|
||||
|
||||
uint32_t bufferByteLength = arrayBuffer->byteLength();
|
||||
if (byteOffset > bufferByteLength ||
|
||||
(!lengthTracking && byteLength > bufferByteLength - byteOffset))
|
||||
{
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE,
|
||||
"1");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (lengthTracking)
|
||||
byteLength = bufferByteLength - byteOffset;
|
||||
|
||||
RootedObject proto(cx, protoArg);
|
||||
RootedObject obj(cx);
|
||||
@@ -1891,57 +1923,72 @@ DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
|
||||
}
|
||||
}
|
||||
|
||||
// Caller should have established these preconditions, and no
|
||||
// (non-self-hosted) JS code has had an opportunity to run so nothing can
|
||||
// have invalidated them.
|
||||
MOZ_ASSERT(byteOffset <= arrayBuffer->byteLength());
|
||||
MOZ_ASSERT(byteOffset + byteLength <= arrayBuffer->byteLength());
|
||||
|
||||
DataViewObject& dvobj = obj->as<DataViewObject>();
|
||||
dvobj.setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(byteOffset));
|
||||
dvobj.setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(byteLength));
|
||||
dvobj.setFixedSlot(TypedArrayObject::LENGTH_SLOT,
|
||||
Int32Value(lengthTracking ? TypedArrayObject::LENGTH_TRACKING
|
||||
: int32_t(byteLength)));
|
||||
dvobj.setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectValue(*arrayBuffer));
|
||||
dvobj.initPrivate(arrayBuffer->dataPointer() + byteOffset);
|
||||
auto dataPointer = arrayBuffer->dataPointerEither();
|
||||
dvobj.initPrivate((dataPointer + byteOffset).unwrap(/*safe - stored as private data*/));
|
||||
|
||||
// Include a barrier if the data view's data pointer is in the nursery, as
|
||||
// is done for typed arrays.
|
||||
if (!IsInsideNursery(obj) && cx->runtime()->gc.nursery.isInside(arrayBuffer->dataPointer()))
|
||||
if (arrayBuffer->is<ArrayBufferObject>() &&
|
||||
!IsInsideNursery(obj) &&
|
||||
cx->runtime()->gc.nursery.isInside(dataPointer))
|
||||
{
|
||||
cx->runtime()->gc.storeBuffer.putWholeCell(obj);
|
||||
}
|
||||
|
||||
// Verify that the private slot is at the expected place
|
||||
MOZ_ASSERT(dvobj.numFixedSlots() == TypedArrayObject::DATA_SLOT);
|
||||
|
||||
if (!arrayBuffer->addView(cx, &dvobj))
|
||||
return nullptr;
|
||||
if (arrayBuffer->is<ArrayBufferObject>()) {
|
||||
if (!arrayBuffer->as<ArrayBufferObject>().addView(cx, &dvobj))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &dvobj;
|
||||
}
|
||||
|
||||
bool
|
||||
DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, const CallArgs& args,
|
||||
uint32_t* byteOffsetPtr, uint32_t* byteLengthPtr)
|
||||
uint32_t* byteOffsetPtr, uint32_t* byteLengthPtr,
|
||||
bool* lengthTrackingPtr)
|
||||
{
|
||||
if (!IsArrayBuffer(bufobj)) {
|
||||
if (!IsAnyArrayBuffer(bufobj)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
|
||||
"DataView", "ArrayBuffer", bufobj->getClass()->name);
|
||||
"DataView", "ArrayBuffer or SharedArrayBuffer",
|
||||
bufobj->getClass()->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<ArrayBufferObject*> buffer(cx, &AsArrayBuffer(bufobj));
|
||||
Rooted<ArrayBufferObjectMaybeShared*> buffer(cx, &bufobj->as<ArrayBufferObjectMaybeShared>());
|
||||
uint32_t byteOffset = 0;
|
||||
uint32_t byteLength = buffer->byteLength();
|
||||
bool isResizableOrGrowable =
|
||||
(buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isResizable()) ||
|
||||
(buffer->is<SharedArrayBufferObject>() &&
|
||||
buffer->as<SharedArrayBufferObject>().isGrowable());
|
||||
bool lengthTracking = isResizableOrGrowable && !args.hasDefined(2);
|
||||
|
||||
if (args.length() > 1) {
|
||||
if (!ToUint32(cx, args[1], &byteOffset))
|
||||
uint64_t offset;
|
||||
if (!ToIndex(cx, args[1], &offset))
|
||||
return false;
|
||||
if (byteOffset > INT32_MAX) {
|
||||
if (offset > INT32_MAX) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE,
|
||||
"1");
|
||||
return false;
|
||||
}
|
||||
byteOffset = uint32_t(offset);
|
||||
}
|
||||
|
||||
if (buffer->isDetached()) {
|
||||
if (buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isDetached()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
@@ -1955,14 +2002,18 @@ DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, cons
|
||||
|
||||
if (args.get(2).isUndefined()) {
|
||||
byteLength -= byteOffset;
|
||||
lengthTracking = isResizableOrGrowable;
|
||||
} else {
|
||||
if (!ToUint32(cx, args[2], &byteLength))
|
||||
uint64_t viewByteLength;
|
||||
if (!ToIndex(cx, args[2], &viewByteLength))
|
||||
return false;
|
||||
if (byteLength > INT32_MAX) {
|
||||
lengthTracking = false;
|
||||
if (viewByteLength > INT32_MAX) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_ARG_INDEX_OUT_OF_RANGE, "2");
|
||||
return false;
|
||||
}
|
||||
byteLength = uint32_t(viewByteLength);
|
||||
|
||||
MOZ_ASSERT(byteOffset + byteLength >= byteOffset,
|
||||
"can't overflow: both numbers are less than INT32_MAX");
|
||||
@@ -1981,6 +2032,7 @@ DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, cons
|
||||
|
||||
*byteOffsetPtr = byteOffset;
|
||||
*byteLengthPtr = byteLength;
|
||||
*lengthTrackingPtr = lengthTracking;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1992,7 +2044,8 @@ DataViewObject::constructSameCompartment(JSContext* cx, HandleObject bufobj, con
|
||||
assertSameCompartment(cx, bufobj);
|
||||
|
||||
uint32_t byteOffset, byteLength;
|
||||
if (!getAndCheckConstructorArgs(cx, bufobj, args, &byteOffset, &byteLength))
|
||||
bool lengthTracking;
|
||||
if (!getAndCheckConstructorArgs(cx, bufobj, args, &byteOffset, &byteLength, &lengthTracking))
|
||||
return false;
|
||||
|
||||
RootedObject proto(cx);
|
||||
@@ -2000,8 +2053,9 @@ DataViewObject::constructSameCompartment(JSContext* cx, HandleObject bufobj, con
|
||||
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
|
||||
return false;
|
||||
|
||||
Rooted<ArrayBufferObject*> buffer(cx, &AsArrayBuffer(bufobj));
|
||||
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, proto);
|
||||
Rooted<ArrayBufferObjectMaybeShared*> buffer(cx, &bufobj->as<ArrayBufferObjectMaybeShared>());
|
||||
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, proto,
|
||||
lengthTracking);
|
||||
if (!obj)
|
||||
return false;
|
||||
args.rval().setObject(*obj);
|
||||
@@ -2041,8 +2095,12 @@ DataViewObject::constructWrapped(JSContext* cx, HandleObject bufobj, const CallA
|
||||
|
||||
// NB: This entails the IsArrayBuffer check
|
||||
uint32_t byteOffset, byteLength;
|
||||
if (!getAndCheckConstructorArgs(cx, unwrapped, args, &byteOffset, &byteLength))
|
||||
bool lengthTracking;
|
||||
if (!getAndCheckConstructorArgs(cx, unwrapped, args, &byteOffset, &byteLength,
|
||||
&lengthTracking))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure to get the [[Prototype]] for the created view from this
|
||||
// compartment.
|
||||
@@ -2058,11 +2116,12 @@ DataViewObject::constructWrapped(JSContext* cx, HandleObject bufobj, const CallA
|
||||
return false;
|
||||
}
|
||||
|
||||
FixedInvokeArgs<3> args2(cx);
|
||||
FixedInvokeArgs<4> args2(cx);
|
||||
|
||||
args2[0].set(PrivateUint32Value(byteOffset));
|
||||
args2[1].set(PrivateUint32Value(byteLength));
|
||||
args2[2].setObject(*proto);
|
||||
args2[3].setBoolean(lengthTracking);
|
||||
|
||||
RootedValue fval(cx, global->createDataViewForThis());
|
||||
RootedValue thisv(cx, ObjectValue(*bufobj));
|
||||
@@ -2090,6 +2149,11 @@ template <typename NativeType>
|
||||
/* static */ uint8_t*
|
||||
DataViewObject::getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, uint64_t offset)
|
||||
{
|
||||
if (obj->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATA_VIEW_OUT_OF_BOUNDS);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const size_t TypeSize = sizeof(NativeType);
|
||||
if (offset > UINT32_MAX - TypeSize || offset + TypeSize > obj->byteLength()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE,
|
||||
@@ -2194,7 +2258,7 @@ DataViewObject::read(JSContext* cx, Handle<DataViewObject*> obj,
|
||||
bool isLittleEndian = args.length() >= 2 && ToBoolean(args[1]);
|
||||
|
||||
// Steps 6-7.
|
||||
if (obj->arrayBuffer().isDetached()) {
|
||||
if (obj->arrayBufferEither().isDetached()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
@@ -2293,7 +2357,7 @@ DataViewObject::write(JSContext* cx, Handle<DataViewObject*> obj,
|
||||
bool isLittleEndian = args.length() >= 3 && ToBoolean(args[2]);
|
||||
|
||||
// Steps 7-8.
|
||||
if (obj->arrayBuffer().isDetached()) {
|
||||
if (obj->arrayBufferEither().isDetached()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
@@ -3118,7 +3182,13 @@ template<Value ValueGetter(DataViewObject* view)>
|
||||
bool
|
||||
DataViewObject::getterImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
args.rval().set(ValueGetter(&args.thisv().toObject().as<DataViewObject>()));
|
||||
Rooted<DataViewObject*> view(cx, &args.thisv().toObject().as<DataViewObject>());
|
||||
if (ValueGetter != bufferValue && view->isOutOfBounds()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATA_VIEW_OUT_OF_BOUNDS);
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().set(ValueGetter(view));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+121
-20
@@ -52,6 +52,8 @@ class TypedArrayObject : public NativeObject
|
||||
"right buffer slot");
|
||||
|
||||
// Slot containing length of the view in number of typed elements.
|
||||
// Length-tracking views on resizable/growable buffers store
|
||||
// LENGTH_TRACKING here and compute their visible length from the buffer.
|
||||
static const size_t LENGTH_SLOT = 1;
|
||||
static_assert(LENGTH_SLOT == JS_TYPEDARRAYLAYOUT_LENGTH_SLOT,
|
||||
"self-hosted code with burned-in constants must get the "
|
||||
@@ -65,6 +67,8 @@ class TypedArrayObject : public NativeObject
|
||||
|
||||
static const size_t RESERVED_SLOTS = 3;
|
||||
|
||||
static const int32_t LENGTH_TRACKING = -1;
|
||||
|
||||
#ifdef DEBUG
|
||||
static const uint8_t ZeroLengthArrayData = 0x4A;
|
||||
#endif
|
||||
@@ -139,15 +143,13 @@ class TypedArrayObject : public NativeObject
|
||||
return tarr->getFixedSlot(BUFFER_SLOT);
|
||||
}
|
||||
static Value byteOffsetValue(TypedArrayObject* tarr) {
|
||||
Value v = tarr->getFixedSlot(BYTEOFFSET_SLOT);
|
||||
MOZ_ASSERT(v.toInt32() >= 0);
|
||||
return v;
|
||||
return Int32Value(tarr->byteOffset());
|
||||
}
|
||||
static Value byteLengthValue(TypedArrayObject* tarr) {
|
||||
return Int32Value(tarr->getFixedSlot(LENGTH_SLOT).toInt32() * tarr->bytesPerElement());
|
||||
return Int32Value(tarr->byteLength());
|
||||
}
|
||||
static Value lengthValue(TypedArrayObject* tarr) {
|
||||
return tarr->getFixedSlot(LENGTH_SLOT);
|
||||
return Int32Value(tarr->length());
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -159,14 +161,70 @@ class TypedArrayObject : public NativeObject
|
||||
JSObject* bufferObject() const {
|
||||
return bufferValue(const_cast<TypedArrayObject*>(this)).toObjectOrNull();
|
||||
}
|
||||
bool isLengthTracking() const {
|
||||
return getFixedSlot(LENGTH_SLOT).toInt32() == LENGTH_TRACKING;
|
||||
}
|
||||
bool hasResizableOrGrowableBuffer() const {
|
||||
if (!hasBuffer())
|
||||
return false;
|
||||
if (isSharedMemory())
|
||||
return bufferShared()->isGrowable();
|
||||
return bufferUnshared()->isResizable();
|
||||
}
|
||||
uint32_t byteOffsetMaybeOutOfBounds() const {
|
||||
Value v = getFixedSlot(BYTEOFFSET_SLOT);
|
||||
MOZ_ASSERT(v.toInt32() >= 0);
|
||||
return v.toInt32();
|
||||
}
|
||||
uint32_t fixedLengthMaybeOutOfBounds() const {
|
||||
int32_t length = getFixedSlot(LENGTH_SLOT).toInt32();
|
||||
MOZ_ASSERT(length >= 0);
|
||||
return length;
|
||||
}
|
||||
uint32_t bufferByteLength() const {
|
||||
MOZ_ASSERT(hasBuffer());
|
||||
if (isSharedMemory())
|
||||
return bufferShared()->byteLength();
|
||||
return bufferUnshared()->byteLength();
|
||||
}
|
||||
bool isOutOfBounds() const {
|
||||
if (!hasBuffer())
|
||||
return false;
|
||||
if (hasDetachedBuffer())
|
||||
return true;
|
||||
|
||||
uint32_t bufferByteLength = this->bufferByteLength();
|
||||
uint32_t offset = byteOffsetMaybeOutOfBounds();
|
||||
if (offset > bufferByteLength)
|
||||
return true;
|
||||
|
||||
if (isLengthTracking())
|
||||
return false;
|
||||
|
||||
uint32_t byteLength = fixedLengthMaybeOutOfBounds() * bytesPerElement();
|
||||
return byteLength > bufferByteLength - offset;
|
||||
}
|
||||
uint32_t byteOffset() const {
|
||||
return byteOffsetValue(const_cast<TypedArrayObject*>(this)).toInt32();
|
||||
if (isOutOfBounds())
|
||||
return 0;
|
||||
return byteOffsetMaybeOutOfBounds();
|
||||
}
|
||||
uint32_t byteLength() const {
|
||||
return byteLengthValue(const_cast<TypedArrayObject*>(this)).toInt32();
|
||||
return length() * bytesPerElement();
|
||||
}
|
||||
uint32_t length() const {
|
||||
return lengthValue(const_cast<TypedArrayObject*>(this)).toInt32();
|
||||
if (!isLengthTracking()) {
|
||||
if (isOutOfBounds())
|
||||
return 0;
|
||||
return fixedLengthMaybeOutOfBounds();
|
||||
}
|
||||
|
||||
if (isOutOfBounds())
|
||||
return 0;
|
||||
|
||||
uint32_t bufferByteLength = this->bufferByteLength();
|
||||
uint32_t offset = byteOffsetMaybeOutOfBounds();
|
||||
return (bufferByteLength - offset) / bytesPerElement();
|
||||
}
|
||||
|
||||
bool hasInlineElements() const;
|
||||
@@ -466,28 +524,26 @@ class DataViewObject : public NativeObject
|
||||
defineGetter(JSContext* cx, PropertyName* name, HandleNativeObject proto);
|
||||
|
||||
static bool getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, const CallArgs& args,
|
||||
uint32_t *byteOffset, uint32_t* byteLength);
|
||||
uint32_t *byteOffset, uint32_t* byteLength,
|
||||
bool* lengthTracking);
|
||||
static bool constructSameCompartment(JSContext* cx, HandleObject bufobj, const CallArgs& args);
|
||||
static bool constructWrapped(JSContext* cx, HandleObject bufobj, const CallArgs& args);
|
||||
|
||||
friend bool ArrayBufferObject::createDataViewForThisImpl(JSContext* cx, const CallArgs& args);
|
||||
static DataViewObject*
|
||||
create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
|
||||
Handle<ArrayBufferObject*> arrayBuffer, JSObject* proto);
|
||||
Handle<ArrayBufferObjectMaybeShared*> arrayBuffer, JSObject* proto,
|
||||
bool lengthTracking = false);
|
||||
|
||||
public:
|
||||
static const Class class_;
|
||||
|
||||
static Value byteOffsetValue(DataViewObject* view) {
|
||||
Value v = view->getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT);
|
||||
MOZ_ASSERT(v.toInt32() >= 0);
|
||||
return v;
|
||||
return Int32Value(view->byteOffset());
|
||||
}
|
||||
|
||||
static Value byteLengthValue(DataViewObject* view) {
|
||||
Value v = view->getFixedSlot(TypedArrayObject::LENGTH_SLOT);
|
||||
MOZ_ASSERT(v.toInt32() >= 0);
|
||||
return v;
|
||||
return Int32Value(view->byteLength());
|
||||
}
|
||||
|
||||
static Value bufferValue(DataViewObject* view) {
|
||||
@@ -495,21 +551,66 @@ class DataViewObject : public NativeObject
|
||||
}
|
||||
|
||||
uint32_t byteOffset() const {
|
||||
return byteOffsetValue(const_cast<DataViewObject*>(this)).toInt32();
|
||||
if (isOutOfBounds())
|
||||
return 0;
|
||||
return byteOffsetMaybeOutOfBounds();
|
||||
}
|
||||
|
||||
uint32_t byteLength() const {
|
||||
return byteLengthValue(const_cast<DataViewObject*>(this)).toInt32();
|
||||
if (isOutOfBounds())
|
||||
return 0;
|
||||
if (isLengthTracking())
|
||||
return arrayBufferEither().byteLength() - byteOffsetMaybeOutOfBounds();
|
||||
return fixedByteLengthMaybeOutOfBounds();
|
||||
}
|
||||
|
||||
ArrayBufferObject& arrayBuffer() const {
|
||||
return bufferValue(const_cast<DataViewObject*>(this)).toObject().as<ArrayBufferObject>();
|
||||
ArrayBufferObjectMaybeShared& arrayBufferEither() const {
|
||||
return bufferValue(const_cast<DataViewObject*>(this)).
|
||||
toObject().as<ArrayBufferObjectMaybeShared>();
|
||||
}
|
||||
|
||||
bool isSharedMemory() const {
|
||||
return bufferValue(const_cast<DataViewObject*>(this)).
|
||||
toObject().is<SharedArrayBufferObject>();
|
||||
}
|
||||
|
||||
void* dataPointer() const {
|
||||
return getPrivate();
|
||||
}
|
||||
|
||||
bool isLengthTracking() const {
|
||||
return getFixedSlot(TypedArrayObject::LENGTH_SLOT).toInt32() ==
|
||||
TypedArrayObject::LENGTH_TRACKING;
|
||||
}
|
||||
|
||||
uint32_t byteOffsetMaybeOutOfBounds() const {
|
||||
Value v = getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT);
|
||||
MOZ_ASSERT(v.toInt32() >= 0);
|
||||
return v.toInt32();
|
||||
}
|
||||
|
||||
uint32_t fixedByteLengthMaybeOutOfBounds() const {
|
||||
int32_t length = getFixedSlot(TypedArrayObject::LENGTH_SLOT).toInt32();
|
||||
MOZ_ASSERT(length >= 0);
|
||||
return length;
|
||||
}
|
||||
|
||||
bool isOutOfBounds() const {
|
||||
const ArrayBufferObjectMaybeShared& buffer = arrayBufferEither();
|
||||
if (buffer.isDetached())
|
||||
return true;
|
||||
|
||||
uint32_t bufferByteLength = buffer.byteLength();
|
||||
uint32_t offset = byteOffsetMaybeOutOfBounds();
|
||||
if (offset > bufferByteLength)
|
||||
return true;
|
||||
|
||||
if (isLengthTracking())
|
||||
return false;
|
||||
|
||||
return fixedByteLengthMaybeOutOfBounds() > bufferByteLength - offset;
|
||||
}
|
||||
|
||||
static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
static bool getInt8Impl(JSContext* cx, const CallArgs& args);
|
||||
|
||||
@@ -54,6 +54,7 @@ ModuleGenerator::ModuleGenerator(ImportVector&& imports)
|
||||
lastPatchedCallsite_(0),
|
||||
startOfUnpatchedCallsites_(0),
|
||||
parallel_(false),
|
||||
parallelCompilationInProgressOnHelperThread_(false),
|
||||
outstanding_(0),
|
||||
activeFuncDef_(nullptr),
|
||||
startedFuncDefs_(false),
|
||||
@@ -71,18 +72,21 @@ ModuleGenerator::~ModuleGenerator()
|
||||
AutoLockHelperThreadState lock;
|
||||
while (true) {
|
||||
IonCompileTaskPtrVector& worklist = HelperThreadState().wasmWorklist(lock);
|
||||
MOZ_ASSERT(outstanding_ >= worklist.length());
|
||||
outstanding_ -= worklist.length();
|
||||
worklist.clear();
|
||||
for (size_t i = worklist.length(); i > 0;) {
|
||||
if (worklist[i - 1]->finishedList() == &finishedTasks_) {
|
||||
HelperThreadState().remove(worklist, &i);
|
||||
MOZ_ASSERT(outstanding_ > 0);
|
||||
outstanding_--;
|
||||
} else {
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
IonCompileTaskPtrVector& finished = HelperThreadState().wasmFinishedList(lock);
|
||||
MOZ_ASSERT(outstanding_ >= finished.length());
|
||||
outstanding_ -= finished.length();
|
||||
finished.clear();
|
||||
|
||||
uint32_t numFailed = HelperThreadState().harvestFailedWasmJobs(lock);
|
||||
MOZ_ASSERT(outstanding_ >= numFailed);
|
||||
outstanding_ -= numFailed;
|
||||
for (size_t i = finishedTasks_.length(); i > 0;) {
|
||||
HelperThreadState().remove(finishedTasks_, &i);
|
||||
MOZ_ASSERT(outstanding_ > 0);
|
||||
outstanding_--;
|
||||
}
|
||||
|
||||
if (!outstanding_)
|
||||
break;
|
||||
@@ -91,8 +95,10 @@ ModuleGenerator::~ModuleGenerator()
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(HelperThreadState().wasmCompilationInProgress);
|
||||
HelperThreadState().wasmCompilationInProgress = false;
|
||||
if (parallelCompilationInProgressOnHelperThread_) {
|
||||
MOZ_ASSERT(HelperThreadState().wasmCompilationInProgress);
|
||||
HelperThreadState().wasmCompilationInProgress = false;
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(!outstanding_);
|
||||
}
|
||||
@@ -208,12 +214,9 @@ ModuleGenerator::finishOutstandingTask()
|
||||
while (true) {
|
||||
MOZ_ASSERT(outstanding_ > 0);
|
||||
|
||||
if (HelperThreadState().wasmFailed(lock))
|
||||
return false;
|
||||
|
||||
if (!HelperThreadState().wasmFinishedList(lock).empty()) {
|
||||
if (!finishedTasks_.empty()) {
|
||||
outstanding_--;
|
||||
task = HelperThreadState().wasmFinishedList(lock).popCopy();
|
||||
task = finishedTasks_.popCopy();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -221,6 +224,9 @@ ModuleGenerator::finishOutstandingTask()
|
||||
}
|
||||
}
|
||||
|
||||
if (task->failed())
|
||||
return false;
|
||||
|
||||
return finishTask(task);
|
||||
}
|
||||
|
||||
@@ -365,6 +371,9 @@ ModuleGenerator::patchFarJumps(const TrapExitOffsetArray& trapExits)
|
||||
bool
|
||||
ModuleGenerator::finishTask(IonCompileTask* task)
|
||||
{
|
||||
if (task->failed())
|
||||
return false;
|
||||
|
||||
const FuncBytes& func = task->func();
|
||||
FuncCompileResults& results = task->results();
|
||||
|
||||
@@ -856,31 +865,25 @@ ModuleGenerator::startFuncDefs()
|
||||
MOZ_ASSERT(!startedFuncDefs_);
|
||||
MOZ_ASSERT(!finishedFuncDefs_);
|
||||
|
||||
// The wasmCompilationInProgress atomic ensures that there is only one
|
||||
// parallel compilation in progress at a time. In the special case of
|
||||
// asm.js, where the ModuleGenerator itself can be on a helper thread, this
|
||||
// avoids the possibility of deadlock since at most 1 helper thread will be
|
||||
// blocking on other helper threads and there are always >1 helper threads.
|
||||
// With wasm, this restriction could be relaxed by moving the worklist state
|
||||
// out of HelperThreadState since each independent compilation needs its own
|
||||
// worklist pair. Alternatively, the deadlock could be avoided by having the
|
||||
// ModuleGenerator thread make progress (on compile tasks) instead of
|
||||
// blocking.
|
||||
// Helper-thread initiated wasm compilations stay serialized so that we do
|
||||
// not end up with multiple helper threads blocking on other helper threads.
|
||||
// Main-thread compilations can still overlap because they drain their own
|
||||
// finished-task queue and do not steal tasks from other generators.
|
||||
|
||||
GlobalHelperThreadState& threads = HelperThreadState();
|
||||
MOZ_ASSERT(threads.threadCount > 1);
|
||||
|
||||
uint32_t numTasks;
|
||||
if (CanUseExtraThreads() && threads.wasmCompilationInProgress.compareExchange(false, true)) {
|
||||
if (CanUseExtraThreads() && (!CurrentHelperThread() ||
|
||||
threads.wasmCompilationInProgress.compareExchange(false, true))) {
|
||||
#ifdef DEBUG
|
||||
{
|
||||
AutoLockHelperThreadState lock;
|
||||
MOZ_ASSERT(!HelperThreadState().wasmFailed(lock));
|
||||
MOZ_ASSERT(HelperThreadState().wasmWorklist(lock).empty());
|
||||
MOZ_ASSERT(HelperThreadState().wasmFinishedList(lock).empty());
|
||||
}
|
||||
#endif
|
||||
parallel_ = true;
|
||||
parallelCompilationInProgressOnHelperThread_ = !!CurrentHelperThread();
|
||||
numTasks = 2 * threads.maxWasmCompilationThreads();
|
||||
} else {
|
||||
numTasks = 1;
|
||||
@@ -891,6 +894,9 @@ ModuleGenerator::startFuncDefs()
|
||||
for (size_t i = 0; i < numTasks; i++)
|
||||
tasks_.infallibleEmplaceBack(*shared_, COMPILATION_LIFO_DEFAULT_CHUNK_SIZE);
|
||||
|
||||
for (auto& task : tasks_)
|
||||
task.setFinishedList(&finishedTasks_);
|
||||
|
||||
if (!freeTasks_.reserve(numTasks))
|
||||
return false;
|
||||
for (size_t i = 0; i < numTasks; i++)
|
||||
|
||||
@@ -104,9 +104,11 @@ class MOZ_STACK_CLASS ModuleGenerator
|
||||
|
||||
// Parallel compilation
|
||||
bool parallel_;
|
||||
bool parallelCompilationInProgressOnHelperThread_;
|
||||
uint32_t outstanding_;
|
||||
IonCompileTaskVector tasks_;
|
||||
IonCompileTaskPtrVector freeTasks_;
|
||||
IonCompileTaskPtrVector finishedTasks_;
|
||||
|
||||
// Assertions
|
||||
DebugOnly<FunctionGenerator*> activeFuncDef_;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#define wasm_ion_compile_h
|
||||
|
||||
#include "jit/MacroAssembler.h"
|
||||
#include "vm/HelperThreads.h"
|
||||
#include "wasm/WasmTypes.h"
|
||||
|
||||
namespace js {
|
||||
@@ -106,6 +107,8 @@ class IonCompileTask
|
||||
UniqueFuncBytes func_;
|
||||
CompileMode mode_;
|
||||
Maybe<FuncCompileResults> results_;
|
||||
IonCompileTaskPtrVector* finishedList_;
|
||||
bool failed_;
|
||||
|
||||
IonCompileTask(const IonCompileTask&) = delete;
|
||||
IonCompileTask& operator=(const IonCompileTask&) = delete;
|
||||
@@ -113,7 +116,26 @@ class IonCompileTask
|
||||
public:
|
||||
IonCompileTask(const ModuleGeneratorData& mg, size_t defaultChunkSize)
|
||||
: mg_(mg), lifo_(defaultChunkSize), func_(nullptr), mode_(CompileMode::None)
|
||||
, finishedList_(nullptr)
|
||||
, failed_(false)
|
||||
{}
|
||||
|
||||
void setFinishedList(IonCompileTaskPtrVector* finishedList) {
|
||||
finishedList_ = finishedList;
|
||||
}
|
||||
|
||||
IonCompileTaskPtrVector* finishedList() const {
|
||||
MOZ_ASSERT(finishedList_);
|
||||
return finishedList_;
|
||||
}
|
||||
|
||||
void setFailed() {
|
||||
failed_ = true;
|
||||
}
|
||||
|
||||
bool failed() const {
|
||||
return failed_;
|
||||
}
|
||||
LifoAlloc& lifo() {
|
||||
return lifo_;
|
||||
}
|
||||
@@ -143,6 +165,7 @@ class IonCompileTask
|
||||
results_.reset();
|
||||
lifo_.releaseAll();
|
||||
mode_ = CompileMode::None;
|
||||
failed_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1442,7 +1442,6 @@ ReloadPrefsCallback(const char* pref, void* data)
|
||||
bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
|
||||
|
||||
bool streams = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams");
|
||||
bool weakRefs = Preferences::GetBool(JS_OPTIONS_DOT_STR "weakrefs");
|
||||
|
||||
bool unboxedObjects = Preferences::GetBool(JS_OPTIONS_DOT_STR "unboxed_objects");
|
||||
|
||||
@@ -1469,8 +1468,7 @@ ReloadPrefsCallback(const char* pref, void* data)
|
||||
.setWerror(werror)
|
||||
.setExtraWarnings(extraWarnings)
|
||||
.setArrayProtoValues(arrayProtoValues)
|
||||
.setStreams(streams)
|
||||
.setWeakRefs(weakRefs);
|
||||
.setStreams(streams);
|
||||
|
||||
JS_SetParallelParsingEnabled(cx, parallelParsing);
|
||||
JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/* 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/. */
|
||||
|
||||
function assertThrowsTypeError(fn) {
|
||||
try {
|
||||
fn();
|
||||
} catch (e) {
|
||||
do_check_true(e instanceof TypeError);
|
||||
return;
|
||||
}
|
||||
do_throw("expected TypeError");
|
||||
}
|
||||
|
||||
add_task(function* test_finalization_registry_api_surface() {
|
||||
do_check_eq(typeof FinalizationRegistry, "function");
|
||||
do_check_eq(FinalizationRegistry.name, "FinalizationRegistry");
|
||||
do_check_eq(FinalizationRegistry.length, 1);
|
||||
|
||||
assertThrowsTypeError(() => FinalizationRegistry(function() {}));
|
||||
assertThrowsTypeError(() => new FinalizationRegistry(1));
|
||||
|
||||
let registry = new FinalizationRegistry(function() {});
|
||||
do_check_eq(Object.prototype.toString.call(registry), "[object FinalizationRegistry]");
|
||||
do_check_eq(typeof FinalizationRegistry.prototype.register, "function");
|
||||
do_check_eq(FinalizationRegistry.prototype.register.length, 2);
|
||||
do_check_eq(typeof FinalizationRegistry.prototype.unregister, "function");
|
||||
do_check_eq(FinalizationRegistry.prototype.unregister.length, 1);
|
||||
do_check_eq(FinalizationRegistry.prototype.cleanupSome, undefined);
|
||||
|
||||
let desc = Object.getOwnPropertyDescriptor(FinalizationRegistry.prototype,
|
||||
Symbol.toStringTag);
|
||||
do_check_eq(desc.value, "FinalizationRegistry");
|
||||
do_check_eq(desc.writable, false);
|
||||
do_check_eq(desc.enumerable, false);
|
||||
do_check_eq(desc.configurable, true);
|
||||
});
|
||||
|
||||
add_task(function* test_register_validation_and_unregister() {
|
||||
let registry = new FinalizationRegistry(function() {});
|
||||
let target = {};
|
||||
let token = {};
|
||||
|
||||
do_check_eq(registry.register(target, "held"), undefined);
|
||||
do_check_eq(registry.register({}, "held", token), undefined);
|
||||
do_check_eq(registry.unregister(token), true);
|
||||
do_check_eq(registry.unregister({}), false);
|
||||
|
||||
assertThrowsTypeError(() => registry.register(1, "held"));
|
||||
assertThrowsTypeError(() => registry.register(target, target));
|
||||
assertThrowsTypeError(() => registry.register(target, "held", 1));
|
||||
assertThrowsTypeError(() => registry.register(Symbol.for("registered"), "held"));
|
||||
assertThrowsTypeError(() => registry.unregister(undefined));
|
||||
|
||||
do_check_eq(registry.register(Symbol("target"), "held"), undefined);
|
||||
let symbolToken = Symbol("token");
|
||||
do_check_eq(registry.register({}, "held", symbolToken), undefined);
|
||||
do_check_eq(registry.unregister(symbolToken), true);
|
||||
});
|
||||
|
||||
add_task(function* test_proto_from_constructor_realm() {
|
||||
let other = new Components.utils.Sandbox("http://example.com", { freshZone: true });
|
||||
Components.utils.evalInSandbox("var newTarget = new Function();", other);
|
||||
|
||||
for (let proto of [undefined, null, true, "", Symbol(), 1]) {
|
||||
other.newTarget.prototype = proto;
|
||||
let registry = Reflect.construct(FinalizationRegistry, [function() {}],
|
||||
other.newTarget);
|
||||
do_check_true(Object.getPrototypeOf(registry) ===
|
||||
other.FinalizationRegistry.prototype);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_cleanup_callback_after_gc() {
|
||||
let cleaned = [];
|
||||
let registry = new FinalizationRegistry(value => cleaned.push(value));
|
||||
let token = {};
|
||||
|
||||
(function() {
|
||||
let target = {};
|
||||
registry.register(target, "held", token);
|
||||
})();
|
||||
|
||||
for (let i = 0; i < 4 && cleaned.length == 0; i++) {
|
||||
Components.utils.forceGC();
|
||||
yield Promise.resolve();
|
||||
}
|
||||
|
||||
do_check_eq(cleaned.length, 1);
|
||||
do_check_eq(cleaned[0], "held");
|
||||
do_check_eq(registry.unregister(token), false);
|
||||
});
|
||||
|
||||
add_task(function* test_unregister_prevents_cleanup() {
|
||||
let cleaned = [];
|
||||
let registry = new FinalizationRegistry(value => cleaned.push(value));
|
||||
let token = {};
|
||||
|
||||
(function() {
|
||||
let target = {};
|
||||
registry.register(target, "held", token);
|
||||
})();
|
||||
|
||||
do_check_eq(registry.unregister(token), true);
|
||||
Components.utils.forceGC();
|
||||
yield Promise.resolve();
|
||||
do_check_eq(cleaned.length, 0);
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
/* 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/. */
|
||||
|
||||
function assertThrowsTypeError(fn) {
|
||||
try {
|
||||
fn();
|
||||
} catch (e) {
|
||||
do_check_true(e instanceof TypeError);
|
||||
return;
|
||||
}
|
||||
do_throw("expected TypeError");
|
||||
}
|
||||
|
||||
add_task(function* test_weakref_api_surface() {
|
||||
do_check_eq(typeof WeakRef, "function");
|
||||
do_check_eq(WeakRef.name, "WeakRef");
|
||||
do_check_eq(WeakRef.length, 1);
|
||||
|
||||
assertThrowsTypeError(() => WeakRef({}));
|
||||
assertThrowsTypeError(() => new WeakRef(1));
|
||||
assertThrowsTypeError(() => new WeakRef(Symbol.for("registered")));
|
||||
|
||||
let target = {};
|
||||
let ref = new WeakRef(target);
|
||||
do_check_eq(Object.prototype.toString.call(ref), "[object WeakRef]");
|
||||
do_check_eq(ref.deref(), target);
|
||||
do_check_eq(typeof WeakRef.prototype.deref, "function");
|
||||
do_check_eq(WeakRef.prototype.deref.length, 0);
|
||||
|
||||
let desc = Object.getOwnPropertyDescriptor(WeakRef.prototype,
|
||||
Symbol.toStringTag);
|
||||
do_check_eq(desc.value, "WeakRef");
|
||||
do_check_eq(desc.writable, false);
|
||||
do_check_eq(desc.enumerable, false);
|
||||
do_check_eq(desc.configurable, true);
|
||||
});
|
||||
|
||||
add_task(function* test_newtarget_prototype_is_not_object() {
|
||||
function newTarget() {}
|
||||
|
||||
for (let proto of [undefined, null, true, "", Symbol(), 1]) {
|
||||
newTarget.prototype = proto;
|
||||
let ref = Reflect.construct(WeakRef, [{}], newTarget);
|
||||
do_check_true(Object.getPrototypeOf(ref) === WeakRef.prototype);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_proto_from_constructor_realm() {
|
||||
let other = new Components.utils.Sandbox("http://example.com", { freshZone: true });
|
||||
Components.utils.evalInSandbox("var newTarget = new Function();", other);
|
||||
|
||||
for (let proto of [undefined, null, true, "", Symbol(), 1]) {
|
||||
other.newTarget.prototype = proto;
|
||||
let ref = Reflect.construct(WeakRef, [{}], other.newTarget);
|
||||
do_check_true(Object.getPrototypeOf(ref) === other.WeakRef.prototype);
|
||||
}
|
||||
});
|
||||
@@ -71,7 +71,9 @@ support-files =
|
||||
[test_import_fail.js]
|
||||
[test_interposition.js]
|
||||
[test_isModuleLoaded.js]
|
||||
[test_finalization_registry.js]
|
||||
[test_js_weak_references.js]
|
||||
[test_weakref.js]
|
||||
[test_onGarbageCollection-01.js]
|
||||
head = head_ongc.js
|
||||
[test_onGarbageCollection-02.js]
|
||||
|
||||
@@ -1743,7 +1743,9 @@ nsCSSRendering::PaintBackground(const PaintBGParams& aParams)
|
||||
}
|
||||
|
||||
static bool
|
||||
IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::Side aSide)
|
||||
IsOpaqueBorderEdge(const nsStyleBorder& aBorder,
|
||||
const nsStyleColor& aColor,
|
||||
mozilla::Side aSide)
|
||||
{
|
||||
if (aBorder.GetComputedBorder().Side(aSide) == 0)
|
||||
return true;
|
||||
@@ -1765,25 +1767,20 @@ IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::Side aSide)
|
||||
if (aBorder.mBorderImageSource.GetType() != eStyleImageType_Null)
|
||||
return false;
|
||||
|
||||
StyleComplexColor color = aBorder.mBorderColor[aSide];
|
||||
// We don't know the foreground color here, so if it's being used
|
||||
// we must assume it might be transparent.
|
||||
if (!color.IsNumericColor()) {
|
||||
return false;
|
||||
}
|
||||
return NS_GET_A(color.mColor) == 255;
|
||||
nscolor color = aColor.CalcComplexColor(aBorder.mBorderColor[aSide]);
|
||||
return NS_GET_A(color) == 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all border edges are either missing or opaque.
|
||||
*/
|
||||
static bool
|
||||
IsOpaqueBorder(const nsStyleBorder& aBorder)
|
||||
IsOpaqueBorder(const nsStyleBorder& aBorder, const nsStyleColor& aColor)
|
||||
{
|
||||
if (aBorder.mBorderColors)
|
||||
return false;
|
||||
NS_FOR_CSS_SIDES(i) {
|
||||
if (!IsOpaqueBorderEdge(aBorder, i))
|
||||
if (!IsOpaqueBorderEdge(aBorder, aColor, i))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1916,7 +1913,7 @@ nsCSSRendering::GetImageLayerClip(const nsStyleImageLayers::Layer& aLayer,
|
||||
}
|
||||
|
||||
bool isSolidBorder =
|
||||
aWillPaintBorder && IsOpaqueBorder(aBorder);
|
||||
aWillPaintBorder && IsOpaqueBorder(aBorder, *aForFrame->StyleColor());
|
||||
if (isSolidBorder && layerClip == StyleGeometryBox::Border) {
|
||||
// If we have rounded corners, we need to inflate the background
|
||||
// drawing area a bit to avoid seams between the border and
|
||||
|
||||
@@ -4652,7 +4652,10 @@ nsFrame::GetIntrinsicSize()
|
||||
/* virtual */ AspectRatio
|
||||
nsFrame::GetIntrinsicRatio()
|
||||
{
|
||||
return AspectRatio();
|
||||
const nsStylePosition* stylePos = StylePosition();
|
||||
return stylePos->mAspectRatio == 0.0f
|
||||
? AspectRatio()
|
||||
: AspectRatio(stylePos->mAspectRatio);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -4721,9 +4724,16 @@ nsFrame::ComputeSize(nsRenderingContext* aRenderingContext,
|
||||
const LogicalSize& aPadding,
|
||||
ComputeSizeFlags aFlags)
|
||||
{
|
||||
MOZ_ASSERT(!GetIntrinsicRatio(),
|
||||
"Please override this method and call "
|
||||
"nsFrame::ComputeSizeWithIntrinsicDimensions instead.");
|
||||
AspectRatio intrinsicRatio = GetIntrinsicRatio();
|
||||
if (intrinsicRatio) {
|
||||
return ComputeSizeWithIntrinsicDimensions(aRenderingContext, aWM,
|
||||
GetIntrinsicSize(),
|
||||
intrinsicRatio,
|
||||
aCBSize, aMargin,
|
||||
aBorder, aPadding,
|
||||
aFlags);
|
||||
}
|
||||
|
||||
LogicalSize result = ComputeAutoSize(aRenderingContext, aWM,
|
||||
aCBSize, aAvailableISize,
|
||||
aMargin, aBorder, aPadding,
|
||||
|
||||
@@ -71,15 +71,16 @@ vpx_codec_set_frame_buffer_functions
|
||||
vpx_codec_enc_init_multi_ver
|
||||
#endif
|
||||
#ifdef MOZ_AV1
|
||||
aom_codec_av1_dx
|
||||
aom_codec_dec_init_ver
|
||||
aom_codec_decode
|
||||
aom_codec_destroy
|
||||
aom_codec_err_to_string
|
||||
aom_codec_get_frame
|
||||
aom_codec_peek_stream_info
|
||||
aom_img_alloc
|
||||
aom_img_free
|
||||
dav1d_default_settings
|
||||
dav1d_open
|
||||
dav1d_parse_sequence_header
|
||||
dav1d_send_data
|
||||
dav1d_get_picture
|
||||
dav1d_flush
|
||||
dav1d_close
|
||||
dav1d_data_create
|
||||
dav1d_data_unref
|
||||
dav1d_picture_unref
|
||||
#endif
|
||||
#ifdef MOZ_JXL
|
||||
JxlDecoderCreate
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
:root {
|
||||
--color: #fff;
|
||||
background: var(--color);
|
||||
color: var(--color);
|
||||
}
|
||||
div {
|
||||
display: block;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
border-radius: 50%;
|
||||
background-color: #323232;
|
||||
border: 10px solid var(--color);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
:root {
|
||||
--color: #fff;
|
||||
background: var(--color);
|
||||
color: var(--color);
|
||||
}
|
||||
div {
|
||||
display: block;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
border-radius: 50%;
|
||||
background-color: #323232;
|
||||
border: 10px solid currentcolor;
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
@@ -89,6 +89,8 @@ fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.2/.te
|
||||
# Test for antialiasing gaps between background and border
|
||||
fuzzy-if(gtkWidget,1,9) fuzzy-if(winWidget&&!d2d,1,9) fuzzy-if(d2d,5,40) fuzzy-if(skiaContent,1,9) == curved-border-background-nogap.html curved-border-background-nogap-ref.html
|
||||
|
||||
== currentcolor-border-radius.html currentcolor-border-radius-ref.html
|
||||
|
||||
== color-layer-1a.html color-layer-1-ref.html
|
||||
|
||||
== corner-split.html corner-split-ref.svg # bug 1185636
|
||||
|
||||
+109
-11
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "nsCSSValue.h"
|
||||
#include "nsStyleCoord.h"
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
|
||||
namespace mozilla {
|
||||
@@ -46,6 +47,13 @@ namespace css {
|
||||
* result_type aValue1, float aValue2);
|
||||
*
|
||||
* result_type
|
||||
* MergeMinMax(nsCSSUnit aCalcFunction,
|
||||
* result_type aValue1, result_type aValue2);
|
||||
*
|
||||
* result_type
|
||||
* MergeClamp(result_type aMin, result_type aCenter, result_type aMax);
|
||||
*
|
||||
* result_type
|
||||
* ComputeLeafValue(const input_type& aValue);
|
||||
*
|
||||
* float
|
||||
@@ -70,6 +78,8 @@ namespace css {
|
||||
* MergeAdditive for Plus and Minus
|
||||
* MergeMultiplicativeL for Times_L (number * value)
|
||||
* MergeMultiplicativeR for Times_R (value * number) and Divided
|
||||
* MergeMinMax for Min and Max
|
||||
* MergeClamp for Clamp
|
||||
*/
|
||||
template <class CalcOps>
|
||||
static typename CalcOps::result_type
|
||||
@@ -104,6 +114,25 @@ ComputeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps)
|
||||
float rhs = aOps.ComputeNumber(arr->Item(1));
|
||||
return aOps.MergeMultiplicativeR(CalcOps::GetUnit(aValue), lhs, rhs);
|
||||
}
|
||||
case eCSSUnit_Calc_Min:
|
||||
case eCSSUnit_Calc_Max: {
|
||||
typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(arr->Count() >= 1, "unexpected length");
|
||||
typename CalcOps::result_type result = ComputeCalc(arr->Item(0), aOps);
|
||||
for (uint32_t i = 1; i < arr->Count(); ++i) {
|
||||
typename CalcOps::result_type next = ComputeCalc(arr->Item(i), aOps);
|
||||
result = aOps.MergeMinMax(CalcOps::GetUnit(aValue), result, next);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case eCSSUnit_Calc_Clamp: {
|
||||
typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(arr->Count() == 3, "unexpected length");
|
||||
typename CalcOps::result_type min = ComputeCalc(arr->Item(0), aOps);
|
||||
typename CalcOps::result_type center = ComputeCalc(arr->Item(1), aOps);
|
||||
typename CalcOps::result_type max = ComputeCalc(arr->Item(2), aOps);
|
||||
return aOps.MergeClamp(min, center, max);
|
||||
}
|
||||
default: {
|
||||
return aOps.ComputeLeafValue(aValue);
|
||||
}
|
||||
@@ -168,6 +197,23 @@ struct BasicCoordCalcOps
|
||||
}
|
||||
return NSCoordSaturatingMultiply(aValue1, aValue2);
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeMinMax(nsCSSUnit aCalcFunction,
|
||||
result_type aValue1, result_type aValue2)
|
||||
{
|
||||
if (aCalcFunction == eCSSUnit_Calc_Min) {
|
||||
return std::min(aValue1, aValue2);
|
||||
}
|
||||
MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Max, "unexpected unit");
|
||||
return std::max(aValue1, aValue2);
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeClamp(result_type aMin, result_type aCenter, result_type aMax)
|
||||
{
|
||||
return std::max(aMin, std::min(aCenter, aMax));
|
||||
}
|
||||
};
|
||||
|
||||
struct BasicFloatCalcOps
|
||||
@@ -206,6 +252,23 @@ struct BasicFloatCalcOps
|
||||
"unexpected unit");
|
||||
return aValue1 / aValue2;
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeMinMax(nsCSSUnit aCalcFunction,
|
||||
result_type aValue1, result_type aValue2)
|
||||
{
|
||||
if (aCalcFunction == eCSSUnit_Calc_Min) {
|
||||
return std::min(aValue1, aValue2);
|
||||
}
|
||||
MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Max, "unexpected unit");
|
||||
return std::max(aValue1, aValue2);
|
||||
}
|
||||
|
||||
result_type
|
||||
MergeClamp(result_type aMin, result_type aCenter, result_type aMax)
|
||||
{
|
||||
return std::max(aMin, std::min(aCenter, aMax));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -255,8 +318,20 @@ template <class CalcOps>
|
||||
static void
|
||||
SerializeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps)
|
||||
{
|
||||
aOps.Append("calc(");
|
||||
nsCSSUnit unit = CalcOps::GetUnit(aValue);
|
||||
if (unit == eCSSUnit_Calc) {
|
||||
const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(array->Count() == 1, "unexpected length");
|
||||
nsCSSUnit childUnit = CalcOps::GetUnit(array->Item(0));
|
||||
if (childUnit == eCSSUnit_Calc_Min ||
|
||||
childUnit == eCSSUnit_Calc_Max ||
|
||||
childUnit == eCSSUnit_Calc_Clamp) {
|
||||
SerializeCalcInternal(array->Item(0), aOps);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
aOps.Append("calc(");
|
||||
if (unit == eCSSUnit_Calc) {
|
||||
const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(array->Count() == 1, "unexpected length");
|
||||
@@ -282,6 +357,14 @@ IsCalcMultiplicativeUnit(nsCSSUnit aUnit)
|
||||
aUnit == eCSSUnit_Calc_Divided;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
IsCalcMinMaxClampUnit(nsCSSUnit aUnit)
|
||||
{
|
||||
return aUnit == eCSSUnit_Calc_Min ||
|
||||
aUnit == eCSSUnit_Calc_Max ||
|
||||
aUnit == eCSSUnit_Calc_Clamp;
|
||||
}
|
||||
|
||||
// Serialize a non-toplevel value in a calc() tree. See big comment
|
||||
// above.
|
||||
template <class CalcOps>
|
||||
@@ -318,11 +401,7 @@ SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps)
|
||||
if (needParens) {
|
||||
aOps.Append("(");
|
||||
}
|
||||
if (unit == eCSSUnit_Calc_Times_L) {
|
||||
aOps.AppendNumber(array->Item(0));
|
||||
} else {
|
||||
SerializeCalcInternal(array->Item(0), aOps);
|
||||
}
|
||||
SerializeCalcInternal(array->Item(0), aOps);
|
||||
if (needParens) {
|
||||
aOps.Append(")");
|
||||
}
|
||||
@@ -340,14 +419,33 @@ SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps)
|
||||
if (needParens) {
|
||||
aOps.Append("(");
|
||||
}
|
||||
if (unit == eCSSUnit_Calc_Times_L) {
|
||||
SerializeCalcInternal(array->Item(1), aOps);
|
||||
} else {
|
||||
aOps.AppendNumber(array->Item(1));
|
||||
}
|
||||
SerializeCalcInternal(array->Item(1), aOps);
|
||||
if (needParens) {
|
||||
aOps.Append(")");
|
||||
}
|
||||
} else if (IsCalcMinMaxClampUnit(unit)) {
|
||||
const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(unit != eCSSUnit_Calc_Clamp || array->Count() == 3,
|
||||
"unexpected length");
|
||||
MOZ_ASSERT(unit == eCSSUnit_Calc_Clamp || array->Count() >= 1,
|
||||
"unexpected length");
|
||||
|
||||
if (unit == eCSSUnit_Calc_Min) {
|
||||
aOps.Append("min(");
|
||||
} else if (unit == eCSSUnit_Calc_Max) {
|
||||
aOps.Append("max(");
|
||||
} else {
|
||||
MOZ_ASSERT(unit == eCSSUnit_Calc_Clamp, "unexpected unit");
|
||||
aOps.Append("clamp(");
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < array->Count(); ++i) {
|
||||
if (i != 0) {
|
||||
aOps.Append(", ");
|
||||
}
|
||||
SerializeCalcInternal(array->Item(i), aOps);
|
||||
}
|
||||
aOps.Append(")");
|
||||
} else {
|
||||
aOps.AppendLeafValue(aValue);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include "mozilla/CSSStyleSheet.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "nsIAtom.h"
|
||||
#include "nsCSSRuleProcessor.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
@@ -130,6 +132,153 @@ int32_t DoCompare(Numeric a, Numeric b)
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct MediaQueryTypedCalcLengthResult
|
||||
{
|
||||
double mValue;
|
||||
int32_t mExponent;
|
||||
};
|
||||
|
||||
static bool
|
||||
EvaluateMediaQueryTypedCalcLength(nsPresContext* aPresContext,
|
||||
const nsCSSValue& aValue,
|
||||
MediaQueryTypedCalcLengthResult& aResult)
|
||||
{
|
||||
switch (aValue.GetUnit()) {
|
||||
case eCSSUnit_Calc: {
|
||||
nsCSSValue::Array* array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(array->Count() == 1, "unexpected length");
|
||||
return EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0),
|
||||
aResult);
|
||||
}
|
||||
|
||||
case eCSSUnit_Calc_Plus:
|
||||
case eCSSUnit_Calc_Minus: {
|
||||
nsCSSValue::Array* array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(array->Count() == 2, "unexpected length");
|
||||
MediaQueryTypedCalcLengthResult lhs;
|
||||
MediaQueryTypedCalcLengthResult rhs;
|
||||
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0), lhs) ||
|
||||
!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(1), rhs) ||
|
||||
lhs.mExponent != rhs.mExponent) {
|
||||
return false;
|
||||
}
|
||||
aResult.mExponent = lhs.mExponent;
|
||||
aResult.mValue = aValue.GetUnit() == eCSSUnit_Calc_Plus
|
||||
? lhs.mValue + rhs.mValue
|
||||
: lhs.mValue - rhs.mValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
case eCSSUnit_Calc_Times_L:
|
||||
case eCSSUnit_Calc_Times_R:
|
||||
case eCSSUnit_Calc_Divided: {
|
||||
nsCSSValue::Array* array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(array->Count() == 2, "unexpected length");
|
||||
MediaQueryTypedCalcLengthResult lhs;
|
||||
MediaQueryTypedCalcLengthResult rhs;
|
||||
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0), lhs) ||
|
||||
!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(1), rhs)) {
|
||||
return false;
|
||||
}
|
||||
if (aValue.GetUnit() == eCSSUnit_Calc_Divided) {
|
||||
if (rhs.mValue == 0.0) {
|
||||
return false;
|
||||
}
|
||||
aResult.mValue = lhs.mValue / rhs.mValue;
|
||||
aResult.mExponent = lhs.mExponent - rhs.mExponent;
|
||||
} else {
|
||||
aResult.mValue = lhs.mValue * rhs.mValue;
|
||||
aResult.mExponent = lhs.mExponent + rhs.mExponent;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case eCSSUnit_Calc_Min:
|
||||
case eCSSUnit_Calc_Max:
|
||||
case eCSSUnit_Calc_Clamp: {
|
||||
nsCSSValue::Array* array = aValue.GetArrayValue();
|
||||
MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Calc_Clamp ||
|
||||
array->Count() == 3, "unexpected length");
|
||||
MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Calc_Clamp ||
|
||||
array->Count() >= 1, "unexpected length");
|
||||
|
||||
MediaQueryTypedCalcLengthResult result;
|
||||
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(0),
|
||||
result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aValue.GetUnit() == eCSSUnit_Calc_Clamp) {
|
||||
MediaQueryTypedCalcLengthResult center;
|
||||
MediaQueryTypedCalcLengthResult max;
|
||||
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(1),
|
||||
center) ||
|
||||
!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(2),
|
||||
max) ||
|
||||
result.mExponent != center.mExponent ||
|
||||
result.mExponent != max.mExponent) {
|
||||
return false;
|
||||
}
|
||||
aResult.mExponent = result.mExponent;
|
||||
aResult.mValue = std::max(result.mValue,
|
||||
std::min(center.mValue, max.mValue));
|
||||
return true;
|
||||
}
|
||||
|
||||
for (uint32_t i = 1; i < array->Count(); ++i) {
|
||||
MediaQueryTypedCalcLengthResult item;
|
||||
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, array->Item(i),
|
||||
item) ||
|
||||
result.mExponent != item.mExponent) {
|
||||
return false;
|
||||
}
|
||||
result.mValue = aValue.GetUnit() == eCSSUnit_Calc_Min
|
||||
? std::min(result.mValue, item.mValue)
|
||||
: std::max(result.mValue, item.mValue);
|
||||
}
|
||||
aResult = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
case eCSSUnit_Number:
|
||||
aResult.mValue = aValue.GetFloatValue();
|
||||
aResult.mExponent = 0;
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!aValue.IsLengthUnit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aResult.mValue = double(nsRuleNode::CalcLengthWithInitialFont(aPresContext,
|
||||
aValue));
|
||||
aResult.mExponent = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ComputeMediaQueryTypedCalcLength(nsPresContext* aPresContext,
|
||||
const nsCSSValue& aValue,
|
||||
nscoord& aResult)
|
||||
{
|
||||
if (aValue.IsLengthUnit()) {
|
||||
aResult = nsRuleNode::CalcLengthWithInitialFont(aPresContext, aValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
MediaQueryTypedCalcLengthResult result;
|
||||
if (!EvaluateMediaQueryTypedCalcLength(aPresContext, aValue, result) ||
|
||||
result.mExponent != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aResult = NSToCoordFloorClamped(result.mValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
nsMediaExpression::Matches(nsPresContext *aPresContext,
|
||||
const nsCSSValue& aActualValue) const
|
||||
@@ -161,11 +310,15 @@ nsMediaExpression::Matches(nsPresContext *aPresContext,
|
||||
case nsMediaFeature::eLength:
|
||||
{
|
||||
NS_ASSERTION(actual.IsLengthUnit(), "bad actual value");
|
||||
NS_ASSERTION(required.IsLengthUnit(), "bad required value");
|
||||
NS_ASSERTION(required.IsLengthUnit() || required.IsCalcUnit(),
|
||||
"bad required value");
|
||||
nscoord actualCoord = nsRuleNode::CalcLengthWithInitialFont(
|
||||
aPresContext, actual);
|
||||
nscoord requiredCoord = nsRuleNode::CalcLengthWithInitialFont(
|
||||
aPresContext, required);
|
||||
nscoord requiredCoord;
|
||||
if (!ComputeMediaQueryTypedCalcLength(aPresContext, required,
|
||||
requiredCoord)) {
|
||||
return false;
|
||||
}
|
||||
cmp = DoCompare(actualCoord, requiredCoord);
|
||||
}
|
||||
break;
|
||||
@@ -283,6 +436,10 @@ nsMediaExpression::Matches(nsPresContext *aPresContext,
|
||||
return cmp != 1;
|
||||
case nsMediaExpression::eEqual:
|
||||
return cmp == 0;
|
||||
case nsMediaExpression::eMinExclusive:
|
||||
return cmp == 1;
|
||||
case nsMediaExpression::eMaxExclusive:
|
||||
return cmp == -1;
|
||||
}
|
||||
NS_NOTREACHED("unexpected mRange");
|
||||
return false;
|
||||
@@ -487,10 +644,17 @@ nsMediaQuery::AppendToString(nsAString& aString) const
|
||||
aString.Append(nsDependentAtomString(*feature->mName));
|
||||
|
||||
if (expr.mValue.GetUnit() != eCSSUnit_Null) {
|
||||
aString.AppendLiteral(": ");
|
||||
if (expr.mRange == nsMediaExpression::eMinExclusive) {
|
||||
aString.AppendLiteral(" > ");
|
||||
} else if (expr.mRange == nsMediaExpression::eMaxExclusive) {
|
||||
aString.AppendLiteral(" < ");
|
||||
} else {
|
||||
aString.AppendLiteral(": ");
|
||||
}
|
||||
switch (feature->mValueType) {
|
||||
case nsMediaFeature::eLength:
|
||||
NS_ASSERTION(expr.mValue.IsLengthUnit(), "bad unit");
|
||||
NS_ASSERTION(expr.mValue.IsLengthUnit() || expr.mValue.IsCalcUnit(),
|
||||
"bad unit");
|
||||
// Use 'width' as a property that takes length values
|
||||
// written in the normal way.
|
||||
expr.mValue.AppendToString(eCSSProperty_width, aString,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user