mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 02:14:43 +00:00
Merge mozilla-central and inbound
This commit is contained in:
commit
e41a3e1c8a
@ -31,6 +31,7 @@
|
||||
gestures.forEach(AccessFuTest.addSequence);
|
||||
AccessFuTest.addFunc(stopGestureTracker);
|
||||
AccessFuTest.waitForExplicitFinish();
|
||||
Logger.logLevel = Logger.DEBUG;
|
||||
AccessFuTest.runTests();
|
||||
});
|
||||
}
|
||||
|
@ -156,11 +156,6 @@ pref("browser.search.suggest.enabled", true);
|
||||
// tell the search service that we don't really expose the "current engine"
|
||||
pref("browser.search.noCurrentEngine", true);
|
||||
|
||||
// Enable sparse localization by setting a few package locale overrides
|
||||
pref("chrome.override_package.global", "b2g-l10n");
|
||||
pref("chrome.override_package.mozapps", "b2g-l10n");
|
||||
pref("chrome.override_package.passwordmgr", "b2g-l10n");
|
||||
|
||||
// enable xul error pages
|
||||
pref("browser.xul.error_pages.enabled", true);
|
||||
|
||||
@ -899,9 +894,9 @@ pref("osfile.reset_worker_delay", 5000);
|
||||
pref("apz.asyncscroll.throttle", 40);
|
||||
pref("apz.pan_repaint_interval", 16);
|
||||
|
||||
// Maximum fling velocity in inches/ms. Slower devices may need to reduce this
|
||||
// to avoid checkerboarding. Note, float value must be set as a string.
|
||||
pref("apz.max_velocity_inches_per_ms", "0.0375");
|
||||
// APZ physics settings, tuned by UX designers
|
||||
pref("apz.max_velocity_inches_per_ms", "0.07");
|
||||
pref("apz.fling_friction", "0.003");
|
||||
|
||||
// Tweak default displayport values to reduce the risk of running out of
|
||||
// memory when zooming in
|
||||
|
@ -5,3 +5,8 @@
|
||||
#filter substitution
|
||||
|
||||
pref("general.useragent.locale", "@AB_CD@");
|
||||
|
||||
// Enable sparse localization by setting a few package locale overrides
|
||||
pref("chrome.override_package.global", "b2g-l10n");
|
||||
pref("chrome.override_package.mozapps", "b2g-l10n");
|
||||
pref("chrome.override_package.passwordmgr", "b2g-l10n");
|
||||
|
Binary file not shown.
Binary file not shown.
@ -212,3 +212,5 @@ http://example.cn:80 privileged
|
||||
http://example.co.jp:80 privileged
|
||||
http://example.fi:80 privileged
|
||||
|
||||
# Hosts for testing marketplace apps installations
|
||||
https://marketplace.firefox.com:443 privileged
|
||||
|
12
configure.in
12
configure.in
@ -5964,18 +5964,6 @@ if test "$MOZ_GAMEPAD"; then
|
||||
MOZ_GAMEPAD_BACKEND=cocoa
|
||||
;;
|
||||
WINNT)
|
||||
if test -z "$MOZ_HAS_WINSDK_WITH_D3D"; then
|
||||
if test -n "$MOZ_DIRECTX_SDK_PATH" ; then
|
||||
if ! test -f "$MOZ_DIRECTX_SDK_PATH"/lib/$MOZ_DIRECTX_SDK_CPU_SUFFIX/dxguid.lib ; then
|
||||
MOZ_GAMEPAD=
|
||||
fi
|
||||
elif test "$GCC" != "yes"; then
|
||||
MOZ_GAMEPAD=
|
||||
fi
|
||||
fi
|
||||
if test -z "$MOZ_GAMEPAD"; then
|
||||
AC_MSG_ERROR([Couldn't find the DirectX SDK, needed for gamepad support. Please install it or, reconfigure with --disable-gamepad to disable gamepad support.])
|
||||
fi
|
||||
MOZ_GAMEPAD_BACKEND=windows
|
||||
;;
|
||||
Linux)
|
||||
|
@ -3209,7 +3209,7 @@ nsObjectLoadingContent::LegacyCall(JSContext* aCx,
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < args.length(); i++) {
|
||||
if (!JS_WrapValue(aCx, args.handleAt(i))) {
|
||||
if (!JS_WrapValue(aCx, args[i])) {
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return JS::UndefinedValue();
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ public:
|
||||
|
||||
virtual ReentrantMonitor& GetReentrantMonitor() MOZ_OVERRIDE;
|
||||
|
||||
virtual bool IsShutdown() const MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual bool IsShutdown() const MOZ_OVERRIDE;
|
||||
|
||||
virtual bool OnStateMachineThread() const MOZ_OVERRIDE;
|
||||
|
||||
@ -40,11 +40,11 @@ public:
|
||||
|
||||
virtual MediaResource* GetResource() const MOZ_OVERRIDE;
|
||||
|
||||
virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) MOZ_OVERRIDE;
|
||||
|
||||
virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_OVERRIDE;
|
||||
|
||||
virtual int64_t GetEndMediaTime() const MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual int64_t GetEndMediaTime() const MOZ_OVERRIDE;
|
||||
|
||||
virtual int64_t GetMediaDuration() MOZ_OVERRIDE;
|
||||
|
||||
@ -56,25 +56,25 @@ public:
|
||||
|
||||
virtual void SetTransportSeekable(bool aTransportSeekable) MOZ_OVERRIDE;
|
||||
|
||||
virtual VideoFrameContainer* GetVideoFrameContainer() MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual VideoFrameContainer* GetVideoFrameContainer() MOZ_OVERRIDE;
|
||||
virtual layers::ImageContainer* GetImageContainer() MOZ_OVERRIDE;
|
||||
|
||||
virtual bool IsTransportSeekable() MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual bool IsTransportSeekable() MOZ_OVERRIDE;
|
||||
|
||||
virtual bool IsMediaSeekable() MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual bool IsMediaSeekable() MOZ_OVERRIDE;
|
||||
|
||||
virtual void MetadataLoaded(int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void QueueMetadata(int64_t aTime, int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void MetadataLoaded(int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags) MOZ_OVERRIDE;
|
||||
virtual void QueueMetadata(int64_t aTime, int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags) MOZ_OVERRIDE;
|
||||
|
||||
virtual void SetMediaEndTime(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void SetMediaEndTime(int64_t aTime) MOZ_OVERRIDE;
|
||||
|
||||
virtual void UpdatePlaybackPosition(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void UpdatePlaybackPosition(int64_t aTime) MOZ_OVERRIDE;
|
||||
|
||||
virtual void OnReadMetadataCompleted() MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void OnReadMetadataCompleted() MOZ_OVERRIDE;
|
||||
|
||||
virtual MediaDecoderOwner* GetOwner() MOZ_OVERRIDE;
|
||||
|
||||
virtual void NotifyWaitingForResourcesStatusChanged() MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void NotifyWaitingForResourcesStatusChanged() MOZ_OVERRIDE;
|
||||
|
||||
protected:
|
||||
// This monitor object is not really used to synchronize access to anything.
|
||||
|
@ -164,6 +164,14 @@ VideoTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
|
||||
if (!chunk.IsNull()) {
|
||||
gfx::IntSize imgsize = chunk.mFrame.GetImage()->GetSize();
|
||||
gfxIntSize intrinsicSize = chunk.mFrame.GetIntrinsicSize();
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
// Block the video frames come from video source.
|
||||
if (chunk.mFrame.GetImage()->GetFormat() != ImageFormat::PLANAR_YCBCR) {
|
||||
LOG("Can't encode this ImageFormat %x", chunk.mFrame.GetImage()->GetFormat());
|
||||
NotifyCancel();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
nsresult rv = Init(imgsize.width, imgsize.height,
|
||||
intrinsicSize.width, intrinsicSize.height,
|
||||
aTrackRate);
|
||||
|
@ -89,7 +89,9 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
MaybeSwitchVideoReaders(aTimeThreshold);
|
||||
if (MaybeSwitchVideoReaders(aTimeThreshold)) {
|
||||
GetVideoReader()->DecodeToTarget(aTimeThreshold);
|
||||
}
|
||||
|
||||
bool rv = GetVideoReader()->DecodeVideoFrame(aKeyFrameSkip, aTimeThreshold);
|
||||
|
||||
@ -129,7 +131,7 @@ public:
|
||||
void CallDecoderInitialization();
|
||||
|
||||
private:
|
||||
void MaybeSwitchVideoReaders(int64_t aTimeThreshold) {
|
||||
bool MaybeSwitchVideoReaders(int64_t aTimeThreshold) {
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
MOZ_ASSERT(mActiveVideoDecoder != -1);
|
||||
|
||||
@ -146,10 +148,11 @@ private:
|
||||
MSE_DEBUG("%p MSR::DecodeVF switching to %d", this, mActiveVideoDecoder);
|
||||
|
||||
GetVideoReader()->SetActive();
|
||||
GetVideoReader()->DecodeToTarget(aTimeThreshold);
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
MediaDecoderReader* GetAudioReader() {
|
||||
|
@ -66,6 +66,12 @@ SubBufferDecoder::GetResource() const
|
||||
return static_cast<SourceBufferResource*>(mResource.get());
|
||||
}
|
||||
|
||||
void
|
||||
SubBufferDecoder::NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded)
|
||||
{
|
||||
return mParentDecoder->NotifyDecodedFrames(aParsed, aDecoded);
|
||||
}
|
||||
|
||||
void
|
||||
SubBufferDecoder::SetMediaDuration(int64_t aDuration)
|
||||
{
|
||||
@ -192,12 +198,8 @@ SourceBuffer::GetBuffered(ErrorResult& aRv)
|
||||
return nullptr;
|
||||
}
|
||||
nsRefPtr<TimeRanges> ranges = new TimeRanges();
|
||||
for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
|
||||
nsRefPtr<TimeRanges> r = new TimeRanges();
|
||||
mDecoders[i]->GetBuffered(r);
|
||||
if (r->Length() > 0) {
|
||||
ranges->Add(r->GetStartTime(), r->GetEndTime());
|
||||
}
|
||||
if (mDecoder) {
|
||||
mDecoder->GetBuffered(ranges);
|
||||
}
|
||||
ranges->Normalize();
|
||||
return ranges.forget();
|
||||
@ -264,10 +266,10 @@ SourceBuffer::Abort(ErrorResult& aRv)
|
||||
mAppendWindowStart = 0;
|
||||
mAppendWindowEnd = PositiveInfinity<double>();
|
||||
|
||||
MSE_DEBUG("%p Abort: Discarding decoders.", this);
|
||||
if (mCurrentDecoder) {
|
||||
mCurrentDecoder->GetResource()->Ended();
|
||||
mCurrentDecoder = nullptr;
|
||||
MSE_DEBUG("%p Abort: Discarding decoder.", this);
|
||||
if (mDecoder) {
|
||||
mDecoder->GetResource()->Ended();
|
||||
mDecoder = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,16 +296,15 @@ void
|
||||
SourceBuffer::Detach()
|
||||
{
|
||||
Ended();
|
||||
mDecoders.Clear();
|
||||
mCurrentDecoder = nullptr;
|
||||
mDecoder = nullptr;
|
||||
mMediaSource = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
SourceBuffer::Ended()
|
||||
{
|
||||
for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
|
||||
mDecoders[i]->GetResource()->Ended();
|
||||
if (mDecoder) {
|
||||
mDecoder->GetResource()->Ended();
|
||||
}
|
||||
}
|
||||
|
||||
@ -316,6 +317,7 @@ SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
|
||||
, mTimestampOffset(0)
|
||||
, mAppendMode(SourceBufferAppendMode::Segments)
|
||||
, mUpdating(false)
|
||||
, mDecoderInit(false)
|
||||
{
|
||||
MOZ_ASSERT(aMediaSource);
|
||||
if (mType.EqualsIgnoreCase("video/webm") || mType.EqualsIgnoreCase("audio/webm")) {
|
||||
@ -324,6 +326,8 @@ SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
|
||||
// XXX: Plug in parsers for MPEG4, etc. here.
|
||||
mParser = new ContainerParser();
|
||||
}
|
||||
MSE_DEBUG("%p SourceBuffer: Creating initial decoder.", this);
|
||||
InitNewDecoder();
|
||||
}
|
||||
|
||||
already_AddRefed<SourceBuffer>
|
||||
@ -335,8 +339,8 @@ SourceBuffer::Create(MediaSource* aMediaSource, const nsACString& aType)
|
||||
|
||||
SourceBuffer::~SourceBuffer()
|
||||
{
|
||||
for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
|
||||
mDecoders[i]->GetResource()->Ended();
|
||||
if (mDecoder) {
|
||||
mDecoder->GetResource()->Ended();
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,11 +379,7 @@ SourceBuffer::InitNewDecoder()
|
||||
if (!decoder) {
|
||||
return false;
|
||||
}
|
||||
mDecoders.AppendElement(decoder);
|
||||
// XXX: At this point, we really want to push through any remaining
|
||||
// processing for the old decoder and discard it, rather than hanging on
|
||||
// to all of them in mDecoders.
|
||||
mCurrentDecoder = decoder;
|
||||
mDecoder = decoder;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -424,21 +424,24 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR
|
||||
// TODO: Test buffer full flag.
|
||||
StartUpdating();
|
||||
// TODO: Run buffer append algorithm asynchronously (would call StopUpdating()).
|
||||
if (mParser->IsInitSegmentPresent(aData, aLength) || !mCurrentDecoder) {
|
||||
MSE_DEBUG("%p AppendBuffer: New initialization segment, switching decoders.", this);
|
||||
if (mCurrentDecoder) {
|
||||
mCurrentDecoder->GetResource()->Ended();
|
||||
}
|
||||
if (!InitNewDecoder()) {
|
||||
aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling.
|
||||
return;
|
||||
if (!mDecoder || mParser->IsInitSegmentPresent(aData, aLength)) {
|
||||
if (!mDecoder || mDecoderInit) {
|
||||
MSE_DEBUG("%p AppendBuffer: New initialization segment, creating decoder.", this);
|
||||
mDecoder->GetResource()->Ended();
|
||||
|
||||
if (!InitNewDecoder()) {
|
||||
aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling.
|
||||
return;
|
||||
}
|
||||
}
|
||||
MSE_DEBUG("%p AppendBuffer: Decoder marked as initialized.", this);
|
||||
mDecoderInit = true;
|
||||
}
|
||||
// XXX: For future reference: NDA call must run on the main thread.
|
||||
mCurrentDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData),
|
||||
aLength,
|
||||
mCurrentDecoder->GetResource()->GetLength());
|
||||
mCurrentDecoder->GetResource()->AppendData(aData, aLength);
|
||||
mDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData),
|
||||
aLength,
|
||||
mDecoder->GetResource()->GetLength());
|
||||
mDecoder->GetResource()->AppendData(aData, aLength);
|
||||
|
||||
// Eviction uses a byte threshold. If the buffer is greater than the
|
||||
// number of bytes then data is evicted. The time range for this
|
||||
@ -446,7 +449,7 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR
|
||||
// evict data before that range across all SourceBuffer's it knows
|
||||
// about.
|
||||
const int evict_threshold = 1000000;
|
||||
bool evicted = mCurrentDecoder->GetResource()->EvictData(evict_threshold);
|
||||
bool evicted = mDecoder->GetResource()->EvictData(evict_threshold);
|
||||
if (evicted) {
|
||||
double start = 0.0;
|
||||
double end = 0.0;
|
||||
@ -479,14 +482,15 @@ SourceBuffer::GetBufferedStartEndTime(double* aStart, double* aEnd)
|
||||
void
|
||||
SourceBuffer::Evict(double aStart, double aEnd)
|
||||
{
|
||||
for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
|
||||
// Need to map time to byte offset then evict
|
||||
int64_t end = mDecoders[i]->ConvertToByteOffset(aEnd);
|
||||
if (end <= 0) {
|
||||
NS_WARNING("SourceBuffer::Evict failed");
|
||||
continue;
|
||||
}
|
||||
mDecoders[i]->GetResource()->EvictBefore(end);
|
||||
if (!mDecoder) {
|
||||
return;
|
||||
}
|
||||
// Need to map time to byte offset then evict
|
||||
int64_t end = mDecoder->ConvertToByteOffset(aEnd);
|
||||
if (end > 0) {
|
||||
mDecoder->GetResource()->EvictBefore(end);
|
||||
} else {
|
||||
NS_WARNING("SourceBuffer::Evict failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,10 +140,7 @@ private:
|
||||
|
||||
nsAutoPtr<ContainerParser> mParser;
|
||||
|
||||
// XXX: We only want to keep the current decoder alive, but need a way to
|
||||
// query @buffered for everything this SourceBuffer is responsible for.
|
||||
nsTArray<nsRefPtr<SubBufferDecoder>> mDecoders;
|
||||
nsRefPtr<SubBufferDecoder> mCurrentDecoder;
|
||||
nsRefPtr<SubBufferDecoder> mDecoder;
|
||||
|
||||
double mAppendWindowStart;
|
||||
double mAppendWindowEnd;
|
||||
@ -152,6 +149,8 @@ private:
|
||||
|
||||
SourceBufferAppendMode mAppendMode;
|
||||
bool mUpdating;
|
||||
|
||||
bool mDecoderInit;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
@ -40,6 +40,7 @@ public:
|
||||
virtual bool OnStateMachineThread() const MOZ_OVERRIDE;
|
||||
virtual bool OnDecodeThread() const MOZ_OVERRIDE;
|
||||
virtual SourceBufferResource* GetResource() const MOZ_OVERRIDE;
|
||||
virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_OVERRIDE;
|
||||
virtual void SetMediaDuration(int64_t aDuration) MOZ_OVERRIDE;
|
||||
virtual void UpdateEstimatedMediaDuration(int64_t aDuration) MOZ_OVERRIDE;
|
||||
virtual void SetMediaSeekable(bool aMediaSeekable) MOZ_OVERRIDE;
|
||||
|
@ -678,6 +678,7 @@ MediaEngineWebRTCVideoSource::Shutdown()
|
||||
void
|
||||
MediaEngineWebRTCVideoSource::AllocImpl() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
||||
|
||||
mCameraControl = ICameraControl::Create(mCaptureIndex);
|
||||
if (mCameraControl) {
|
||||
@ -818,6 +819,7 @@ MediaEngineWebRTCVideoSource::OnError(CameraErrorContext aContext, CameraError a
|
||||
void
|
||||
MediaEngineWebRTCVideoSource::OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType)
|
||||
{
|
||||
ReentrantMonitorAutoEnter sync(mCallbackMonitor);
|
||||
mLastCapture =
|
||||
static_cast<nsIDOMFile*>(new nsDOMMemoryFile(static_cast<void*>(aData),
|
||||
static_cast<uint64_t>(aLength),
|
||||
|
@ -2244,8 +2244,7 @@ this.DOMApplicationRegistry = {
|
||||
queuedDownload: {},
|
||||
queuedPackageDownload: {},
|
||||
|
||||
onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
aDontNeedNetwork) {
|
||||
onInstallSuccessAck: function(aManifestURL, aDontNeedNetwork) {
|
||||
// If we are offline, register to run when we'll be online.
|
||||
if ((Services.io.offline) && !aDontNeedNetwork) {
|
||||
let onlineWrapper = {
|
||||
@ -2345,7 +2344,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
|
||||
let dir = this._getAppDir(aId).path;
|
||||
let manFile = OS.Path.join(dir, manifestName);
|
||||
this._writeFile(manFile, JSON.stringify(aJsonManifest));
|
||||
return this._writeFile(manFile, JSON.stringify(aJsonManifest));
|
||||
},
|
||||
|
||||
// Add an app that is already installed to the registry.
|
||||
@ -2418,7 +2417,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
});
|
||||
}),
|
||||
|
||||
confirmInstall: function(aData, aProfileDir, aInstallSuccessCallback) {
|
||||
confirmInstall: Task.async(function*(aData, aProfileDir, aInstallSuccessCallback) {
|
||||
debug("confirmInstall");
|
||||
|
||||
let origin = Services.io.newURI(aData.app.origin, null, null);
|
||||
@ -2443,7 +2442,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
let app = this._setupApp(aData, id);
|
||||
|
||||
let jsonManifest = aData.isPackage ? app.updateManifest : app.manifest;
|
||||
this._writeManifestFile(id, aData.isPackage, jsonManifest);
|
||||
yield this._writeManifestFile(id, aData.isPackage, jsonManifest);
|
||||
|
||||
debug("app.origin: " + app.origin);
|
||||
let manifest = new ManifestHelper(jsonManifest, app.origin);
|
||||
@ -2477,42 +2476,15 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
aData.app[prop] = appObject[prop];
|
||||
}
|
||||
|
||||
let dontNeedNetwork = false;
|
||||
|
||||
if (manifest.appcache_path) {
|
||||
this.queuedDownload[app.manifestURL] = {
|
||||
manifest: manifest,
|
||||
app: appObject,
|
||||
profileDir: aProfileDir
|
||||
}
|
||||
}
|
||||
|
||||
// We notify about the successful installation via mgmt.oninstall and the
|
||||
// corresponging DOMRequest.onsuccess event as soon as the app is properly
|
||||
// saved in the registry.
|
||||
this._saveApps().then(() => {
|
||||
this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject });
|
||||
if (aData.isPackage && aData.autoInstall) {
|
||||
// Skip directly to onInstallSuccessAck, since there isn't
|
||||
// a WebappsRegistry to receive Webapps:Install:Return:OK and respond
|
||||
// Webapps:Install:Return:Ack when an app is being auto-installed.
|
||||
this.onInstallSuccessAck(app.manifestURL);
|
||||
} else {
|
||||
// Broadcast Webapps:Install:Return:OK so the WebappsRegistry can notify
|
||||
// the installing page about the successful install, after which it'll
|
||||
// respond Webapps:Install:Return:Ack, which calls onInstallSuccessAck.
|
||||
this.broadcastMessage("Webapps:Install:Return:OK", aData);
|
||||
}
|
||||
if (!aData.isPackage) {
|
||||
this.updateAppHandlers(null, app.manifest, app);
|
||||
if (aInstallSuccessCallback) {
|
||||
aInstallSuccessCallback(app.manifest);
|
||||
}
|
||||
}
|
||||
Services.obs.notifyObservers(null, "webapps-installed",
|
||||
JSON.stringify({ manifestURL: app.manifestURL }));
|
||||
});
|
||||
|
||||
let dontNeedNetwork = false;
|
||||
if (manifest.package_path) {
|
||||
} else if (manifest.package_path) {
|
||||
// If it is a local app then it must been installed from a local file
|
||||
// instead of web.
|
||||
#ifdef MOZ_ANDROID_SYNTHAPKS
|
||||
@ -2537,12 +2509,40 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
};
|
||||
}
|
||||
|
||||
// We notify about the successful installation via mgmt.oninstall and the
|
||||
// corresponding DOMRequest.onsuccess event as soon as the app is properly
|
||||
// saved in the registry.
|
||||
yield this._saveApps();
|
||||
|
||||
this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject });
|
||||
if (aData.isPackage && aData.autoInstall) {
|
||||
// Skip directly to onInstallSuccessAck, since there isn't
|
||||
// a WebappsRegistry to receive Webapps:Install:Return:OK and respond
|
||||
// Webapps:Install:Return:Ack when an app is being auto-installed.
|
||||
this.onInstallSuccessAck(app.manifestURL);
|
||||
} else {
|
||||
// Broadcast Webapps:Install:Return:OK so the WebappsRegistry can notify
|
||||
// the installing page about the successful install, after which it'll
|
||||
// respond Webapps:Install:Return:Ack, which calls onInstallSuccessAck.
|
||||
this.broadcastMessage("Webapps:Install:Return:OK", aData);
|
||||
}
|
||||
|
||||
if (!aData.isPackage) {
|
||||
this.updateAppHandlers(null, app.manifest, app);
|
||||
if (aInstallSuccessCallback) {
|
||||
aInstallSuccessCallback(app.manifest);
|
||||
}
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(null, "webapps-installed",
|
||||
JSON.stringify({ manifestURL: app.manifestURL }));
|
||||
|
||||
if (aData.forceSuccessAck) {
|
||||
// If it's a local install, there's no content process so just
|
||||
// ack the install.
|
||||
this.onInstallSuccessAck(app.manifestURL, dontNeedNetwork);
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* Install the package after successfully downloading it
|
||||
@ -3104,7 +3104,10 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
throw "CERTDB_ERROR";
|
||||
}
|
||||
|
||||
let [result, zipReader] = yield this._openSignedPackage(aZipFile, certDb);
|
||||
let [result, zipReader] = yield this._openSignedPackage(aApp.installOrigin,
|
||||
aApp.manifestURL,
|
||||
aZipFile,
|
||||
certDb);
|
||||
|
||||
// We cannot really know if the system date is correct or
|
||||
// not. What we can know is if it's after the build date or not,
|
||||
@ -3147,11 +3150,39 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
_openSignedPackage: function(aZipFile, aCertDb) {
|
||||
_openSignedPackage: function(aInstallOrigin, aManifestURL, aZipFile, aCertDb) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let root = TrustedRootCertificate.index;
|
||||
|
||||
let useReviewerCerts = false;
|
||||
try {
|
||||
useReviewerCerts = Services.prefs.
|
||||
getBoolPref("dom.mozApps.use_reviewer_certs");
|
||||
} catch (ex) { }
|
||||
|
||||
// We'll use the reviewer and dev certificates only if the pref is set to
|
||||
// true.
|
||||
if (useReviewerCerts) {
|
||||
let manifestPath = Services.io.newURI(aManifestURL, null, null).path;
|
||||
|
||||
switch (aInstallOrigin) {
|
||||
case "https://marketplace.firefox.com":
|
||||
root = manifestPath.startsWith("/reviewers/")
|
||||
? Ci.nsIX509CertDB.AppMarketplaceProdReviewersRoot
|
||||
: Ci.nsIX509CertDB.AppMarketplaceProdPublicRoot;
|
||||
break;
|
||||
|
||||
case "https://marketplace-dev.allizom.org":
|
||||
root = manifestPath.startsWith("/reviewers/")
|
||||
? Ci.nsIX509CertDB.AppMarketplaceDevReviewersRoot
|
||||
: Ci.nsIX509CertDB.AppMarketplaceDevPublicRoot;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
aCertDb.openSignedAppFileAsync(
|
||||
TrustedRootCertificate.index, aZipFile,
|
||||
root, aZipFile,
|
||||
function(aRv, aZipReader) {
|
||||
deferred.resolve([aRv, aZipReader]);
|
||||
}
|
||||
|
28
dom/apps/tests/marketplace/marketplace_app.webapp
Executable file
28
dom/apps/tests/marketplace/marketplace_app.webapp
Executable file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"version" : "2.0",
|
||||
"name" : "Flashlight (Linterna)",
|
||||
"description" : "Simple Flashlight that you can use everywhere without internet connection and also with cool modes: * Flashlight mode - * Disco mode - * Colors mode",
|
||||
"launch_path" : "/index.html",
|
||||
"icons": {
|
||||
"16": "/img/icons/mortar-16.png",
|
||||
"48": "/img/icons/mortar-48.png",
|
||||
"60": "/img/icons/mortar-60.png",
|
||||
"128": "/img/icons/mortar-128.png"
|
||||
},
|
||||
"developer": {
|
||||
"name": "William Vargas",
|
||||
"url" : "https://twitter.com/tecnowilliam"
|
||||
},
|
||||
"installs_allowed_from": ["*"],
|
||||
"locales": {
|
||||
"es": {
|
||||
"description": "Una simple linterna que puedes utilizar en cualquier lugar sin conexión a internet y con opciones geniales: * Modo Linterna - * Modo Disco - * Modo Colores",
|
||||
"developer": {
|
||||
"name": "William Vargas",
|
||||
"url" : "https://twitter.com/tecnowilliam"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default_locale": "en",
|
||||
"package_path": "marketplace_app.zip"
|
||||
}
|
@ -0,0 +1 @@
|
||||
Content-Type: application/x-web-app-manifest+json
|
BIN
dom/apps/tests/marketplace/marketplace_app.zip
Normal file
BIN
dom/apps/tests/marketplace/marketplace_app.zip
Normal file
Binary file not shown.
50
dom/apps/tests/marketplace/marketplace_privileged_app.webapp
Normal file
50
dom/apps/tests/marketplace/marketplace_privileged_app.webapp
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"version": "0.2.2",
|
||||
"name": "KitchenSink",
|
||||
"description": "Tests and report APIs available on the device",
|
||||
"launch_path": "/index.html",
|
||||
"developer": {
|
||||
"name": "Piotr Zalewa",
|
||||
"url": "http://www.mozillalabs.com"
|
||||
},
|
||||
"icons": {
|
||||
"16": "/img/icons/logo-16.png",
|
||||
"32": "/img/icons/logo-32.png",
|
||||
"64": "/img/icons/logo-64.png",
|
||||
"128": "/img/icons/logo-128.png",
|
||||
"256": "/img/icons/logo-256.png"
|
||||
},
|
||||
"type": "privileged",
|
||||
"permissions": {
|
||||
"alarms": {
|
||||
"description": "Testing"
|
||||
},
|
||||
"browser": {
|
||||
"description": "Testing"
|
||||
},
|
||||
"geolocation": {
|
||||
"description": "Testing"
|
||||
},
|
||||
"contacts": {
|
||||
"access": "readwrite",
|
||||
"description": "Testing"
|
||||
},
|
||||
"device-storage:sdcard": {
|
||||
"access": "readwrite",
|
||||
"description": "Testing"
|
||||
},
|
||||
"fmradio": {
|
||||
"description": "Testing"
|
||||
},
|
||||
"storage": {
|
||||
"description": "Testing"
|
||||
},
|
||||
"systemXHR": {
|
||||
"description": "Testing"
|
||||
},
|
||||
"tcp-socket": {
|
||||
"description": "Testing"
|
||||
}
|
||||
},
|
||||
"package_path": "marketplace_privileged_app.zip"
|
||||
}
|
@ -0,0 +1 @@
|
||||
Content-Type: application/x-web-app-manifest+json
|
BIN
dom/apps/tests/marketplace/marketplace_privileged_app.zip
Normal file
BIN
dom/apps/tests/marketplace/marketplace_privileged_app.zip
Normal file
Binary file not shown.
20
dom/apps/tests/marketplace/marketplace_reviewers_app.webapp
Normal file
20
dom/apps/tests/marketplace/marketplace_reviewers_app.webapp
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "Stopwatch",
|
||||
"description": "Simple stopwatch",
|
||||
"launch_path": "/index.html",
|
||||
"icons": {
|
||||
"128": "/static/img/icon.png"
|
||||
},
|
||||
"developer": {
|
||||
"name": "Andy McKay",
|
||||
"url": "http://www.agmweb.ca/blog/andy/"
|
||||
},
|
||||
"locales": {
|
||||
"fr": {
|
||||
"description": "Simple chronomètre"
|
||||
}
|
||||
},
|
||||
"installs_allowed_from": ["*"],
|
||||
"default_locale": "en",
|
||||
"package_path": "marketplace_reviewers_app.zip"
|
||||
}
|
@ -0,0 +1 @@
|
||||
Content-Type: application/x-web-app-manifest+json
|
BIN
dom/apps/tests/marketplace/marketplace_reviewers_app.zip
Normal file
BIN
dom/apps/tests/marketplace/marketplace_reviewers_app.zip
Normal file
Binary file not shown.
@ -14,10 +14,14 @@ support-files =
|
||||
signed_app_template.webapp
|
||||
signed/*
|
||||
test_packaged_app_common.js
|
||||
marketplace/*
|
||||
pkg_install_iframe.html
|
||||
|
||||
[test_app_update.html]
|
||||
[test_bug_795164.html]
|
||||
[test_install_receipts.html]
|
||||
[test_marketplace_pkg_install.html]
|
||||
skip-if = buildapp == "b2g" || toolkit == "android" # see bug 989806
|
||||
[test_packaged_app_install.html]
|
||||
[test_packaged_app_update.html]
|
||||
[test_receipt_operations.html]
|
||||
|
26
dom/apps/tests/pkg_install_iframe.html
Normal file
26
dom/apps/tests/pkg_install_iframe.html
Normal file
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Cross Origin Helper</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="application/javascript">
|
||||
|
||||
window.addEventListener("message", function onMessage(event) {
|
||||
window.removeEventListener("message", onMessage, false);
|
||||
|
||||
var request = navigator.mozApps.installPackage(event.data);
|
||||
|
||||
request.onerror = function() {
|
||||
parent.postMessage("Error: " + this.error.name, "*");
|
||||
};
|
||||
|
||||
request.onsuccess = function() {
|
||||
parent.postMessage("Application installed", "*");
|
||||
};
|
||||
}, false);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -102,7 +102,7 @@ function readFile(path) {
|
||||
|
||||
function makeResource(templatePath, version, packagePath, packageSize,
|
||||
appName, developerName, developerUrl) {
|
||||
var res = readFile(templatePath, false).
|
||||
var res = readFile(templatePath).
|
||||
replace(/VERSIONTOKEN/g, version).
|
||||
replace(/PACKAGEPATHTOKEN/g, packagePath).
|
||||
replace(/PACKAGESIZETOKEN/g, packageSize).
|
||||
|
198
dom/apps/tests/test_marketplace_pkg_install.html
Normal file
198
dom/apps/tests/test_marketplace_pkg_install.html
Normal file
@ -0,0 +1,198 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=989806
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 989806</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="test_packaged_app_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=989806">Mozilla Bug 989806</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="application/javascript;version=1.7">
|
||||
|
||||
"use strict";
|
||||
|
||||
let gApp = null;
|
||||
|
||||
let gExternalInstallOrigin = "http://mochi.test:8888/";
|
||||
let gExternalAppsPath = gExternalInstallOrigin + "tests/dom/apps/tests/marketplace/";
|
||||
|
||||
let gMarketplaceInstallOrigin = "https://marketplace.firefox.com/";
|
||||
let gMarketplaceAppsPath = gMarketplaceInstallOrigin + "tests/dom/apps/tests/marketplace/";
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function checkAppOnInstallSuccess(aExpected) {
|
||||
navigator.mozApps.mgmt.oninstall = function(evt) {
|
||||
info("Got oninstall event");
|
||||
gApp = evt.application;
|
||||
gApp.ondownloaderror = function() {
|
||||
ok(false, "Download should succeed (got error: " +
|
||||
gApp.downloadError.name + ")");
|
||||
PackagedTestHelper.finish();
|
||||
};
|
||||
gApp.ondownloadsuccess = function() {
|
||||
info("App downloaded");
|
||||
PackagedTestHelper.checkAppState(gApp, aExpected.version, aExpected,
|
||||
true, true, PackagedTestHelper.next);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function checkAppOnInstallError(aExpectedError) {
|
||||
navigator.mozApps.mgmt.oninstall = function(evt) {
|
||||
info("Got oninstall event");
|
||||
gApp = evt.application;
|
||||
gApp.ondownloaderror = function() {
|
||||
is(gApp.downloadError.name, aExpectedError,
|
||||
"Download fails with expected error: " + aExpectedError);
|
||||
if (gApp.downloadError.name != aExpectedError) {
|
||||
PackagedTestHelper.finish();
|
||||
} else {
|
||||
PackagedTestHelper.next();
|
||||
}
|
||||
};
|
||||
gApp.ondownloadsuccess = function() {
|
||||
ok(false, "App download should fail");
|
||||
PackagedTestHelper.finish();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function checkUninstallApp(aApp) {
|
||||
let req = navigator.mozApps.mgmt.uninstall(aApp);
|
||||
|
||||
req.onsuccess = function() {
|
||||
info("App uninstalled");
|
||||
aApp.ondownloadsuccess = null;
|
||||
aApp.ondownloaderror = null;
|
||||
aApp.onprogress = null;
|
||||
PackagedTestHelper.next();
|
||||
};
|
||||
req.onerror = function(evt) {
|
||||
ok(false, "App uninstallation should succeed (got unexpected " +
|
||||
evt.target.error.name + ")");
|
||||
PackagedTestHelper.finish();
|
||||
};
|
||||
}
|
||||
|
||||
function installApp(installOrigin, manifestURL) {
|
||||
let domParent = document.getElementById('container');
|
||||
|
||||
let ifr = document.createElement('iframe');
|
||||
ifr.setAttribute('mozbrowser', 'true');
|
||||
ifr.setAttribute("src", installOrigin + "tests/dom/apps/tests/pkg_install_iframe.html");
|
||||
|
||||
ifr.addEventListener("load", function onIFrameLoad() {
|
||||
ifr.removeEventListener("load", onIFrameLoad, false);
|
||||
|
||||
ifr.contentWindow.postMessage(manifestURL, "*");
|
||||
}, false);
|
||||
|
||||
ifr.addEventListener("mozbrowsererror", function onCertError(e) {
|
||||
ifr.removeEventListener("mozbrowsererror", onCertError);
|
||||
|
||||
ok(false, "mozbrowsererror: " + e.detail.type);
|
||||
domParent.removeChild(ifr);
|
||||
PackagedTestHelper.finish();
|
||||
});
|
||||
|
||||
window.addEventListener("message", function onMessage(event) {
|
||||
window.removeEventListener("message", onMessage);
|
||||
|
||||
is(event.data, "Application installed", "Application installed");
|
||||
|
||||
domParent.removeChild(ifr);
|
||||
});
|
||||
|
||||
domParent.appendChild(ifr);
|
||||
}
|
||||
|
||||
PackagedTestHelper.setSteps([
|
||||
function() {
|
||||
SpecialPowers.setAllAppsLaunchable(true);
|
||||
SpecialPowers.addPermission("webapps-manage", true, document);
|
||||
SpecialPowers.addPermission("browser", true, document);
|
||||
SpecialPowers.autoConfirmAppInstall(() =>
|
||||
SpecialPowers.pushPrefEnv({set: [["dom.mozBrowserFramesEnabled", true]]},
|
||||
PackagedTestHelper.next));
|
||||
},
|
||||
function() {
|
||||
info("== TEST == Marketplace packaged app from https://marketplace.firefox.com/");
|
||||
let miniManifestURL = gMarketplaceAppsPath + "marketplace_app.webapp"
|
||||
let expected = {
|
||||
name: "Flashlight (Linterna)",
|
||||
manifestURL: miniManifestURL,
|
||||
installOrigin: gMarketplaceInstallOrigin.slice(0, -1),
|
||||
progress: 0,
|
||||
installState: "installed",
|
||||
downloadAvailable: false,
|
||||
downloading: false,
|
||||
readyToApplyDownload: false,
|
||||
launch_path: "/index.html",
|
||||
version: "2.0",
|
||||
};
|
||||
checkAppOnInstallSuccess(expected);
|
||||
installApp(gMarketplaceInstallOrigin, miniManifestURL);
|
||||
},
|
||||
function() {
|
||||
info("== TEST == Marketplace privileged app from https://marketplace.firefox.com/");
|
||||
let miniManifestURL = gMarketplaceAppsPath + "marketplace_privileged_app.webapp"
|
||||
let expected = {
|
||||
name: "KitchenSink",
|
||||
manifestURL: miniManifestURL,
|
||||
installOrigin: gMarketplaceInstallOrigin.slice(0, -1),
|
||||
progress: 0,
|
||||
installState: "installed",
|
||||
downloadAvailable: false,
|
||||
downloading: false,
|
||||
readyToApplyDownload: false,
|
||||
launch_path: "/index.html",
|
||||
version: "0.2.2",
|
||||
};
|
||||
checkAppOnInstallSuccess(expected);
|
||||
installApp(gMarketplaceInstallOrigin, miniManifestURL);
|
||||
},
|
||||
function() {
|
||||
info("== TEST == Marketplace reviewers packaged app from https://marketplace.firefox.com/");
|
||||
checkAppOnInstallError("INVALID_SIGNATURE");
|
||||
installApp(gMarketplaceInstallOrigin, gMarketplaceAppsPath + "marketplace_reviewers_app.webapp");
|
||||
},
|
||||
function() {
|
||||
info("== TEST == Marketplace packaged app not from https://marketplace.firefox.com/");
|
||||
checkAppOnInstallError("INSTALL_FROM_DENIED");
|
||||
installApp(gExternalInstallOrigin, gExternalAppsPath + "marketplace_app.webapp");
|
||||
},
|
||||
function() {
|
||||
info("== TEST == Marketplace privileged app not from https://marketplace.firefox.com/");
|
||||
checkAppOnInstallError("INSTALL_FROM_DENIED");
|
||||
installApp(gExternalInstallOrigin, gExternalAppsPath + "marketplace_privileged_app.webapp");
|
||||
},
|
||||
function() {
|
||||
info("== TEST == Marketplace reviewers packaged app not from https://marketplace.firefox.com/");
|
||||
checkAppOnInstallError("INVALID_SIGNATURE");
|
||||
installApp(gExternalInstallOrigin, gExternalAppsPath + "marketplace_reviewers_app.webapp");
|
||||
},
|
||||
function() {
|
||||
PackagedTestHelper.finish();
|
||||
}
|
||||
]);
|
||||
|
||||
addLoadEvent(PackagedTestHelper.start);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
<div id="container"></div>
|
||||
</body>
|
||||
</html>
|
@ -12,31 +12,18 @@ var PackagedTestHelper = (function PackagedTestHelper() {
|
||||
var gAppName = "appname";
|
||||
var gApp = null;
|
||||
var gInstallOrigin = "http://mochi.test:8888";
|
||||
var timeoutID;
|
||||
|
||||
function timeoutError() {
|
||||
ok(false, "Timeout! Probably waiting on a app installation event");
|
||||
info("Finishing this test suite!");
|
||||
finish();
|
||||
}
|
||||
|
||||
function debug(aMsg) {
|
||||
//dump("== PackageTestHelper debug == " + aMsg + "\n");
|
||||
}
|
||||
|
||||
function next() {
|
||||
if (timeoutID) {
|
||||
clearTimeout(timeoutID);
|
||||
}
|
||||
index += 1;
|
||||
if (index >= steps.length) {
|
||||
ok(false, "Shouldn't get here!");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// There's nothing here that should take more than 30 seconds, even on
|
||||
// heavy loads. So there's no need to stop further tests for five minutes.
|
||||
timeoutID = setTimeout(timeoutError, 30000);
|
||||
steps[index]();
|
||||
} catch(ex) {
|
||||
ok(false, "Caught exception", ex);
|
||||
@ -48,10 +35,8 @@ var PackagedTestHelper = (function PackagedTestHelper() {
|
||||
}
|
||||
|
||||
function finish() {
|
||||
if (timeoutID) {
|
||||
clearTimeout(timeoutID);
|
||||
}
|
||||
SpecialPowers.removePermission("webapps-manage", document);
|
||||
SpecialPowers.removePermission("browser", document);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
@ -165,7 +150,7 @@ var PackagedTestHelper = (function PackagedTestHelper() {
|
||||
is(aApp.manifest.size, aExpectedApp.size, "Check size");
|
||||
}
|
||||
if (aApp.manifest) {
|
||||
is(aApp.manifest.launch_path, gSJSPath, "Check launch path");
|
||||
is(aApp.manifest.launch_path, aExpectedApp.launch_path || gSJSPath, "Check launch path");
|
||||
}
|
||||
if (aExpectedApp.manifestURL) {
|
||||
is(aApp.manifestURL, aExpectedApp.manifestURL, "Check manifestURL");
|
||||
|
@ -476,10 +476,7 @@ private:
|
||||
arguments.AppendElement(value);
|
||||
}
|
||||
|
||||
console->ProfileMethod(cx, mAction, arguments, error);
|
||||
if (error.Failed()) {
|
||||
NS_WARNING("Failed to call call profile() method to the ConsoleAPI.");
|
||||
}
|
||||
console->ProfileMethod(cx, mAction, arguments);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -662,23 +659,20 @@ Console::TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime)
|
||||
}
|
||||
|
||||
void
|
||||
Console::Profile(JSContext* aCx, const Sequence<JS::Value>& aData,
|
||||
ErrorResult& aRv)
|
||||
Console::Profile(JSContext* aCx, const Sequence<JS::Value>& aData)
|
||||
{
|
||||
ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData, aRv);
|
||||
ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData);
|
||||
}
|
||||
|
||||
void
|
||||
Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData,
|
||||
ErrorResult& aRv)
|
||||
Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData)
|
||||
{
|
||||
ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData, aRv);
|
||||
ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData);
|
||||
}
|
||||
|
||||
void
|
||||
Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
|
||||
const Sequence<JS::Value>& aData,
|
||||
ErrorResult& aRv)
|
||||
const Sequence<JS::Value>& aData)
|
||||
{
|
||||
if (!NS_IsMainThread()) {
|
||||
// Here we are in a worker thread.
|
||||
@ -688,6 +682,8 @@ Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
|
||||
return;
|
||||
}
|
||||
|
||||
ClearException ce(aCx);
|
||||
|
||||
RootedDictionary<ConsoleProfileEvent> event(aCx);
|
||||
event.mAction = aAction;
|
||||
|
||||
@ -700,15 +696,14 @@ Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
|
||||
|
||||
JS::Rooted<JS::Value> eventValue(aCx);
|
||||
if (!event.ToObject(aCx, &eventValue)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
|
||||
MOZ_ASSERT(eventObj);
|
||||
|
||||
if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue, JSPROP_ENUMERATE)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -717,7 +712,6 @@ Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
|
||||
const nsIID& iid = NS_GET_IID(nsISupports);
|
||||
|
||||
if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -836,13 +830,14 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
|
||||
ConsoleCallData* callData = new ConsoleCallData();
|
||||
mQueuedCalls.insertBack(callData);
|
||||
|
||||
ClearException ce(aCx);
|
||||
|
||||
callData->Initialize(aCx, aMethodName, aMethodString, aData);
|
||||
RAII raii(mQueuedCalls);
|
||||
|
||||
if (mWindow) {
|
||||
nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
|
||||
if (!webNav) {
|
||||
Throw(aCx, NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -857,7 +852,6 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
|
||||
nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, maxDepth);
|
||||
|
||||
if (!stack) {
|
||||
Throw(aCx, NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -866,7 +860,6 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
|
||||
uint32_t language;
|
||||
nsresult rv = stack->GetLanguage(&language);
|
||||
if (NS_FAILED(rv)) {
|
||||
Throw(aCx, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -877,7 +870,6 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
|
||||
callData->mTopStackFrame.ref(),
|
||||
language);
|
||||
if (NS_FAILED(rv)) {
|
||||
Throw(aCx, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -887,7 +879,6 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
|
||||
nsCOMPtr<nsIStackFrame> caller;
|
||||
rv = stack->GetCaller(getter_AddRefs(caller));
|
||||
if (NS_FAILED(rv)) {
|
||||
Throw(aCx, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -902,7 +893,6 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
|
||||
callData->mReifiedStack.construct();
|
||||
nsresult rv = ReifyStack(stack, callData->mReifiedStack.ref());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
Throw(aCx, rv);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -915,7 +905,6 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
|
||||
ErrorResult rv;
|
||||
nsRefPtr<nsPerformance> performance = win->GetPerformance(rv);
|
||||
if (rv.Failed()) {
|
||||
Throw(aCx, rv.ErrorCode());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1111,7 +1100,6 @@ Console::ProcessCallData(ConsoleCallData* aData)
|
||||
|
||||
JS::Rooted<JS::Value> eventValue(cx);
|
||||
if (!event.ToObject(cx, &eventValue)) {
|
||||
Throw(cx, NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -87,12 +87,10 @@ public:
|
||||
TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime);
|
||||
|
||||
void
|
||||
Profile(JSContext* aCx, const Sequence<JS::Value>& aData,
|
||||
ErrorResult& aRv);
|
||||
Profile(JSContext* aCx, const Sequence<JS::Value>& aData);
|
||||
|
||||
void
|
||||
ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData,
|
||||
ErrorResult& aRv);
|
||||
ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData);
|
||||
|
||||
void
|
||||
Assert(JSContext* aCx, bool aCondition, const Sequence<JS::Value>& aData);
|
||||
@ -181,8 +179,7 @@ private:
|
||||
|
||||
void
|
||||
ProfileMethod(JSContext* aCx, const nsAString& aAction,
|
||||
const Sequence<JS::Value>& aData,
|
||||
ErrorResult& aRv);
|
||||
const Sequence<JS::Value>& aData);
|
||||
|
||||
JS::Value
|
||||
IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
|
||||
|
@ -1975,11 +1975,11 @@ BaseStubConstructor(nsIWeakReference* aWeakOwner,
|
||||
|
||||
nsCOMPtr<nsIDOMWindow> currentWin(do_GetInterface(currentInner));
|
||||
rv = WrapNative(cx, currentWin, &NS_GET_IID(nsIDOMWindow),
|
||||
true, argv.handleAt(0));
|
||||
true, argv[0]);
|
||||
|
||||
for (size_t i = 1; i < argc; ++i) {
|
||||
argv[i] = args[i - 1];
|
||||
if (!JS_WrapValue(cx, argv.handleAt(i)))
|
||||
argv[i].set(args[i - 1]);
|
||||
if (!JS_WrapValue(cx, argv[i]))
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -1002,7 +1002,7 @@ nsJSContext::SetProperty(JS::Handle<JSObject*> aTarget, const char* aPropName, n
|
||||
// got the arguments, now attach them.
|
||||
|
||||
for (uint32_t i = 0; i < args.length(); ++i) {
|
||||
if (!JS_WrapValue(mContext, args.handleAt(i))) {
|
||||
if (!JS_WrapValue(mContext, args[i])) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
@ -1066,7 +1066,7 @@ nsJSContext::ConvertSupportsTojsvals(nsISupports* aArgs,
|
||||
if (argsArray) {
|
||||
for (uint32_t argCtr = 0; argCtr < argCount && NS_SUCCEEDED(rv); argCtr++) {
|
||||
nsCOMPtr<nsISupports> arg;
|
||||
JS::MutableHandle<JS::Value> thisVal = aArgsOut.handleAt(argCtr);
|
||||
JS::MutableHandle<JS::Value> thisVal = aArgsOut[argCtr];
|
||||
argsArray->QueryElementAt(argCtr, NS_GET_IID(nsISupports),
|
||||
getter_AddRefs(arg));
|
||||
if (!arg) {
|
||||
@ -1099,7 +1099,7 @@ nsJSContext::ConvertSupportsTojsvals(nsISupports* aArgs,
|
||||
} else {
|
||||
nsCOMPtr<nsIVariant> variant = do_QueryInterface(aArgs);
|
||||
if (variant) {
|
||||
rv = xpc->VariantToJS(cx, aScope, variant, aArgsOut.handleAt(0));
|
||||
rv = xpc->VariantToJS(cx, aScope, variant, aArgsOut[0]);
|
||||
} else {
|
||||
NS_ERROR("Not an array, not an interface?");
|
||||
rv = NS_ERROR_UNEXPECTED;
|
||||
|
@ -2054,7 +2054,7 @@ inline bool
|
||||
AddStringToIDVector(JSContext* cx, JS::AutoIdVector& vector, const char* name)
|
||||
{
|
||||
return vector.growBy(1) &&
|
||||
InternJSString(cx, vector[vector.length() - 1], name);
|
||||
InternJSString(cx, *(vector[vector.length() - 1]).address(), name);
|
||||
}
|
||||
|
||||
// Implementation of the bits that XrayWrapper needs
|
||||
|
@ -12525,8 +12525,8 @@ class CallbackMember(CGNativeMember):
|
||||
{
|
||||
'result': result,
|
||||
'successCode': "continue;\n" if arg.variadic else "break;\n",
|
||||
'jsvalRef': "argv.handleAt(%s)" % jsvalIndex,
|
||||
'jsvalHandle': "argv.handleAt(%s)" % jsvalIndex,
|
||||
'jsvalRef': "argv[%s]" % jsvalIndex,
|
||||
'jsvalHandle': "argv[%s]" % jsvalIndex,
|
||||
# XXXbz we don't have anything better to use for 'obj',
|
||||
# really... It's OK to use CallbackPreserveColor because
|
||||
# CallSetup already handled the unmark-gray bits for us.
|
||||
@ -12558,7 +12558,7 @@ class CallbackMember(CGNativeMember):
|
||||
// This is our current trailing argument; reduce argc
|
||||
--argc;
|
||||
} else {
|
||||
argv[${i}] = JS::UndefinedValue();
|
||||
argv[${i}].setUndefined();
|
||||
}
|
||||
""",
|
||||
argName=arg.identifier.name,
|
||||
@ -12816,7 +12816,7 @@ class CallbackSetter(CallbackAccessor):
|
||||
return fill(
|
||||
"""
|
||||
MOZ_ASSERT(argv.length() == 1);
|
||||
if (!JS_SetProperty(cx, CallbackPreserveColor(), "${attrName}", argv.handleAt(0))) {
|
||||
if (!JS_SetProperty(cx, CallbackPreserveColor(), "${attrName}", argv[0])) {
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return${errorReturn};
|
||||
}
|
||||
|
@ -244,7 +244,7 @@ ToJSValue(JSContext* aCx,
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < aLength; ++i) {
|
||||
if (!ToJSValue(aCx, aArguments[i], v.handleAt(i))) {
|
||||
if (!ToJSValue(aCx, aArguments[i], v[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,19 @@ enum GamepadMappingType
|
||||
StandardMapping = 1
|
||||
};
|
||||
|
||||
// Per spec:
|
||||
// https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#remapping
|
||||
const int kStandardGamepadButtons = 17;
|
||||
const int kStandardGamepadAxes = 4;
|
||||
|
||||
const int kButtonLeftTrigger = 6;
|
||||
const int kButtonRightTrigger = 7;
|
||||
|
||||
const int kLeftStickXAxis = 0;
|
||||
const int kLeftStickYAxis = 1;
|
||||
const int kRightStickXAxis = 2;
|
||||
const int kRightStickYAxis = 3;
|
||||
|
||||
class Gamepad : public nsISupports,
|
||||
public nsWrapperCache
|
||||
{
|
||||
|
@ -97,7 +97,9 @@ function createMediaElement(type, label) {
|
||||
* The error callback if the stream fails to be retrieved
|
||||
*/
|
||||
function getUserMedia(constraints, onSuccess, onError) {
|
||||
constraints["fake"] = FAKE_ENABLED;
|
||||
if (!("fake" in constraints)) {
|
||||
constraints["fake"] = FAKE_ENABLED;
|
||||
}
|
||||
|
||||
info("Call getUserMedia for " + JSON.stringify(constraints));
|
||||
navigator.mozGetUserMedia(constraints, onSuccess, onError);
|
||||
|
@ -131,7 +131,7 @@ MobileMessageManager::Send(JSContext* aCx, JS::Handle<JSObject*> aGlobal,
|
||||
uint32_t aServiceId,
|
||||
JS::Handle<JSString*> aNumber,
|
||||
const nsAString& aMessage,
|
||||
JS::Value* aRequest)
|
||||
JS::MutableHandle<JS::Value> aRequest)
|
||||
{
|
||||
nsCOMPtr<nsISmsService> smsService = do_GetService(SMS_SERVICE_CONTRACTID);
|
||||
NS_ENSURE_TRUE(smsService, NS_ERROR_FAILURE);
|
||||
@ -149,16 +149,14 @@ MobileMessageManager::Send(JSContext* aCx, JS::Handle<JSObject*> aGlobal,
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
js::AssertSameCompartment(aCx, aGlobal);
|
||||
JS::Rooted<JS::Value> rval(aCx);
|
||||
rv = nsContentUtils::WrapNative(aCx,
|
||||
static_cast<nsIDOMDOMRequest*>(request.get()),
|
||||
&rval);
|
||||
aRequest);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_ERROR("Failed to create the js value!");
|
||||
return rv;
|
||||
}
|
||||
|
||||
*aRequest = rval;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -208,7 +206,7 @@ MobileMessageManager::Send(JS::Handle<JS::Value> aNumber,
|
||||
|
||||
if (aNumber.isString()) {
|
||||
JS::Rooted<JSString*> str(aCx, aNumber.toString());
|
||||
return Send(aCx, global, serviceId, str, aMessage, aReturn.address());
|
||||
return Send(aCx, global, serviceId, str, aMessage, aReturn);
|
||||
}
|
||||
|
||||
// Must be an array then.
|
||||
@ -236,7 +234,7 @@ MobileMessageManager::Send(JS::Handle<JS::Value> aNumber,
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult rv = Send(aCx, global, serviceId, str, aMessage, &requests[i]);
|
||||
nsresult rv = Send(aCx, global, serviceId, str, aMessage, requests[i]);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ private:
|
||||
uint32_t aServiceId,
|
||||
JS::Handle<JSString*> aNumber,
|
||||
const nsAString& aMessage,
|
||||
JS::Value* aRequest);
|
||||
JS::MutableHandle<JS::Value> aRequest);
|
||||
|
||||
nsresult DispatchTrustedSmsEventToSelf(const char* aTopic,
|
||||
const nsAString& aEventName,
|
||||
|
@ -272,6 +272,7 @@ let NotificationDB = {
|
||||
var id = data.id;
|
||||
if (!this.notifications[origin]) {
|
||||
if (DEBUG) { debug("No notifications found for origin: " + origin); }
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -279,6 +280,7 @@ let NotificationDB = {
|
||||
var oldNotification = this.notifications[origin][id];
|
||||
if (!oldNotification) {
|
||||
if (DEBUG) { debug("No notification found with id: " + id); }
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -34,3 +34,4 @@ LOCAL_INCLUDES += [
|
||||
'/dom/ipc',
|
||||
]
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
|
||||
|
338
dom/src/notification/test/unit/test_notificationdb.js
Normal file
338
dom/src/notification/test/unit/test_notificationdb.js
Normal file
@ -0,0 +1,338 @@
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
|
||||
"@mozilla.org/childprocessmessagemanager;1",
|
||||
"nsIMessageSender");
|
||||
|
||||
let systemNotification = {
|
||||
origin: "app://system.gaiamobile.org/manifest.webapp",
|
||||
id: "{2bc883bf-2809-4432-b0f4-f54e10372764}",
|
||||
title: "SystemNotification:" + Date.now(),
|
||||
dir: "auto",
|
||||
lang: "",
|
||||
body: "System notification body",
|
||||
tag: "",
|
||||
icon: "icon.png"
|
||||
};
|
||||
|
||||
let calendarNotification = {
|
||||
origin: "app://calendar.gaiamobile.org/manifest.webapp",
|
||||
id: "{d8d11299-a58e-429b-9a9a-57c562982fbf}",
|
||||
title: "CalendarNotification:" + Date.now(),
|
||||
dir: "auto",
|
||||
lang: "",
|
||||
body: "Calendar notification body",
|
||||
tag: "",
|
||||
icon: "icon.png"
|
||||
};
|
||||
|
||||
function run_test() {
|
||||
do_get_profile();
|
||||
Cu.import("resource://gre/modules/NotificationDB.jsm");
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// Helper function to add a listener, send message and treat the reply
|
||||
function addAndSend(msg, reply, callback, payload, runNext = true) {
|
||||
let handler = {
|
||||
receiveMessage: function(message) {
|
||||
if (message.name === reply) {
|
||||
cpmm.removeMessageListener(reply, handler);
|
||||
callback(message);
|
||||
if (runNext) {
|
||||
run_next_test();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
cpmm.addMessageListener(reply, handler);
|
||||
cpmm.sendAsyncMessage(msg, payload);
|
||||
}
|
||||
|
||||
// helper fonction, comparing two notifications
|
||||
function compareNotification(notif1, notif2) {
|
||||
// retrieved notification should be the second one sent
|
||||
for (let prop in notif1) {
|
||||
// compare each property
|
||||
do_check_eq(notif1[prop], notif2[prop]);
|
||||
}
|
||||
}
|
||||
|
||||
// Get one notification, none exists
|
||||
add_test(function test_get_none() {
|
||||
let requestID = 0;
|
||||
let msgReply = "Notification:GetAll:Return:OK";
|
||||
let msgHandler = function(message) {
|
||||
do_check_eq(requestID, message.data.requestID);
|
||||
do_check_eq(0, message.data.notifications.length);
|
||||
};
|
||||
|
||||
addAndSend("Notification:GetAll", msgReply, msgHandler, {
|
||||
origin: systemNotification.origin,
|
||||
requestID: requestID
|
||||
});
|
||||
});
|
||||
|
||||
// Store one notification
|
||||
add_test(function test_send_one() {
|
||||
let requestID = 1;
|
||||
let msgReply = "Notification:Save:Return:OK";
|
||||
let msgHandler = function(message) {
|
||||
do_check_eq(requestID, message.data.requestID);
|
||||
};
|
||||
|
||||
addAndSend("Notification:Save", msgReply, msgHandler, {
|
||||
origin: systemNotification.origin,
|
||||
notification: systemNotification,
|
||||
requestID: requestID
|
||||
});
|
||||
});
|
||||
|
||||
// Get one notification, one exists
|
||||
add_test(function test_get_one() {
|
||||
let requestID = 2;
|
||||
let msgReply = "Notification:GetAll:Return:OK";
|
||||
let msgHandler = function(message) {
|
||||
do_check_eq(requestID, message.data.requestID);
|
||||
do_check_eq(1, message.data.notifications.length);
|
||||
// compare the content
|
||||
compareNotification(systemNotification, message.data.notifications[0]);
|
||||
};
|
||||
|
||||
addAndSend("Notification:GetAll", msgReply, msgHandler, {
|
||||
origin: systemNotification.origin,
|
||||
requestID: requestID
|
||||
});
|
||||
});
|
||||
|
||||
// Delete one notification
|
||||
add_test(function test_delete_one() {
|
||||
let requestID = 3;
|
||||
let msgReply = "Notification:Delete:Return:OK";
|
||||
let msgHandler = function(message) {
|
||||
do_check_eq(requestID, message.data.requestID);
|
||||
};
|
||||
|
||||
addAndSend("Notification:Delete", msgReply, msgHandler, {
|
||||
origin: systemNotification.origin,
|
||||
id: systemNotification.id,
|
||||
requestID: requestID
|
||||
});
|
||||
});
|
||||
|
||||
// Get one notification, none exists
|
||||
add_test(function test_get_none_again() {
|
||||
let requestID = 4;
|
||||
let msgReply = "Notification:GetAll:Return:OK";
|
||||
let msgHandler = function(message) {
|
||||
do_check_eq(requestID, message.data.requestID);
|
||||
do_check_eq(0, message.data.notifications.length);
|
||||
};
|
||||
|
||||
addAndSend("Notification:GetAll", msgReply, msgHandler, {
|
||||
origin: systemNotification.origin,
|
||||
requestID: requestID
|
||||
});
|
||||
});
|
||||
|
||||
// Delete one notification that do not exists anymore
|
||||
add_test(function test_delete_one_nonexistent() {
|
||||
let requestID = 5;
|
||||
let msgReply = "Notification:Delete:Return:OK";
|
||||
let msgHandler = function(message) {
|
||||
do_check_eq(requestID, message.data.requestID);
|
||||
};
|
||||
|
||||
addAndSend("Notification:Delete", msgReply, msgHandler, {
|
||||
origin: systemNotification.origin,
|
||||
id: systemNotification.id,
|
||||
requestID: requestID
|
||||
});
|
||||
});
|
||||
|
||||
// Store two notifications with the same id
|
||||
add_test(function test_send_two_get_one() {
|
||||
let requestID = 6;
|
||||
let calls = 0;
|
||||
|
||||
let msgGetReply = "Notification:GetAll:Return:OK";
|
||||
let msgGetHandler = function(message) {
|
||||
do_check_eq(requestID + 2, message.data.requestID);
|
||||
do_check_eq(1, message.data.notifications.length);
|
||||
// compare the content
|
||||
compareNotification(systemNotification, message.data.notifications[0]);
|
||||
};
|
||||
|
||||
let msgSaveReply = "Notification:Save:Return:OK";
|
||||
let msgSaveHandler = function(message) {
|
||||
calls += 1;
|
||||
if (calls === 2) {
|
||||
addAndSend("Notification:GetAll", msgGetReply, msgGetHandler, {
|
||||
origin: systemNotification.origin,
|
||||
requestID: (requestID + 2)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
addAndSend("Notification:Save", msgSaveReply, msgSaveHandler, {
|
||||
origin: systemNotification.origin,
|
||||
notification: systemNotification,
|
||||
requestID: requestID
|
||||
}, false);
|
||||
|
||||
addAndSend("Notification:Save", msgSaveReply, msgSaveHandler, {
|
||||
origin: systemNotification.origin,
|
||||
notification: systemNotification,
|
||||
requestID: (requestID + 1)
|
||||
}, false);
|
||||
});
|
||||
|
||||
// Delete previous notification
|
||||
add_test(function test_delete_previous() {
|
||||
let requestID = 8;
|
||||
let msgReply = "Notification:Delete:Return:OK";
|
||||
let msgHandler = function(message) {
|
||||
do_check_eq(requestID, message.data.requestID);
|
||||
};
|
||||
|
||||
addAndSend("Notification:Delete", msgReply, msgHandler, {
|
||||
origin: systemNotification.origin,
|
||||
id: systemNotification.id,
|
||||
requestID: requestID
|
||||
});
|
||||
});
|
||||
|
||||
// Store two notifications from same origin with the same tag
|
||||
add_test(function test_send_two_get_one() {
|
||||
let requestID = 10;
|
||||
let tag = "voicemail";
|
||||
|
||||
let systemNotification1 = systemNotification;
|
||||
systemNotification1.id = "{f271f9ee-3955-4c10-b1f2-af552fb270ee}";
|
||||
systemNotification1.tag = tag;
|
||||
|
||||
let systemNotification2 = systemNotification;
|
||||
systemNotification2.id = "{8ef9a628-f0f4-44b4-820d-c117573c33e3}";
|
||||
systemNotification2.tag = tag;
|
||||
|
||||
let msgGetReply = "Notification:GetAll:Return:OK";
|
||||
let msgGetNotifHandler = {
|
||||
receiveMessage: function(message) {
|
||||
if (message.name === msgGetReply) {
|
||||
cpmm.removeMessageListener(msgGetReply, msgGetNotifHandler);
|
||||
let notifications = message.data.notifications;
|
||||
// same tag, so replaced
|
||||
do_check_eq(1, notifications.length);
|
||||
// compare the content
|
||||
compareNotification(systemNotification2, notifications[0]);
|
||||
run_next_test();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cpmm.addMessageListener(msgGetReply, msgGetNotifHandler);
|
||||
|
||||
let msgSaveReply = "Notification:Save:Return:OK";
|
||||
let msgSaveCalls = 0;
|
||||
let msgSaveHandler = function(message) {
|
||||
msgSaveCalls++;
|
||||
// Once both request have been sent, trigger getall
|
||||
if (msgSaveCalls === 2) {
|
||||
cpmm.sendAsyncMessage("Notification:GetAll", {
|
||||
origin: systemNotification1.origin,
|
||||
requestID: message.data.requestID + 2 // 12, 13
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
addAndSend("Notification:Save", msgSaveReply, msgSaveHandler, {
|
||||
origin: systemNotification1.origin,
|
||||
notification: systemNotification1,
|
||||
requestID: requestID // 10
|
||||
}, false);
|
||||
|
||||
addAndSend("Notification:Save", msgSaveReply, msgSaveHandler, {
|
||||
origin: systemNotification2.origin,
|
||||
notification: systemNotification2,
|
||||
requestID: (requestID + 1) // 11
|
||||
}, false);
|
||||
});
|
||||
|
||||
// Store two notifications from two origins with the same tag
|
||||
add_test(function test_send_two_get_two() {
|
||||
let requestID = 20;
|
||||
let tag = "voicemail";
|
||||
|
||||
let systemNotification1 = systemNotification;
|
||||
systemNotification1.tag = tag;
|
||||
|
||||
let calendarNotification2 = calendarNotification;
|
||||
calendarNotification2.tag = tag;
|
||||
|
||||
let msgGetReply = "Notification:GetAll:Return:OK";
|
||||
let msgGetCalls = 0;
|
||||
let msgGetHandler = {
|
||||
receiveMessage: function(message) {
|
||||
if (message.name === msgGetReply) {
|
||||
msgGetCalls++;
|
||||
let notifications = message.data.notifications;
|
||||
|
||||
// one notification per origin
|
||||
do_check_eq(1, notifications.length);
|
||||
|
||||
// first call should be system notification
|
||||
if (msgGetCalls === 1) {
|
||||
compareNotification(systemNotification1, notifications[0]);
|
||||
}
|
||||
|
||||
// second and last call should be calendar notification
|
||||
if (msgGetCalls === 2) {
|
||||
cpmm.removeMessageListener(msgGetReply, msgGetHandler);
|
||||
compareNotification(calendarNotification2, notifications[0]);
|
||||
run_next_test();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
cpmm.addMessageListener(msgGetReply, msgGetHandler);
|
||||
|
||||
let msgSaveReply = "Notification:Save:Return:OK";
|
||||
let msgSaveCalls = 0;
|
||||
let msgSaveHandler = {
|
||||
receiveMessage: function(message) {
|
||||
if (message.name === msgSaveReply) {
|
||||
msgSaveCalls++;
|
||||
if (msgSaveCalls === 2) {
|
||||
cpmm.removeMessageListener(msgSaveReply, msgSaveHandler);
|
||||
|
||||
// Trigger getall for each origin
|
||||
cpmm.sendAsyncMessage("Notification:GetAll", {
|
||||
origin: systemNotification1.origin,
|
||||
requestID: message.data.requestID + 1 // 22
|
||||
});
|
||||
|
||||
cpmm.sendAsyncMessage("Notification:GetAll", {
|
||||
origin: calendarNotification2.origin,
|
||||
requestID: message.data.requestID + 2 // 23
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
cpmm.addMessageListener(msgSaveReply, msgSaveHandler);
|
||||
|
||||
cpmm.sendAsyncMessage("Notification:Save", {
|
||||
origin: systemNotification1.origin,
|
||||
notification: systemNotification1,
|
||||
requestID: requestID // 20
|
||||
});
|
||||
|
||||
cpmm.sendAsyncMessage("Notification:Save", {
|
||||
origin: calendarNotification2.origin,
|
||||
notification: calendarNotification2,
|
||||
requestID: (requestID + 1) // 21
|
||||
});
|
||||
});
|
5
dom/src/notification/test/unit/xpcshell.ini
Normal file
5
dom/src/notification/test/unit/xpcshell.ini
Normal file
@ -0,0 +1,5 @@
|
||||
[DEFAULT]
|
||||
head =
|
||||
tail =
|
||||
|
||||
[test_notificationdb.js]
|
@ -20,10 +20,7 @@ interface Console {
|
||||
void time(optional any time);
|
||||
void timeEnd(optional any time);
|
||||
|
||||
[Throws]
|
||||
void profile(any... data);
|
||||
|
||||
[Throws]
|
||||
void profileEnd(any... data);
|
||||
|
||||
void assert(boolean condition, any... data);
|
||||
|
@ -728,7 +728,7 @@ APZCTreeManager::HandOffFling(AsyncPanZoomController* aPrev, ScreenPoint aVeloci
|
||||
// otherwise built on touch-start and cleared on touch-end, and a fling
|
||||
// happens after touch-end. Note that, unlike DispatchScroll() which is
|
||||
// called on every touch-move during overscroll panning,
|
||||
// HandleFlingOverscroll() is only called once during a fling handoff,
|
||||
// HandOffFling() is only called once during a fling handoff,
|
||||
// so it's not worth trying to avoid building the handoff chain here.
|
||||
BuildOverscrollHandoffChain(aPrev);
|
||||
|
||||
|
@ -66,7 +66,7 @@
|
||||
#define APZC_LOG_FM(fm, prefix, ...) \
|
||||
APZC_LOG(prefix ":" \
|
||||
" i=(%ld %lld) cb=(%d %d %d %d) rcs=(%.3f %.3f) dp=(%.3f %.3f %.3f %.3f) dpm=(%.3f %.3f %.3f %.3f) um=%d " \
|
||||
"v=(%.3f %.3f %.3f %.3f) s=(%.3f %.3f) sr=(%.3f %.3f %.3f %.3f) z=(%.3f %.3f %.3f %.3f) u=(%d %lu)\n", \
|
||||
"v=(%.3f %.3f %.3f %.3f) s=(%.3f %.3f) sr=(%.3f %.3f %.3f %.3f) z(ld=%.3f r=%.3f cr=%.3f z=%.3f ts=%.3f) u=(%d %lu)\n", \
|
||||
__VA_ARGS__, \
|
||||
fm.mPresShellId, fm.GetScrollId(), \
|
||||
fm.mCompositionBounds.x, fm.mCompositionBounds.y, fm.mCompositionBounds.width, fm.mCompositionBounds.height, \
|
||||
@ -77,7 +77,7 @@
|
||||
fm.mViewport.x, fm.mViewport.y, fm.mViewport.width, fm.mViewport.height, \
|
||||
fm.GetScrollOffset().x, fm.GetScrollOffset().y, \
|
||||
fm.mScrollableRect.x, fm.mScrollableRect.y, fm.mScrollableRect.width, fm.mScrollableRect.height, \
|
||||
fm.mDevPixelsPerCSSPixel.scale, fm.mResolution.scale, fm.mCumulativeResolution.scale, fm.GetZoom().scale, \
|
||||
fm.mDevPixelsPerCSSPixel.scale, fm.mResolution.scale, fm.mCumulativeResolution.scale, fm.GetZoom().scale, fm.mTransformScale.scale, \
|
||||
fm.GetScrollOffsetUpdated(), fm.GetScrollGeneration()); \
|
||||
|
||||
// Static helper functions
|
||||
@ -163,6 +163,20 @@ typedef GeckoContentController::APZStateChange APZStateChange;
|
||||
* generated displayport's size is beyond that of the scrollable rect on the
|
||||
* opposite axis.
|
||||
*
|
||||
* "apz.fling_accel_interval_ms"
|
||||
* The time in milliseconds that determines whether a second fling will be
|
||||
* treated as accelerated. If two flings are started within this interval,
|
||||
* the second one will be accelerated. Setting an interval of 0 means that
|
||||
* acceleration will be disabled.
|
||||
*
|
||||
* "apz.fling_accel_base_mult"
|
||||
* "apz.fling_accel_supplemental_mult"
|
||||
* When applying an acceleration on a fling, the new computed velocity is
|
||||
* (new_fling_velocity * base_mult) + (old_velocity * supplemental_mult).
|
||||
* The base_mult and supplemental_mult multiplier values are controlled by
|
||||
* these prefs. Note that "old_velocity" here is the initial velocity of the
|
||||
* previous fling _after_ acceleration was applied to it (if applicable).
|
||||
*
|
||||
* "apz.fling_friction"
|
||||
* Amount of friction applied during flings.
|
||||
*
|
||||
@ -330,10 +344,40 @@ GetFrameTime() {
|
||||
|
||||
class FlingAnimation: public AsyncPanZoomAnimation {
|
||||
public:
|
||||
FlingAnimation(AsyncPanZoomController& aApzc)
|
||||
FlingAnimation(AsyncPanZoomController& aApzc, bool aApplyAcceleration)
|
||||
: AsyncPanZoomAnimation(TimeDuration::FromMilliseconds(gfxPrefs::APZFlingRepaintInterval()))
|
||||
, mApzc(aApzc)
|
||||
{}
|
||||
{
|
||||
TimeStamp now = GetFrameTime();
|
||||
ScreenPoint velocity(mApzc.mX.GetVelocity(), mApzc.mY.GetVelocity());
|
||||
|
||||
// If the last fling was very recent and in the same direction as this one,
|
||||
// boost the velocity to be the sum of the two. Check separate axes separately
|
||||
// because we could have two vertical flings with small horizontal components
|
||||
// on the opposite side of zero, and we still want the y-fling to get accelerated.
|
||||
// Note that the acceleration code is only applied on the APZC that receives the
|
||||
// actual touch event; the accelerated velocities are then handed off using the
|
||||
// normal HandOffFling codepath.
|
||||
if (aApplyAcceleration && !mApzc.mLastFlingTime.IsNull()
|
||||
&& (now - mApzc.mLastFlingTime).ToMilliseconds() < gfxPrefs::APZFlingAccelInterval()) {
|
||||
if (SameDirection(velocity.x, mApzc.mLastFlingVelocity.x)) {
|
||||
velocity.x = Accelerate(velocity.x, mApzc.mLastFlingVelocity.x);
|
||||
APZC_LOG("%p Applying fling x-acceleration from %f to %f (delta %f)\n",
|
||||
&mApzc, mApzc.mX.GetVelocity(), velocity.x, mApzc.mLastFlingVelocity.x);
|
||||
mApzc.mX.SetVelocity(velocity.x);
|
||||
}
|
||||
if (SameDirection(velocity.y, mApzc.mLastFlingVelocity.y)) {
|
||||
velocity.y = Accelerate(velocity.y, mApzc.mLastFlingVelocity.y);
|
||||
APZC_LOG("%p Applying fling y-acceleration from %f to %f (delta %f)\n",
|
||||
&mApzc, mApzc.mY.GetVelocity(), velocity.y, mApzc.mLastFlingVelocity.y);
|
||||
mApzc.mY.SetVelocity(velocity.y);
|
||||
}
|
||||
}
|
||||
|
||||
mApzc.mLastFlingTime = now;
|
||||
mApzc.mLastFlingVelocity = velocity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances a fling by an interpolated amount based on the passed in |aDelta|.
|
||||
* This should be called whenever sampling the content transform for this
|
||||
@ -344,6 +388,19 @@ public:
|
||||
const TimeDuration& aDelta);
|
||||
|
||||
private:
|
||||
static bool SameDirection(float aVelocity1, float aVelocity2)
|
||||
{
|
||||
return (aVelocity1 == 0.0f)
|
||||
|| (aVelocity2 == 0.0f)
|
||||
|| (IsNegative(aVelocity1) == IsNegative(aVelocity2));
|
||||
}
|
||||
|
||||
static float Accelerate(float aBase, float aSupplemental)
|
||||
{
|
||||
return (aBase * gfxPrefs::APZFlingAccelBaseMultiplier())
|
||||
+ (aSupplemental * gfxPrefs::APZFlingAccelSupplementalMultiplier());
|
||||
}
|
||||
|
||||
AsyncPanZoomController& mApzc;
|
||||
};
|
||||
|
||||
@ -755,7 +812,7 @@ nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent)
|
||||
mX.EndTouch();
|
||||
mY.EndTouch();
|
||||
SetState(FLING);
|
||||
StartAnimation(new FlingAnimation(*this));
|
||||
StartAnimation(new FlingAnimation(*this, true));
|
||||
return nsEventStatus_eConsumeNoDefault;
|
||||
|
||||
case PINCHING:
|
||||
@ -1183,7 +1240,7 @@ void AsyncPanZoomController::TakeOverFling(ScreenPoint aVelocity) {
|
||||
mX.SetVelocity(mX.GetVelocity() + aVelocity.x);
|
||||
mY.SetVelocity(mY.GetVelocity() + aVelocity.y);
|
||||
SetState(FLING);
|
||||
StartAnimation(new FlingAnimation(*this));
|
||||
StartAnimation(new FlingAnimation(*this, false));
|
||||
}
|
||||
|
||||
void AsyncPanZoomController::CallDispatchScroll(const ScreenPoint& aStartPoint, const ScreenPoint& aEndPoint,
|
||||
@ -1294,20 +1351,20 @@ bool FlingAnimation::Sample(FrameMetrics& aFrameMetrics,
|
||||
velocity.y = 0;
|
||||
}
|
||||
|
||||
// To hand off the fling, we call APZCTreeManager::HandleFlingOverscroll()
|
||||
// To hand off the fling, we call APZCTreeManager::HandOffFling()
|
||||
// which starts a new fling in the next APZC in the handoff chain with
|
||||
// the same velocity. For simplicity, the actual overscroll of the current
|
||||
// sample is discarded rather than being handed off. The compositor should
|
||||
// sample animations sufficiently frequently that this is not noticeable.
|
||||
|
||||
// Make a local copy of the tree manager pointer and check if it's not
|
||||
// null before calling HandleFlingOverscroll(). This is necessary because
|
||||
// null before calling HandOffFling(). This is necessary because
|
||||
// Destroy(), which nulls out mTreeManager, could be called concurrently.
|
||||
APZCTreeManager* treeManagerLocal = mApzc.mTreeManager;
|
||||
if (treeManagerLocal) {
|
||||
// APZC is holding mMonitor, so directly calling HandleFlingOverscroll()
|
||||
// APZC is holding mMonitor, so directly calling HandOffFling()
|
||||
// (which acquires the tree lock) would violate the lock ordering. Instead
|
||||
// we schedule HandleFlingOverscroll() to be called after mMonitor is
|
||||
// we schedule HandOffFling() to be called after mMonitor is
|
||||
// released.
|
||||
mDeferredTasks.append(NewRunnableMethod(treeManagerLocal,
|
||||
&APZCTreeManager::HandOffFling,
|
||||
@ -1718,11 +1775,11 @@ gfx3DMatrix AsyncPanZoomController::GetTransformToLastDispatchedPaint() {
|
||||
|
||||
void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetrics, bool aIsFirstPaint) {
|
||||
ReentrantMonitorAutoEnter lock(mMonitor);
|
||||
bool isDefault = mFrameMetrics.IsDefault();
|
||||
|
||||
mLastContentPaintMetrics = aLayerMetrics;
|
||||
UpdateTransformScale();
|
||||
|
||||
bool isDefault = mFrameMetrics.IsDefault();
|
||||
mFrameMetrics.mMayHaveTouchListeners = aLayerMetrics.mMayHaveTouchListeners;
|
||||
APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d", this, aIsFirstPaint);
|
||||
|
||||
|
@ -294,12 +294,6 @@ public:
|
||||
*/
|
||||
void CancelAnimation();
|
||||
|
||||
/**
|
||||
* Take over a fling with the given velocity from another APZC. Used for
|
||||
* during overscroll handoff for a fling.
|
||||
*/
|
||||
void TakeOverFling(ScreenPoint aVelocity);
|
||||
|
||||
/**
|
||||
* Returns allowed touch behavior for the given point on the scrollable layer.
|
||||
* Internally performs a kind of hit testing based on the regions constructed
|
||||
@ -501,14 +495,6 @@ protected:
|
||||
*/
|
||||
void DispatchRepaintRequest(const FrameMetrics& aFrameMetrics);
|
||||
|
||||
/**
|
||||
* Advances a fling by an interpolated amount based on the passed in |aDelta|.
|
||||
* This should be called whenever sampling the content transform for this
|
||||
* frame. Returns true if the fling animation should be advanced by one frame,
|
||||
* or false if there is no fling or the fling has ended.
|
||||
*/
|
||||
bool DoFling(const TimeDuration& aDelta);
|
||||
|
||||
/**
|
||||
* Gets the current frame metrics. This is *not* the Gecko copy stored in the
|
||||
* layers code.
|
||||
@ -777,7 +763,25 @@ private:
|
||||
RefPtr<AsyncPanZoomAnimation> mAnimation;
|
||||
|
||||
friend class Axis;
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
* The functions and members in this section are used to manage
|
||||
* fling animations.
|
||||
*/
|
||||
public:
|
||||
/**
|
||||
* Take over a fling with the given velocity from another APZC. Used for
|
||||
* during overscroll handoff for a fling.
|
||||
*/
|
||||
void TakeOverFling(ScreenPoint aVelocity);
|
||||
|
||||
private:
|
||||
friend class FlingAnimation;
|
||||
// The initial velocity of the most recent fling.
|
||||
ScreenPoint mLastFlingVelocity;
|
||||
// The time at which the most recent fling started.
|
||||
TimeStamp mLastFlingTime;
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
|
@ -111,6 +111,9 @@ private:
|
||||
DECL_GFX_PREF(Live, "apz.content_response_timeout", APZContentResponseTimeout, int32_t, 300);
|
||||
DECL_GFX_PREF(Live, "apz.cross_slide.enabled", APZCrossSlideEnabled, bool, false);
|
||||
DECL_GFX_PREF(Live, "apz.enlarge_displayport_when_clipped", APZEnlargeDisplayPortWhenClipped, bool, false);
|
||||
DECL_GFX_PREF(Live, "apz.fling_accel_interval_ms", APZFlingAccelInterval, int32_t, 500);
|
||||
DECL_GFX_PREF(Live, "apz.fling_accel_base_mult", APZFlingAccelBaseMultiplier, float, 1.0f);
|
||||
DECL_GFX_PREF(Live, "apz.fling_accel_supplemental_mult", APZFlingAccelSupplementalMultiplier, float, 1.0f);
|
||||
DECL_GFX_PREF(Once, "apz.fling_friction", APZFlingFriction, float, 0.002f);
|
||||
DECL_GFX_PREF(Live, "apz.fling_repaint_interval", APZFlingRepaintInterval, int32_t, 75);
|
||||
DECL_GFX_PREF(Once, "apz.fling_stopped_threshold", APZFlingStoppedThreshold, float, 0.01f);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -501,7 +501,7 @@ JavaScriptChild::AnswerCall(const ObjectId &objId, const nsTArray<JSParam> &argv
|
||||
ContextOptionsRef(cx).setDontReportUncaught(true);
|
||||
|
||||
HandleValueArray args = HandleValueArray::subarray(vals, 2, vals.length() - 2);
|
||||
bool success = JS::Call(cx, vals.handleAt(1), vals.handleAt(0), args, &rval);
|
||||
bool success = JS::Call(cx, vals[1], vals[0], args, &rval);
|
||||
if (!success)
|
||||
return fail(cx, rs);
|
||||
}
|
||||
@ -536,7 +536,7 @@ JavaScriptChild::AnswerCall(const ObjectId &objId, const nsTArray<JSParam> &argv
|
||||
// treat this as the outparam never having been set.
|
||||
for (size_t i = 0; i < vals.length(); i++) {
|
||||
JSVariant variant;
|
||||
if (!toVariant(cx, vals.handleAt(i), &variant))
|
||||
if (!toVariant(cx, vals[i], &variant))
|
||||
return fail(cx, rs);
|
||||
outparams->ReplaceElementAt(i, JSParam(variant));
|
||||
}
|
||||
@ -596,7 +596,7 @@ JavaScriptChild::AnswerGetPropertyNames(const ObjectId &objId, const uint32_t &f
|
||||
|
||||
for (size_t i = 0; i < props.length(); i++) {
|
||||
nsString name;
|
||||
if (!convertIdToGeckoString(cx, props.handleAt(i), &name))
|
||||
if (!convertIdToGeckoString(cx, props[i], &name))
|
||||
return fail(cx, rs);
|
||||
|
||||
names->AppendElement(name);
|
||||
|
@ -236,7 +236,7 @@ typedef enum JSWhyMagic
|
||||
JS_BLOCK_NEEDS_CLONE, /* value of static block object slot */
|
||||
JS_HASH_KEY_EMPTY, /* see class js::HashableValue */
|
||||
JS_ION_ERROR, /* error while running Ion code */
|
||||
JS_ION_BAILOUT, /* status code to signal EnterIon will OSR into Interpret */
|
||||
JS_ION_BAILOUT, /* missing recover instruction result */
|
||||
JS_OPTIMIZED_OUT, /* optimized out slot */
|
||||
JS_GENERIC_MAGIC /* for local use */
|
||||
} JSWhyMagic;
|
||||
|
@ -930,7 +930,7 @@ StructMetaTypeDescr::create(JSContext *cx,
|
||||
|
||||
// userFieldTypes[id] = typeObj
|
||||
if (!JSObject::defineGeneric(cx, userFieldTypes, id,
|
||||
fieldTypeObjs.handleAt(i), nullptr, nullptr,
|
||||
fieldTypeObjs[i], nullptr, nullptr,
|
||||
JSPROP_READONLY | JSPROP_PERMANENT))
|
||||
return nullptr;
|
||||
|
||||
|
@ -1163,27 +1163,27 @@ InitTypeClasses(JSContext* cx, HandleObject parent)
|
||||
if (!InitTypeConstructor(cx, parent, CTypeProto, CDataProto,
|
||||
sPointerFunction, nullptr, sPointerProps,
|
||||
sPointerInstanceFunctions, sPointerInstanceProps,
|
||||
protos.handleAt(SLOT_POINTERPROTO), protos.handleAt(SLOT_POINTERDATAPROTO)))
|
||||
protos[SLOT_POINTERPROTO], protos[SLOT_POINTERDATAPROTO]))
|
||||
return false;
|
||||
|
||||
if (!InitTypeConstructor(cx, parent, CTypeProto, CDataProto,
|
||||
sArrayFunction, nullptr, sArrayProps,
|
||||
sArrayInstanceFunctions, sArrayInstanceProps,
|
||||
protos.handleAt(SLOT_ARRAYPROTO), protos.handleAt(SLOT_ARRAYDATAPROTO)))
|
||||
protos[SLOT_ARRAYPROTO], protos[SLOT_ARRAYDATAPROTO]))
|
||||
return false;
|
||||
|
||||
if (!InitTypeConstructor(cx, parent, CTypeProto, CDataProto,
|
||||
sStructFunction, sStructFunctions, sStructProps,
|
||||
sStructInstanceFunctions, nullptr,
|
||||
protos.handleAt(SLOT_STRUCTPROTO), protos.handleAt(SLOT_STRUCTDATAPROTO)))
|
||||
protos[SLOT_STRUCTPROTO], protos[SLOT_STRUCTDATAPROTO]))
|
||||
return false;
|
||||
|
||||
if (!InitTypeConstructor(cx, parent, CTypeProto, protos.handleAt(SLOT_POINTERDATAPROTO),
|
||||
if (!InitTypeConstructor(cx, parent, CTypeProto, protos[SLOT_POINTERDATAPROTO],
|
||||
sFunctionFunction, nullptr, sFunctionProps, sFunctionInstanceFunctions, nullptr,
|
||||
protos.handleAt(SLOT_FUNCTIONPROTO), protos.handleAt(SLOT_FUNCTIONDATAPROTO)))
|
||||
protos[SLOT_FUNCTIONPROTO], protos[SLOT_FUNCTIONDATAPROTO]))
|
||||
return false;
|
||||
|
||||
protos[SLOT_CDATAPROTO] = CDataProto;
|
||||
protos[SLOT_CDATAPROTO].set(CDataProto);
|
||||
|
||||
// Create and attach the ctypes.{Int64,UInt64} constructors.
|
||||
// Each of these has, respectively:
|
||||
@ -1193,18 +1193,18 @@ InitTypeClasses(JSContext* cx, HandleObject parent)
|
||||
// * 'prototype' property:
|
||||
// * [[Class]] {"Int64Proto","UInt64Proto"}
|
||||
// * 'constructor' property === ctypes.{Int64,UInt64}
|
||||
protos[SLOT_INT64PROTO] = InitInt64Class(cx, parent, &sInt64ProtoClass,
|
||||
Int64::Construct, sInt64Functions, sInt64StaticFunctions);
|
||||
protos[SLOT_INT64PROTO].set(InitInt64Class(cx, parent, &sInt64ProtoClass,
|
||||
Int64::Construct, sInt64Functions, sInt64StaticFunctions));
|
||||
if (!protos[SLOT_INT64PROTO])
|
||||
return false;
|
||||
protos[SLOT_UINT64PROTO] = InitInt64Class(cx, parent, &sUInt64ProtoClass,
|
||||
UInt64::Construct, sUInt64Functions, sUInt64StaticFunctions);
|
||||
protos[SLOT_UINT64PROTO].set(InitInt64Class(cx, parent, &sUInt64ProtoClass,
|
||||
UInt64::Construct, sUInt64Functions, sUInt64StaticFunctions));
|
||||
if (!protos[SLOT_UINT64PROTO])
|
||||
return false;
|
||||
|
||||
// Finally, store a pointer to the global ctypes object.
|
||||
// Note that there is no other reliable manner of locating this object.
|
||||
protos[SLOT_CTYPES] = parent;
|
||||
protos[SLOT_CTYPES].set(parent);
|
||||
|
||||
// Attach the prototypes just created to each of ctypes.CType.prototype,
|
||||
// and the special type constructors, so we can access them when constructing
|
||||
@ -4827,7 +4827,7 @@ StructType::DefineInternal(JSContext* cx, JSObject* typeObj_, JSObject* fieldsOb
|
||||
Rooted<JSFlatString*> name(cx, ExtractStructField(cx, item, fieldType.address()));
|
||||
if (!name)
|
||||
return false;
|
||||
fieldRoots[i] = JS::ObjectValue(*fieldType);
|
||||
fieldRoots[i].setObject(*fieldType);
|
||||
|
||||
// Make sure each field name is unique
|
||||
FieldInfoHash::AddPtr entryPtr = fields->lookupForAdd(name);
|
||||
@ -5135,7 +5135,7 @@ StructType::BuildFieldsArray(JSContext* cx, JSObject* obj)
|
||||
for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) {
|
||||
const FieldInfoHash::Entry& entry = r.front();
|
||||
// Add the field descriptor to the array.
|
||||
if (!AddFieldToArray(cx, &fieldsVec[entry.value().mIndex],
|
||||
if (!AddFieldToArray(cx, fieldsVec[entry.value().mIndex].address(),
|
||||
entry.key(), entry.value().mType))
|
||||
return nullptr;
|
||||
}
|
||||
@ -5620,7 +5620,7 @@ FunctionType::Create(JSContext* cx, unsigned argc, jsval* vp)
|
||||
// Pull out the argument types from the array, if any.
|
||||
JS_ASSERT_IF(argTypes.length(), arrayObj);
|
||||
for (uint32_t i = 0; i < argTypes.length(); ++i) {
|
||||
if (!JS_GetElement(cx, arrayObj, i, argTypes.handleAt(i)))
|
||||
if (!JS_GetElement(cx, arrayObj, i, argTypes[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -5946,7 +5946,7 @@ FunctionType::ArgTypesGetter(JSContext* cx, JS::CallArgs args)
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
vec[i] = JS::ObjectValue(*fninfo->mArgTypes[i]);
|
||||
vec[i].setObject(*fninfo->mArgTypes[i]);
|
||||
|
||||
argTypes = JS_NewArrayObject(cx, vec);
|
||||
if (!argTypes)
|
||||
@ -6182,7 +6182,7 @@ CClosure::ClosureStub(ffi_cif* cif, void* result, void** args, void* userData)
|
||||
// Convert each argument, and have any CData objects created depend on
|
||||
// the existing buffers.
|
||||
RootedObject argType(cx, fninfo->mArgTypes[i]);
|
||||
if (!ConvertToJS(cx, argType, NullPtr(), args[i], false, false, &argv[i]))
|
||||
if (!ConvertToJS(cx, argType, NullPtr(), args[i], false, false, argv[i].address()))
|
||||
return;
|
||||
}
|
||||
|
||||
|
72
js/src/jit-test/tests/ion/dce-with-rinstructions.js
Normal file
72
js/src/jit-test/tests/ion/dce-with-rinstructions.js
Normal file
@ -0,0 +1,72 @@
|
||||
setJitCompilerOption("baseline.usecount.trigger", 10);
|
||||
setJitCompilerOption("ion.usecount.trigger", 20);
|
||||
var i;
|
||||
|
||||
// Check that we are able to remove the addition inside "ra" functions, when we
|
||||
// inline the first version of uceFault, and ensure that the bailout is correct
|
||||
// when uceFault is replaced (which cause an invalidation bailout)
|
||||
|
||||
var uceFault = function (i) {
|
||||
if (i > 98)
|
||||
uceFault = function (i) { return true; };
|
||||
return false;
|
||||
}
|
||||
|
||||
var uceFault_number = eval(uneval(uceFault).replace('uceFault', 'uceFault_number'));
|
||||
function ra_number(i) {
|
||||
var x = 1 + i;
|
||||
if (uceFault_number(i) || uceFault_number(i))
|
||||
assertEq(x, 100 /* = 1 + 99 */);
|
||||
return i;
|
||||
}
|
||||
|
||||
var uceFault_float = eval(uneval(uceFault).replace('uceFault', 'uceFault_float'));
|
||||
function ra_float(i) {
|
||||
var t = Math.fround(1/3);
|
||||
var fi = Math.fround(i);
|
||||
var x = Math.fround(Math.fround(Math.fround(Math.fround(t + fi) + t) + fi) + t);
|
||||
if (uceFault_float(i) || uceFault_float(i))
|
||||
assertEq(x, 199); /* != 199.00000002980232 (when computed with double additions) */
|
||||
return i;
|
||||
}
|
||||
|
||||
var uceFault_string = eval(uneval(uceFault).replace('uceFault', 'uceFault_string'));
|
||||
function ra_string(i) {
|
||||
var x = "s" + i;
|
||||
if (uceFault_string(i) || uceFault_string(i))
|
||||
assertEq(x, "s99");
|
||||
return i;
|
||||
}
|
||||
|
||||
var uceFault_object = eval(uneval(uceFault).replace('uceFault', 'uceFault_object'));
|
||||
function ra_object(i) {
|
||||
var x = {} + i;
|
||||
if (uceFault_object(i) || uceFault_object(i))
|
||||
assertEq(x, "[object Object]99");
|
||||
return i;
|
||||
}
|
||||
|
||||
for (i = 0; i < 100; i++) {
|
||||
ra_number(i);
|
||||
ra_float(i);
|
||||
ra_string(i);
|
||||
ra_object(i);
|
||||
}
|
||||
|
||||
// Test that we can refer multiple time to the same recover instruction, as well
|
||||
// as chaining recover instructions.
|
||||
|
||||
function alignedAlloc($size, $alignment) {
|
||||
var $1 = $size + 4 | 0;
|
||||
var $2 = $alignment - 1 | 0;
|
||||
var $3 = $1 + $2 | 0;
|
||||
var $4 = malloc($3);
|
||||
}
|
||||
|
||||
function malloc($bytes) {
|
||||
var $189 = undefined;
|
||||
var $198 = $189 + 8 | 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < 50; i++)
|
||||
alignedAlloc(608, 16);
|
@ -239,7 +239,7 @@ ValidateFFI(JSContext *cx, AsmJSModule::Global &global, HandleValue importVal,
|
||||
if (!v.isObject() || !v.toObject().is<JSFunction>())
|
||||
return LinkFail(cx, "FFI imports must be functions");
|
||||
|
||||
(*ffis)[global.ffiIndex()] = &v.toObject().as<JSFunction>();
|
||||
(*ffis)[global.ffiIndex()].set(&v.toObject().as<JSFunction>());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,8 @@ SnapshotIterator::SnapshotIterator(const IonBailoutIterator &iter)
|
||||
iter.ionScript()->recoversSize()),
|
||||
fp_(iter.jsFrame()),
|
||||
machine_(iter.machineState()),
|
||||
ionScript_(iter.ionScript())
|
||||
ionScript_(iter.ionScript()),
|
||||
instructionResults_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -558,9 +558,6 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC,
|
||||
if (callerPC == nullptr) {
|
||||
IonSpew(IonSpew_BaselineBailouts, " Setting SPS flag on top frame!");
|
||||
flags |= BaselineFrame::HAS_PUSHED_SPS_FRAME;
|
||||
} else if (js_JitOptions.profileInlineFrames) {
|
||||
IonSpew(IonSpew_BaselineBailouts, " Setting SPS flag on inline frame!");
|
||||
flags |= BaselineFrame::HAS_PUSHED_SPS_FRAME;
|
||||
}
|
||||
}
|
||||
|
||||
@ -674,7 +671,7 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC,
|
||||
size_t argOffset = builder.framePushed() + IonJSFrameLayout::offsetOfActualArg(i);
|
||||
*builder.valuePointerAtStackOffset(argOffset) = arg;
|
||||
} else {
|
||||
startFrameFormals[i] = arg;
|
||||
startFrameFormals[i].set(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -758,7 +755,7 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC,
|
||||
if (!savedCallerArgs.resize(inlined_args))
|
||||
return false;
|
||||
for (uint32_t i = 0; i < inlined_args; i++)
|
||||
savedCallerArgs[i] = iter.read();
|
||||
savedCallerArgs[i].set(iter.read());
|
||||
|
||||
if (IsSetPropPC(pc)) {
|
||||
// We would love to just save all the arguments and leave them
|
||||
@ -988,55 +985,24 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC,
|
||||
blFrame->unsetPushedSPSFrame();
|
||||
|
||||
if (cx->runtime()->spsProfiler.enabled()) {
|
||||
if (js_JitOptions.profileInlineFrames) {
|
||||
// If SPS is enabled, there are two corner cases to handle:
|
||||
// 1. If resuming into the prologue, and innermost frame is an inlined
|
||||
// frame, and bailout is because of argument check failure, then:
|
||||
// Top SPS profiler entry would be for caller frame.
|
||||
// Ion would not have set the PC index field on that frame
|
||||
// (since this bailout happens before MFunctionBoundary).
|
||||
// Make sure that's done now.
|
||||
// 2. If resuming into the prologue, and the bailout is NOT because of an
|
||||
// argument check, then:
|
||||
// Top SPS profiler entry would be for callee frame.
|
||||
// Ion would already have pushed an SPS entry for this frame.
|
||||
// The pc for this entry would be set to nullptr.
|
||||
// Make sure it's set to script->pc.
|
||||
if (caller && bailoutKind == Bailout_ArgumentCheck) {
|
||||
IonSpew(IonSpew_BaselineBailouts, " Setting PCidx on innermost "
|
||||
"inlined frame's parent's SPS entry (%s:%d) (pcIdx=%d)!",
|
||||
caller->filename(), caller->lineno(),
|
||||
caller->pcToOffset(callerPC));
|
||||
cx->runtime()->spsProfiler.updatePC(caller, callerPC);
|
||||
|
||||
} else if (bailoutKind != Bailout_ArgumentCheck) {
|
||||
IonSpew(IonSpew_BaselineBailouts,
|
||||
" Popping SPS entry for innermost inlined frame");
|
||||
cx->runtime()->spsProfiler.exit(script, fun);
|
||||
}
|
||||
|
||||
} else {
|
||||
// If not profiling inline frames, then this is logically simpler.
|
||||
//
|
||||
// 1. If resuming into inline code, then the top SPS entry will be
|
||||
// for the outermost caller, and will have an uninitialized PC.
|
||||
// This will be fixed up later in BailoutIonToBaseline.
|
||||
//
|
||||
// 2. If resuming into top-level code prologue, with ArgumentCheck,
|
||||
// no SPS entry will have been pushed. Can be left alone.
|
||||
//
|
||||
// 3. If resuming into top-level code prologue, without ArgumentCheck,
|
||||
// an SPS entry will have been pushed, and needs to be popped.
|
||||
//
|
||||
// 4. If resuming into top-level code main body, an SPS entry will
|
||||
// have been pushed, and can be left alone.
|
||||
//
|
||||
// Only need to handle case 3 here.
|
||||
if (!caller && bailoutKind != Bailout_ArgumentCheck) {
|
||||
IonSpew(IonSpew_BaselineBailouts,
|
||||
" Popping SPS entry for outermost frame");
|
||||
cx->runtime()->spsProfiler.exit(script, fun);
|
||||
}
|
||||
// 1. If resuming into inline code, then the top SPS entry will be
|
||||
// for the outermost caller, and will have an uninitialized PC.
|
||||
// This will be fixed up later in BailoutIonToBaseline.
|
||||
//
|
||||
// 2. If resuming into top-level code prologue, with ArgumentCheck,
|
||||
// no SPS entry will have been pushed. Can be left alone.
|
||||
//
|
||||
// 3. If resuming into top-level code prologue, without ArgumentCheck,
|
||||
// an SPS entry will have been pushed, and needs to be popped.
|
||||
//
|
||||
// 4. If resuming into top-level code main body, an SPS entry will
|
||||
// have been pushed, and can be left alone.
|
||||
//
|
||||
// Only need to handle case 3 here.
|
||||
if (!caller && bailoutKind != Bailout_ArgumentCheck) {
|
||||
IonSpew(IonSpew_BaselineBailouts,
|
||||
" Popping SPS entry for outermost frame");
|
||||
cx->runtime()->spsProfiler.exit(script, fun);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -1370,8 +1336,12 @@ jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, IonBailoutIt
|
||||
return BAILOUT_RETURN_FATAL_ERROR;
|
||||
IonSpew(IonSpew_BaselineBailouts, " Incoming frame ptr = %p", builder.startFrame());
|
||||
|
||||
AutoValueVector instructionResults(cx);
|
||||
SnapshotIterator snapIter(iter);
|
||||
|
||||
if (!snapIter.initIntructionResults(instructionResults))
|
||||
return BAILOUT_RETURN_FATAL_ERROR;
|
||||
|
||||
RootedFunction callee(cx, iter.maybeCallee());
|
||||
RootedScript scr(cx, iter.script());
|
||||
if (callee) {
|
||||
@ -1399,7 +1369,12 @@ jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, IonBailoutIt
|
||||
jsbytecode *topCallerPC = nullptr;
|
||||
|
||||
while (true) {
|
||||
MOZ_ASSERT(snapIter.instruction()->isResumePoint());
|
||||
if (!snapIter.instruction()->isResumePoint()) {
|
||||
if (!snapIter.instruction()->recover(cx, snapIter))
|
||||
return BAILOUT_RETURN_FATAL_ERROR;
|
||||
snapIter.nextInstruction();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frameNo > 0) {
|
||||
TraceLogStartEvent(logger, TraceLogCreateTextId(logger, scr));
|
||||
@ -1453,10 +1428,9 @@ jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, IonBailoutIt
|
||||
}
|
||||
IonSpew(IonSpew_BaselineBailouts, " Done restoring frames");
|
||||
|
||||
// If there were multiple inline frames unpacked, and inline frame profiling
|
||||
// is off, then the current top SPS frame is for the outermost caller, and
|
||||
// has an uninitialized PC. Initialize it now.
|
||||
if (frameNo > 0 && !js_JitOptions.profileInlineFrames)
|
||||
// If there were multiple inline frames unpacked, then the current top SPS frame
|
||||
// is for the outermost caller, and has an uninitialized PC. Initialize it now.
|
||||
if (frameNo > 0)
|
||||
cx->runtime()->spsProfiler.updatePC(topCaller, topCallerPC);
|
||||
|
||||
BailoutKind bailoutKind = snapIter.bailoutKind();
|
||||
|
@ -2342,6 +2342,58 @@ BaselineCompiler::emit_JSOP_INITELEM_SETTER()
|
||||
return emitInitElemGetterSetter();
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCompiler::emit_JSOP_INITELEM_INC()
|
||||
{
|
||||
// Keep the object and rhs on the stack.
|
||||
frame.syncStack(0);
|
||||
|
||||
// Load object in R0, index in R1.
|
||||
masm.loadValue(frame.addressOfStackValue(frame.peek(-3)), R0);
|
||||
masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R1);
|
||||
|
||||
// Call IC.
|
||||
ICSetElem_Fallback::Compiler stubCompiler(cx);
|
||||
if (!emitOpIC(stubCompiler.getStub(&stubSpace_)))
|
||||
return false;
|
||||
|
||||
// Pop the rhs
|
||||
frame.pop();
|
||||
|
||||
// Increment index
|
||||
Address indexAddr = frame.addressOfStackValue(frame.peek(-1));
|
||||
masm.incrementInt32Value(indexAddr);
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef bool (*SpreadFn)(JSContext *, HandleObject, HandleValue,
|
||||
HandleValue, MutableHandleValue);
|
||||
static const VMFunction SpreadInfo = FunctionInfo<SpreadFn>(js::SpreadOperation);
|
||||
|
||||
bool
|
||||
BaselineCompiler::emit_JSOP_SPREAD()
|
||||
{
|
||||
// Load index and iterable in R0 and R1, but keep values on the stack for
|
||||
// the decompiler.
|
||||
frame.syncStack(0);
|
||||
masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0);
|
||||
masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1);
|
||||
|
||||
prepareVMCall();
|
||||
|
||||
pushArg(R1);
|
||||
pushArg(R0);
|
||||
masm.extractObject(frame.addressOfStackValue(frame.peek(-3)), R0.scratchReg());
|
||||
pushArg(R0.scratchReg());
|
||||
|
||||
if (!callVM(SpreadInfo))
|
||||
return false;
|
||||
|
||||
frame.popn(2);
|
||||
frame.push(R0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCompiler::emit_JSOP_GETLOCAL()
|
||||
{
|
||||
|
@ -97,6 +97,8 @@ namespace jit {
|
||||
_(JSOP_INITELEM) \
|
||||
_(JSOP_INITELEM_GETTER) \
|
||||
_(JSOP_INITELEM_SETTER) \
|
||||
_(JSOP_INITELEM_INC) \
|
||||
_(JSOP_SPREAD) \
|
||||
_(JSOP_MUTATEPROTO) \
|
||||
_(JSOP_INITPROP) \
|
||||
_(JSOP_INITPROP_GETTER) \
|
||||
|
@ -111,7 +111,7 @@ BaselineFrame::copyRawFrameSlots(AutoValueVector *vec) const
|
||||
|
||||
mozilla::PodCopy(vec->begin(), argv(), nformals);
|
||||
for (unsigned i = 0; i < nfixed; i++)
|
||||
(*vec)[nformals + i] = *valueSlot(i);
|
||||
(*vec)[nformals + i].set(*valueSlot(i));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -5002,7 +5002,8 @@ DoSetElemFallback(JSContext *cx, BaselineFrame *frame, ICSetElem_Fallback *stub_
|
||||
|
||||
JS_ASSERT(op == JSOP_SETELEM ||
|
||||
op == JSOP_INITELEM ||
|
||||
op == JSOP_INITELEM_ARRAY);
|
||||
op == JSOP_INITELEM_ARRAY ||
|
||||
op == JSOP_INITELEM_INC);
|
||||
|
||||
RootedObject obj(cx, ToObjectFromStack(cx, objv));
|
||||
if (!obj)
|
||||
@ -5025,6 +5026,9 @@ DoSetElemFallback(JSContext *cx, BaselineFrame *frame, ICSetElem_Fallback *stub_
|
||||
JS_ASSERT(uint32_t(index.toInt32()) == GET_UINT24(pc));
|
||||
if (!InitArrayElemOperation(cx, pc, obj, index.toInt32(), rhs))
|
||||
return false;
|
||||
} else if (op == JSOP_INITELEM_INC) {
|
||||
if (!InitArrayElemOperation(cx, pc, obj, index.toInt32(), rhs))
|
||||
return false;
|
||||
} else {
|
||||
if (!SetObjectElement(cx, obj, index, rhs, script->strict(), script, pc))
|
||||
return false;
|
||||
|
@ -1728,7 +1728,7 @@ CodeGenerator::visitTypeBarrierV(LTypeBarrierV *lir)
|
||||
Register scratch = ToTempRegisterOrInvalid(lir->temp());
|
||||
|
||||
Label miss;
|
||||
masm.guardTypeSet(operand, lir->mir()->resultTypeSet(), scratch, &miss);
|
||||
masm.guardTypeSet(operand, lir->mir()->resultTypeSet(), lir->mir()->barrierKind(), scratch, &miss);
|
||||
if (!bailoutFrom(&miss, lir->snapshot()))
|
||||
return false;
|
||||
return true;
|
||||
@ -1737,6 +1737,8 @@ CodeGenerator::visitTypeBarrierV(LTypeBarrierV *lir)
|
||||
bool
|
||||
CodeGenerator::visitTypeBarrierO(LTypeBarrierO *lir)
|
||||
{
|
||||
MOZ_ASSERT(lir->mir()->barrierKind() != BarrierKind::TypeTagOnly);
|
||||
|
||||
Register obj = ToRegister(lir->object());
|
||||
Register scratch = ToTempRegisterOrInvalid(lir->temp());
|
||||
|
||||
@ -1754,7 +1756,7 @@ CodeGenerator::visitMonitorTypes(LMonitorTypes *lir)
|
||||
Register scratch = ToTempUnboxRegister(lir->temp());
|
||||
|
||||
Label matched, miss;
|
||||
masm.guardTypeSet(operand, lir->mir()->typeSet(), scratch, &miss);
|
||||
masm.guardTypeSet(operand, lir->mir()->typeSet(), lir->mir()->barrierKind(), scratch, &miss);
|
||||
if (!bailoutFrom(&miss, lir->snapshot()))
|
||||
return false;
|
||||
return true;
|
||||
@ -2710,7 +2712,7 @@ CodeGenerator::generateArgumentsChecks(bool bailout)
|
||||
// ... * sizeof(Value) - Scale by value size.
|
||||
// ArgToStackOffset(...) - Compute displacement within arg vector.
|
||||
int32_t offset = ArgToStackOffset((i - info.startArgSlot()) * sizeof(Value));
|
||||
masm.guardTypeSet(Address(StackPointer, offset), types, temp, &miss);
|
||||
masm.guardTypeSet(Address(StackPointer, offset), types, BarrierKind::TypeSet, temp, &miss);
|
||||
}
|
||||
|
||||
if (miss.used()) {
|
||||
@ -3186,7 +3188,7 @@ CodeGenerator::emitValueResultChecks(LInstruction *lir, MDefinition *mir)
|
||||
if (mir->resultTypeSet() && !mir->resultTypeSet()->unknown()) {
|
||||
// We have a result TypeSet, assert this value is in it.
|
||||
Label miss, ok;
|
||||
masm.guardTypeSet(output, mir->resultTypeSet(), temp1, &miss);
|
||||
masm.guardTypeSet(output, mir->resultTypeSet(), BarrierKind::TypeSet, temp1, &miss);
|
||||
masm.jump(&ok);
|
||||
|
||||
masm.bind(&miss);
|
||||
@ -8051,7 +8053,7 @@ CodeGenerator::visitProfilerStackOp(LProfilerStackOp *lir)
|
||||
|
||||
case MProfilerStackOp::Enter:
|
||||
if (gen->options.spsSlowAssertionsEnabled()) {
|
||||
if (!inlinedFunction || js_JitOptions.profileInlineFrames) {
|
||||
if (!inlinedFunction) {
|
||||
saveLive(lir);
|
||||
pushArg(ImmGCPtr(lir->script()));
|
||||
if (!callVM(SPSEnterInfo, lir))
|
||||
@ -8074,7 +8076,7 @@ CodeGenerator::visitProfilerStackOp(LProfilerStackOp *lir)
|
||||
|
||||
case MProfilerStackOp::Exit:
|
||||
if (gen->options.spsSlowAssertionsEnabled()) {
|
||||
if (!inlinedFunction || js_JitOptions.profileInlineFrames) {
|
||||
if (!inlinedFunction) {
|
||||
saveLive(lir);
|
||||
pushArg(ImmGCPtr(lir->script()));
|
||||
// Once we've exited, then we shouldn't emit instrumentation for
|
||||
|
@ -171,6 +171,9 @@ jit::EliminateDeadCode(MIRGenerator *mir, MIRGraph &graph)
|
||||
!inst->hasUses() && !inst->isGuard() &&
|
||||
!inst->isControlInstruction()) {
|
||||
inst = block->discardAt(inst);
|
||||
} else if (!inst->hasLiveDefUses() && inst->canRecoverOnBailout()) {
|
||||
inst->setRecoveredOnBailout();
|
||||
inst++;
|
||||
} else {
|
||||
inst++;
|
||||
}
|
||||
@ -1382,11 +1385,6 @@ jit::AssertBasicGraphCoherency(MIRGraph &graph)
|
||||
for (size_t i = 0; i < block->numPredecessors(); i++)
|
||||
JS_ASSERT(CheckPredecessorImpliesSuccessor(*block, block->getPredecessor(i)));
|
||||
|
||||
// Assert that use chains are valid for this instruction.
|
||||
for (MDefinitionIterator iter(*block); iter; iter++) {
|
||||
for (uint32_t i = 0, e = iter->numOperands(); i < e; i++)
|
||||
JS_ASSERT(CheckOperandImpliesUse(*iter, iter->getOperand(i)));
|
||||
}
|
||||
for (MResumePointIterator iter(block->resumePointsBegin()); iter != block->resumePointsEnd(); iter++) {
|
||||
for (uint32_t i = 0, e = iter->numOperands(); i < e; i++) {
|
||||
if (iter->getUseFor(i)->hasProducer())
|
||||
@ -1395,11 +1393,16 @@ jit::AssertBasicGraphCoherency(MIRGraph &graph)
|
||||
}
|
||||
for (MPhiIterator phi(block->phisBegin()); phi != block->phisEnd(); phi++) {
|
||||
JS_ASSERT(phi->numOperands() == block->numPredecessors());
|
||||
MOZ_ASSERT(!phi->isRecoveredOnBailout());
|
||||
}
|
||||
for (MDefinitionIterator iter(*block); iter; iter++) {
|
||||
JS_ASSERT(iter->block() == *block);
|
||||
for (MUseIterator i(iter->usesBegin()); i != iter->usesEnd(); i++)
|
||||
JS_ASSERT(CheckUseImpliesOperand(*iter, *i));
|
||||
|
||||
// Assert that use chains are valid for this instruction.
|
||||
for (uint32_t i = 0, end = iter->numOperands(); i < end; i++)
|
||||
JS_ASSERT(CheckOperandImpliesUse(*iter, iter->getOperand(i)));
|
||||
for (MUseIterator use(iter->usesBegin()); use != iter->usesEnd(); use++)
|
||||
JS_ASSERT(CheckUseImpliesOperand(*iter, *use));
|
||||
|
||||
if (iter->isInstruction()) {
|
||||
if (MResumePoint *resume = iter->toInstruction()->resumePoint()) {
|
||||
@ -1407,6 +1410,9 @@ jit::AssertBasicGraphCoherency(MIRGraph &graph)
|
||||
JS_ASSERT(ins->block() == iter->block());
|
||||
}
|
||||
}
|
||||
|
||||
if (iter->isRecoveredOnBailout())
|
||||
MOZ_ASSERT(!iter->hasLiveDefUses());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5049,7 +5049,7 @@ IonBuilder::jsop_funapplyarguments(uint32_t argc)
|
||||
return false;
|
||||
|
||||
types::TemporaryTypeSet *types = bytecodeTypes(pc);
|
||||
return pushTypeBarrier(apply, types, true);
|
||||
return pushTypeBarrier(apply, types, BarrierKind::TypeSet);
|
||||
}
|
||||
|
||||
// When inlining we have the arguments the function gets called with
|
||||
@ -5378,7 +5378,7 @@ IonBuilder::makeCall(JSFunction *target, CallInfo &callInfo, bool cloneAtCallsit
|
||||
if (call->isCallDOMNative())
|
||||
return pushDOMTypeBarrier(call, types, call->getSingleTarget());
|
||||
|
||||
return pushTypeBarrier(call, types, true);
|
||||
return pushTypeBarrier(call, types, BarrierKind::TypeSet);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -5426,7 +5426,7 @@ IonBuilder::jsop_eval(uint32_t argc)
|
||||
if (!string->mightBeType(MIRType_String)) {
|
||||
current->push(string);
|
||||
types::TemporaryTypeSet *types = bytecodeTypes(pc);
|
||||
return pushTypeBarrier(string, types, true);
|
||||
return pushTypeBarrier(string, types, BarrierKind::TypeSet);
|
||||
}
|
||||
|
||||
current->pushSlot(info().thisSlot());
|
||||
@ -5465,7 +5465,7 @@ IonBuilder::jsop_eval(uint32_t argc)
|
||||
current->push(ins);
|
||||
|
||||
types::TemporaryTypeSet *types = bytecodeTypes(pc);
|
||||
return resumeAfter(ins) && pushTypeBarrier(ins, types, true);
|
||||
return resumeAfter(ins) && pushTypeBarrier(ins, types, BarrierKind::TypeSet);
|
||||
}
|
||||
|
||||
return jsop_call(argc, /* constructing = */ false);
|
||||
@ -6274,7 +6274,7 @@ IonBuilder::testSingletonPropertyTypes(MDefinition *obj, JSObject *singleton, Pr
|
||||
// instruction replaces the top of the stack.
|
||||
// (5) Lastly, a type barrier instruction replaces the top of the stack.
|
||||
bool
|
||||
IonBuilder::pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, bool needsBarrier)
|
||||
IonBuilder::pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, BarrierKind kind)
|
||||
{
|
||||
// Barriers are never needed for instructions whose result will not be used.
|
||||
if (BytecodeIsPopped(pc))
|
||||
@ -6286,7 +6286,7 @@ IonBuilder::pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed,
|
||||
// must be a resume point capturing the original def, and resuming
|
||||
// to that point will explicitly monitor the new type.
|
||||
|
||||
if (!needsBarrier) {
|
||||
if (kind == BarrierKind::NoBarrier) {
|
||||
MDefinition *replace = ensureDefiniteType(def, observed->getKnownMIRType());
|
||||
if (replace != def) {
|
||||
current->pop();
|
||||
@ -6301,7 +6301,7 @@ IonBuilder::pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed,
|
||||
|
||||
current->pop();
|
||||
|
||||
MInstruction *barrier = MTypeBarrier::New(alloc(), def, observed);
|
||||
MInstruction *barrier = MTypeBarrier::New(alloc(), def, observed, kind);
|
||||
current->add(barrier);
|
||||
|
||||
if (barrier->type() == MIRType_Undefined)
|
||||
@ -6342,7 +6342,8 @@ IonBuilder::pushDOMTypeBarrier(MInstruction *ins, types::TemporaryTypeSet *obser
|
||||
JS_ASSERT(barrier);
|
||||
}
|
||||
|
||||
return pushTypeBarrier(replace, observed, barrier);
|
||||
return pushTypeBarrier(replace, observed,
|
||||
barrier ? BarrierKind::TypeSet : BarrierKind::NoBarrier);
|
||||
}
|
||||
|
||||
MDefinition *
|
||||
@ -6449,13 +6450,13 @@ IonBuilder::getStaticName(JSObject *staticObject, PropertyName *name, bool *psuc
|
||||
}
|
||||
|
||||
types::TemporaryTypeSet *types = bytecodeTypes(pc);
|
||||
bool barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), staticType,
|
||||
name, types, /* updateObserved = */ true);
|
||||
BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), staticType,
|
||||
name, types, /* updateObserved = */ true);
|
||||
|
||||
JSObject *singleton = types->getSingleton();
|
||||
|
||||
MIRType knownType = types->getKnownMIRType();
|
||||
if (!barrier) {
|
||||
if (barrier == BarrierKind::NoBarrier) {
|
||||
if (singleton) {
|
||||
// Try to inline a known constant value.
|
||||
if (testSingletonProperty(staticObject, name) == singleton)
|
||||
@ -6470,7 +6471,7 @@ IonBuilder::getStaticName(JSObject *staticObject, PropertyName *name, bool *psuc
|
||||
MInstruction *obj = constant(ObjectValue(*staticObject));
|
||||
|
||||
MIRType rvalType = types->getKnownMIRType();
|
||||
if (barrier)
|
||||
if (barrier != BarrierKind::NoBarrier)
|
||||
rvalType = MIRType_Value;
|
||||
|
||||
return loadSlot(obj, property.maybeTypes()->definiteSlot(), NumFixedSlots(staticObject),
|
||||
@ -6612,7 +6613,7 @@ IonBuilder::jsop_getname(PropertyName *name)
|
||||
return false;
|
||||
|
||||
types::TemporaryTypeSet *types = bytecodeTypes(pc);
|
||||
return pushTypeBarrier(ins, types, true);
|
||||
return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -6631,7 +6632,7 @@ IonBuilder::jsop_intrinsic(PropertyName *name)
|
||||
if (!resumeAfter(ins))
|
||||
return false;
|
||||
|
||||
return pushTypeBarrier(ins, types, true);
|
||||
return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
|
||||
}
|
||||
|
||||
// Bake in the intrinsic. Make sure that TI agrees with us on the type.
|
||||
@ -6696,7 +6697,7 @@ IonBuilder::jsop_getelem()
|
||||
return false;
|
||||
|
||||
types::TemporaryTypeSet *types = bytecodeTypes(pc);
|
||||
return pushTypeBarrier(ins, types, true);
|
||||
return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
|
||||
}
|
||||
|
||||
bool emitted = false;
|
||||
@ -6738,7 +6739,7 @@ IonBuilder::jsop_getelem()
|
||||
return false;
|
||||
|
||||
types::TemporaryTypeSet *types = bytecodeTypes(pc);
|
||||
return pushTypeBarrier(ins, types, true);
|
||||
return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -7004,7 +7005,7 @@ IonBuilder::pushDerivedTypedObject(bool *emitted,
|
||||
{
|
||||
derivedTypedObj->setResultTypeSet(observedTypes);
|
||||
} else {
|
||||
if (!pushTypeBarrier(derivedTypedObj, observedTypes, true))
|
||||
if (!pushTypeBarrier(derivedTypedObj, observedTypes, BarrierKind::TypeSet))
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -7186,7 +7187,7 @@ IonBuilder::getElemTryArguments(bool *emitted, MDefinition *obj, MDefinition *in
|
||||
current->push(load);
|
||||
|
||||
types::TemporaryTypeSet *types = bytecodeTypes(pc);
|
||||
if (!pushTypeBarrier(load, types, true))
|
||||
if (!pushTypeBarrier(load, types, BarrierKind::TypeSet))
|
||||
return false;
|
||||
|
||||
*emitted = true;
|
||||
@ -7255,18 +7256,19 @@ IonBuilder::getElemTryCache(bool *emitted, MDefinition *obj, MDefinition *index)
|
||||
// Emit GetElementCache.
|
||||
|
||||
types::TemporaryTypeSet *types = bytecodeTypes(pc);
|
||||
bool barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj, nullptr, types);
|
||||
BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj,
|
||||
nullptr, types);
|
||||
|
||||
// Always add a barrier if the index might be a string, so that the cache
|
||||
// can attach stubs for particular properties.
|
||||
if (index->mightBeType(MIRType_String))
|
||||
barrier = true;
|
||||
barrier = BarrierKind::TypeSet;
|
||||
|
||||
// See note about always needing a barrier in jsop_getprop.
|
||||
if (needsToMonitorMissingProperties(types))
|
||||
barrier = true;
|
||||
barrier = BarrierKind::TypeSet;
|
||||
|
||||
MInstruction *ins = MGetElementCache::New(alloc(), obj, index, barrier);
|
||||
MInstruction *ins = MGetElementCache::New(alloc(), obj, index, barrier != BarrierKind::NoBarrier);
|
||||
|
||||
current->add(ins);
|
||||
current->push(ins);
|
||||
@ -7275,7 +7277,7 @@ IonBuilder::getElemTryCache(bool *emitted, MDefinition *obj, MDefinition *index)
|
||||
return false;
|
||||
|
||||
// Spice up type information.
|
||||
if (index->type() == MIRType_Int32 && !barrier) {
|
||||
if (index->type() == MIRType_Int32 && barrier == BarrierKind::NoBarrier) {
|
||||
bool needHoleCheck = !ElementAccessIsPacked(constraints(), obj);
|
||||
MIRType knownType = GetElemKnownType(needHoleCheck, types);
|
||||
|
||||
@ -7302,7 +7304,8 @@ IonBuilder::jsop_getelem_dense(MDefinition *obj, MDefinition *index)
|
||||
AddObjectsForPropertyRead(obj, nullptr, types);
|
||||
}
|
||||
|
||||
bool barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj, nullptr, types);
|
||||
BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj,
|
||||
nullptr, types);
|
||||
bool needsHoleCheck = !ElementAccessIsPacked(constraints(), obj);
|
||||
|
||||
// Reads which are on holes in the object do not have to bail out if
|
||||
@ -7313,7 +7316,7 @@ IonBuilder::jsop_getelem_dense(MDefinition *obj, MDefinition *index)
|
||||
!ElementAccessHasExtraIndexedProperty(constraints(), obj);
|
||||
|
||||
MIRType knownType = MIRType_Value;
|
||||
if (!barrier)
|
||||
if (barrier == BarrierKind::NoBarrier)
|
||||
knownType = GetElemKnownType(needsHoleCheck, types);
|
||||
|
||||
// Ensure index is an integer.
|
||||
@ -7341,7 +7344,7 @@ IonBuilder::jsop_getelem_dense(MDefinition *obj, MDefinition *index)
|
||||
ExecutionMode executionMode = info().executionMode();
|
||||
bool loadDouble =
|
||||
executionMode == SequentialExecution &&
|
||||
!barrier &&
|
||||
barrier == BarrierKind::NoBarrier &&
|
||||
loopDepth_ &&
|
||||
!readOutOfBounds &&
|
||||
!needsHoleCheck &&
|
||||
@ -7392,7 +7395,7 @@ IonBuilder::jsop_getelem_dense(MDefinition *obj, MDefinition *index)
|
||||
// NB: we have not added a MConvertElementsToDoubles MIR, so we
|
||||
// cannot *assume* the result is a double.
|
||||
if (executionMode == ParallelExecution &&
|
||||
barrier &&
|
||||
barrier != BarrierKind::NoBarrier &&
|
||||
types->getKnownMIRType() == MIRType_Int32 &&
|
||||
objTypes &&
|
||||
objTypes->convertDoubleElements(constraints()) == types::TemporaryTypeSet::AlwaysConvertToDoubles)
|
||||
@ -7402,7 +7405,7 @@ IonBuilder::jsop_getelem_dense(MDefinition *obj, MDefinition *index)
|
||||
if (!types)
|
||||
return false;
|
||||
|
||||
barrier = false; // Don't need a barrier anymore
|
||||
barrier = BarrierKind::NoBarrier; // Don't need a barrier anymore
|
||||
}
|
||||
|
||||
if (knownType != MIRType_Value)
|
||||
@ -7561,7 +7564,7 @@ IonBuilder::jsop_getelem_typed(MDefinition *obj, MDefinition *index,
|
||||
// observed (we've only read out-of-bounds values). Note that for
|
||||
// Uint32Array, we only check for int32: if allowDouble is false we
|
||||
// will bailout when we read a double.
|
||||
bool needsBarrier = true;
|
||||
BarrierKind barrier = BarrierKind::TypeSet;
|
||||
switch (arrayType) {
|
||||
case ScalarTypeDescr::TYPE_INT8:
|
||||
case ScalarTypeDescr::TYPE_UINT8:
|
||||
@ -7571,12 +7574,12 @@ IonBuilder::jsop_getelem_typed(MDefinition *obj, MDefinition *index,
|
||||
case ScalarTypeDescr::TYPE_INT32:
|
||||
case ScalarTypeDescr::TYPE_UINT32:
|
||||
if (types->hasType(types::Type::Int32Type()))
|
||||
needsBarrier = false;
|
||||
barrier = BarrierKind::NoBarrier;
|
||||
break;
|
||||
case ScalarTypeDescr::TYPE_FLOAT32:
|
||||
case ScalarTypeDescr::TYPE_FLOAT64:
|
||||
if (allowDouble)
|
||||
needsBarrier = false;
|
||||
barrier = BarrierKind::NoBarrier;
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSUME_UNREACHABLE("Unknown typed array type");
|
||||
@ -7590,7 +7593,7 @@ IonBuilder::jsop_getelem_typed(MDefinition *obj, MDefinition *index,
|
||||
current->add(load);
|
||||
current->push(load);
|
||||
|
||||
return pushTypeBarrier(load, types, needsBarrier);
|
||||
return pushTypeBarrier(load, types, barrier);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8494,7 +8497,7 @@ IonBuilder::invalidatedIdempotentCache()
|
||||
|
||||
bool
|
||||
IonBuilder::loadSlot(MDefinition *obj, size_t slot, size_t nfixed, MIRType rvalType,
|
||||
bool barrier, types::TemporaryTypeSet *types)
|
||||
BarrierKind barrier, types::TemporaryTypeSet *types)
|
||||
{
|
||||
if (slot < nfixed) {
|
||||
MLoadFixedSlot *load = MLoadFixedSlot::New(alloc(), obj, slot);
|
||||
@ -8518,7 +8521,7 @@ IonBuilder::loadSlot(MDefinition *obj, size_t slot, size_t nfixed, MIRType rvalT
|
||||
|
||||
bool
|
||||
IonBuilder::loadSlot(MDefinition *obj, Shape *shape, MIRType rvalType,
|
||||
bool barrier, types::TemporaryTypeSet *types)
|
||||
BarrierKind barrier, types::TemporaryTypeSet *types)
|
||||
{
|
||||
return loadSlot(obj, shape->slot(), shape->numFixedSlots(), rvalType, barrier, types);
|
||||
}
|
||||
@ -8568,8 +8571,8 @@ IonBuilder::jsop_getprop(PropertyName *name)
|
||||
return emitted;
|
||||
|
||||
types::TemporaryTypeSet *types = bytecodeTypes(pc);
|
||||
bool barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(),
|
||||
current->peek(-1), name, types);
|
||||
BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(),
|
||||
current->peek(-1), name, types);
|
||||
|
||||
// Always use a call if we are performing analysis and
|
||||
// not actually emitting code, to simplify later analysis. Also skip deeper
|
||||
@ -8591,7 +8594,7 @@ IonBuilder::jsop_getprop(PropertyName *name)
|
||||
|
||||
current->pop();
|
||||
current->push(call);
|
||||
return resumeAfter(call) && pushTypeBarrier(call, types, true);
|
||||
return resumeAfter(call) && pushTypeBarrier(call, types, BarrierKind::TypeSet);
|
||||
}
|
||||
|
||||
// Try to hardcode known constants.
|
||||
@ -8626,7 +8629,7 @@ IonBuilder::jsop_getprop(PropertyName *name)
|
||||
if (!resumeAfter(call))
|
||||
return false;
|
||||
|
||||
return pushTypeBarrier(call, types, true);
|
||||
return pushTypeBarrier(call, types, BarrierKind::TypeSet);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -8765,7 +8768,7 @@ IonBuilder::getPropTryComplexPropOfTypedObject(bool *emitted,
|
||||
|
||||
bool
|
||||
IonBuilder::getPropTryDefiniteSlot(bool *emitted, PropertyName *name,
|
||||
bool barrier, types::TemporaryTypeSet *types)
|
||||
BarrierKind barrier, types::TemporaryTypeSet *types)
|
||||
{
|
||||
JS_ASSERT(*emitted == false);
|
||||
types::HeapTypeSetKey property;
|
||||
@ -8781,7 +8784,7 @@ IonBuilder::getPropTryDefiniteSlot(bool *emitted, PropertyName *name,
|
||||
}
|
||||
|
||||
MLoadFixedSlot *fixed = MLoadFixedSlot::New(alloc(), useObj, property.maybeTypes()->definiteSlot());
|
||||
if (!barrier)
|
||||
if (barrier == BarrierKind::NoBarrier)
|
||||
fixed->setResultType(types->getKnownMIRType());
|
||||
|
||||
current->add(fixed);
|
||||
@ -8903,7 +8906,7 @@ CanInlinePropertyOpShapes(const BaselineInspector::ShapeVector &shapes)
|
||||
|
||||
bool
|
||||
IonBuilder::getPropTryInlineAccess(bool *emitted, PropertyName *name,
|
||||
bool barrier, types::TemporaryTypeSet *types)
|
||||
BarrierKind barrier, types::TemporaryTypeSet *types)
|
||||
{
|
||||
JS_ASSERT(*emitted == false);
|
||||
if (current->peek(-1)->type() != MIRType_Object)
|
||||
@ -8917,7 +8920,7 @@ IonBuilder::getPropTryInlineAccess(bool *emitted, PropertyName *name,
|
||||
return true;
|
||||
|
||||
MIRType rvalType = types->getKnownMIRType();
|
||||
if (barrier || IsNullOrUndefined(rvalType))
|
||||
if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType))
|
||||
rvalType = MIRType_Value;
|
||||
|
||||
MDefinition *obj = current->pop();
|
||||
@ -8964,7 +8967,7 @@ IonBuilder::getPropTryInlineAccess(bool *emitted, PropertyName *name,
|
||||
|
||||
bool
|
||||
IonBuilder::getPropTryCache(bool *emitted, PropertyName *name,
|
||||
bool barrier, types::TemporaryTypeSet *types)
|
||||
BarrierKind barrier, types::TemporaryTypeSet *types)
|
||||
{
|
||||
JS_ASSERT(*emitted == false);
|
||||
|
||||
@ -8981,18 +8984,19 @@ IonBuilder::getPropTryCache(bool *emitted, PropertyName *name,
|
||||
// Since getters have no guaranteed return values, we must barrier in order to be
|
||||
// able to attach stubs for them.
|
||||
if (inspector->hasSeenAccessedGetter(pc))
|
||||
barrier = true;
|
||||
barrier = BarrierKind::TypeSet;
|
||||
|
||||
if (needsToMonitorMissingProperties(types))
|
||||
barrier = true;
|
||||
barrier = BarrierKind::TypeSet;
|
||||
|
||||
// Caches can read values from prototypes, so update the barrier to
|
||||
// reflect such possible values.
|
||||
if (!barrier)
|
||||
if (barrier == BarrierKind::NoBarrier)
|
||||
barrier = PropertyReadOnPrototypeNeedsTypeBarrier(constraints(), obj, name, types);
|
||||
|
||||
current->pop();
|
||||
MGetPropertyCache *load = MGetPropertyCache::New(alloc(), obj, name, barrier);
|
||||
MGetPropertyCache *load = MGetPropertyCache::New(alloc(), obj, name,
|
||||
barrier != BarrierKind::NoBarrier);
|
||||
|
||||
// Try to mark the cache as idempotent.
|
||||
//
|
||||
@ -9019,7 +9023,7 @@ IonBuilder::getPropTryCache(bool *emitted, PropertyName *name,
|
||||
return false;
|
||||
|
||||
MIRType rvalType = types->getKnownMIRType();
|
||||
if (barrier || IsNullOrUndefined(rvalType))
|
||||
if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType))
|
||||
rvalType = MIRType_Value;
|
||||
load->setResultType(rvalType);
|
||||
|
||||
@ -9863,7 +9867,7 @@ IonBuilder::jsop_getaliasedvar(ScopeCoordinate sc)
|
||||
current->push(load);
|
||||
|
||||
types::TemporaryTypeSet *types = bytecodeTypes(pc);
|
||||
return pushTypeBarrier(load, types, true);
|
||||
return pushTypeBarrier(load, types, BarrierKind::TypeSet);
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -343,7 +343,7 @@ class IonBuilder : public MIRGenerator
|
||||
|
||||
// Add a guard which ensure that the set of type which goes through this
|
||||
// generated code correspond to the observed types for the bytecode.
|
||||
bool pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, bool needBarrier);
|
||||
bool pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, BarrierKind kind);
|
||||
|
||||
// As pushTypeBarrier, but will compute the needBarrier boolean itself based
|
||||
// on observed and the JSFunction that we're planning to call. The
|
||||
@ -380,9 +380,9 @@ class IonBuilder : public MIRGenerator
|
||||
|
||||
bool hasStaticScopeObject(ScopeCoordinate sc, JSObject **pcall);
|
||||
bool loadSlot(MDefinition *obj, size_t slot, size_t nfixed, MIRType rvalType,
|
||||
bool barrier, types::TemporaryTypeSet *types);
|
||||
BarrierKind barrier, types::TemporaryTypeSet *types);
|
||||
bool loadSlot(MDefinition *obj, Shape *shape, MIRType rvalType,
|
||||
bool barrier, types::TemporaryTypeSet *types);
|
||||
BarrierKind barrier, types::TemporaryTypeSet *types);
|
||||
bool storeSlot(MDefinition *obj, size_t slot, size_t nfixed,
|
||||
MDefinition *value, bool needsBarrier,
|
||||
MIRType slotType = MIRType_None);
|
||||
@ -394,11 +394,11 @@ class IonBuilder : public MIRGenerator
|
||||
bool getPropTryConstant(bool *emitted, PropertyName *name,
|
||||
types::TemporaryTypeSet *types);
|
||||
bool getPropTryDefiniteSlot(bool *emitted, PropertyName *name,
|
||||
bool barrier, types::TemporaryTypeSet *types);
|
||||
BarrierKind barrier, types::TemporaryTypeSet *types);
|
||||
bool getPropTryCommonGetter(bool *emitted, PropertyName *name,
|
||||
types::TemporaryTypeSet *types);
|
||||
bool getPropTryInlineAccess(bool *emitted, PropertyName *name,
|
||||
bool barrier, types::TemporaryTypeSet *types);
|
||||
BarrierKind barrier, types::TemporaryTypeSet *types);
|
||||
bool getPropTryTypedObject(bool *emitted, PropertyName *name,
|
||||
types::TemporaryTypeSet *resultTypes);
|
||||
bool getPropTryScalarPropOfTypedObject(bool *emitted,
|
||||
@ -411,7 +411,7 @@ class IonBuilder : public MIRGenerator
|
||||
size_t fieldIndex,
|
||||
types::TemporaryTypeSet *resultTypes);
|
||||
bool getPropTryCache(bool *emitted, PropertyName *name,
|
||||
bool barrier, types::TemporaryTypeSet *types);
|
||||
BarrierKind barrier, types::TemporaryTypeSet *types);
|
||||
bool needsToMonitorMissingProperties(types::TemporaryTypeSet *types);
|
||||
|
||||
// jsop_setprop() helpers.
|
||||
|
@ -1969,7 +1969,7 @@ GenerateSetSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &att
|
||||
Register scratchReg = object;
|
||||
masm.push(scratchReg);
|
||||
|
||||
masm.guardTypeSet(valReg, propTypes, scratchReg, &barrierFailure);
|
||||
masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratchReg, &barrierFailure);
|
||||
masm.pop(object);
|
||||
}
|
||||
}
|
||||
@ -2521,7 +2521,7 @@ GenerateAddSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &att
|
||||
JS_ASSERT(!propTypes->unknown());
|
||||
|
||||
Register scratchReg = object;
|
||||
masm.guardTypeSet(valReg, propTypes, scratchReg, &failuresPopObject);
|
||||
masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratchReg, &failuresPopObject);
|
||||
masm.loadPtr(Address(StackPointer, 0), object);
|
||||
}
|
||||
|
||||
|
@ -631,9 +631,8 @@ HandleException(ResumeFromException *rfe)
|
||||
if (invalidated)
|
||||
popSPSFrame = ionScript->hasSPSInstrumentation();
|
||||
|
||||
// If inline-frames are not profiled, then don't pop an SPS frame
|
||||
// for them.
|
||||
if (frames.more() && !js_JitOptions.profileInlineFrames)
|
||||
// Don't pop an SPS frame for inlined frames, since they are not instrumented.
|
||||
if (frames.more())
|
||||
popSPSFrame = false;
|
||||
|
||||
// When profiling, each frame popped needs a notification that
|
||||
@ -1339,7 +1338,8 @@ SnapshotIterator::SnapshotIterator(IonScript *ionScript, SnapshotOffset snapshot
|
||||
ionScript->recoversSize()),
|
||||
fp_(fp),
|
||||
machine_(machine),
|
||||
ionScript_(ionScript)
|
||||
ionScript_(ionScript),
|
||||
instructionResults_(nullptr)
|
||||
{
|
||||
JS_ASSERT(snapshotOffset < ionScript->snapshotsListSize());
|
||||
}
|
||||
@ -1354,7 +1354,8 @@ SnapshotIterator::SnapshotIterator(const JitFrameIterator &iter)
|
||||
iter.ionScript()->recoversSize()),
|
||||
fp_(iter.jsFrame()),
|
||||
machine_(iter.machineState()),
|
||||
ionScript_(iter.ionScript())
|
||||
ionScript_(iter.ionScript()),
|
||||
instructionResults_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@ -1362,7 +1363,8 @@ SnapshotIterator::SnapshotIterator()
|
||||
: snapshot_(nullptr, 0, 0, 0),
|
||||
recover_(snapshot_, nullptr, 0),
|
||||
fp_(nullptr),
|
||||
ionScript_(nullptr)
|
||||
ionScript_(nullptr),
|
||||
instructionResults_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@ -1427,6 +1429,9 @@ SnapshotIterator::allocationReadable(const RValueAllocation &alloc)
|
||||
return hasStack(alloc.stackOffset());
|
||||
#endif
|
||||
|
||||
case RValueAllocation::RECOVER_INSTRUCTION:
|
||||
return hasInstructionResult(alloc.index());
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
@ -1532,6 +1537,9 @@ SnapshotIterator::allocationValue(const RValueAllocation &alloc)
|
||||
}
|
||||
#endif
|
||||
|
||||
case RValueAllocation::RECOVER_INSTRUCTION:
|
||||
return fromInstructionResult(alloc.index());
|
||||
|
||||
default:
|
||||
MOZ_ASSUME_UNREACHABLE("huh?");
|
||||
}
|
||||
@ -1546,7 +1554,7 @@ SnapshotIterator::resumePoint() const
|
||||
uint32_t
|
||||
SnapshotIterator::numAllocations() const
|
||||
{
|
||||
return resumePoint()->numOperands();
|
||||
return instruction()->numOperands();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
@ -1565,6 +1573,43 @@ SnapshotIterator::skipInstruction()
|
||||
nextInstruction();
|
||||
}
|
||||
|
||||
bool
|
||||
SnapshotIterator::initIntructionResults(AutoValueVector &results)
|
||||
{
|
||||
MOZ_ASSERT(recover_.numInstructionsRead() == 1);
|
||||
|
||||
// The last instruction will always be a resume point, no need to allocate
|
||||
// space for it.
|
||||
if (recover_.numInstructions() == 1)
|
||||
return true;
|
||||
|
||||
MOZ_ASSERT(recover_.numInstructions() > 1);
|
||||
size_t numResults = recover_.numInstructions() - 1;
|
||||
if (!results.reserve(numResults))
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < numResults; i++)
|
||||
results.infallibleAppend(MagicValue(JS_ION_BAILOUT));
|
||||
|
||||
instructionResults_ = &results;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
SnapshotIterator::storeInstructionResult(Value v)
|
||||
{
|
||||
uint32_t currIns = recover_.numInstructionsRead() - 1;
|
||||
MOZ_ASSERT((*instructionResults_)[currIns].isMagic(JS_ION_BAILOUT));
|
||||
(*instructionResults_)[currIns].set(v);
|
||||
}
|
||||
|
||||
Value
|
||||
SnapshotIterator::fromInstructionResult(uint32_t index) const
|
||||
{
|
||||
MOZ_ASSERT(!(*instructionResults_)[index].isMagic(JS_ION_BAILOUT));
|
||||
return (*instructionResults_)[index];
|
||||
}
|
||||
|
||||
void
|
||||
SnapshotIterator::nextFrame()
|
||||
{
|
||||
|
@ -69,9 +69,10 @@ class TypeWrapper {
|
||||
} /* anonymous namespace */
|
||||
|
||||
template <typename Source, typename TypeSet> void
|
||||
MacroAssembler::guardTypeSet(const Source &address, const TypeSet *types,
|
||||
MacroAssembler::guardTypeSet(const Source &address, const TypeSet *types, BarrierKind kind,
|
||||
Register scratch, Label *miss)
|
||||
{
|
||||
JS_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
|
||||
JS_ASSERT(!types->unknown());
|
||||
|
||||
Label matched;
|
||||
@ -126,8 +127,10 @@ MacroAssembler::guardTypeSet(const Source &address, const TypeSet *types,
|
||||
// Test specific objects.
|
||||
JS_ASSERT(scratch != InvalidReg);
|
||||
branchTestObject(NotEqual, tag, miss);
|
||||
Register obj = extractObject(address, scratch);
|
||||
guardObjectType(obj, types, scratch, miss);
|
||||
if (kind != BarrierKind::TypeTagOnly) {
|
||||
Register obj = extractObject(address, scratch);
|
||||
guardObjectType(obj, types, scratch, miss);
|
||||
}
|
||||
|
||||
bind(&matched);
|
||||
}
|
||||
@ -203,30 +206,30 @@ MacroAssembler::guardType(const Source &address, types::Type type,
|
||||
Register scratch, Label *miss)
|
||||
{
|
||||
TypeWrapper wrapper(type);
|
||||
guardTypeSet(address, &wrapper, scratch, miss);
|
||||
guardTypeSet(address, &wrapper, BarrierKind::TypeSet, scratch, miss);
|
||||
}
|
||||
|
||||
template void MacroAssembler::guardTypeSet(const Address &address, const types::TemporaryTypeSet *types,
|
||||
Register scratch, Label *miss);
|
||||
BarrierKind kind, Register scratch, Label *miss);
|
||||
template void MacroAssembler::guardTypeSet(const ValueOperand &value, const types::TemporaryTypeSet *types,
|
||||
Register scratch, Label *miss);
|
||||
BarrierKind kind, Register scratch, Label *miss);
|
||||
|
||||
template void MacroAssembler::guardTypeSet(const Address &address, const types::HeapTypeSet *types,
|
||||
Register scratch, Label *miss);
|
||||
BarrierKind kind, Register scratch, Label *miss);
|
||||
template void MacroAssembler::guardTypeSet(const ValueOperand &value, const types::HeapTypeSet *types,
|
||||
Register scratch, Label *miss);
|
||||
BarrierKind kind, Register scratch, Label *miss);
|
||||
template void MacroAssembler::guardTypeSet(const TypedOrValueRegister ®, const types::HeapTypeSet *types,
|
||||
Register scratch, Label *miss);
|
||||
BarrierKind kind, Register scratch, Label *miss);
|
||||
|
||||
template void MacroAssembler::guardTypeSet(const Address &address, const types::TypeSet *types,
|
||||
Register scratch, Label *miss);
|
||||
BarrierKind kind, Register scratch, Label *miss);
|
||||
template void MacroAssembler::guardTypeSet(const ValueOperand &value, const types::TypeSet *types,
|
||||
Register scratch, Label *miss);
|
||||
BarrierKind kind, Register scratch, Label *miss);
|
||||
|
||||
template void MacroAssembler::guardTypeSet(const Address &address, const TypeWrapper *types,
|
||||
Register scratch, Label *miss);
|
||||
BarrierKind kind, Register scratch, Label *miss);
|
||||
template void MacroAssembler::guardTypeSet(const ValueOperand &value, const TypeWrapper *types,
|
||||
Register scratch, Label *miss);
|
||||
BarrierKind kind, Register scratch, Label *miss);
|
||||
|
||||
template void MacroAssembler::guardObjectType(Register obj, const types::TemporaryTypeSet *types,
|
||||
Register scratch, Label *miss);
|
||||
|
@ -300,7 +300,7 @@ class MacroAssembler : public MacroAssemblerSpecific
|
||||
// Emits a test of a value against all types in a TypeSet. A scratch
|
||||
// register is required.
|
||||
template <typename Source, typename TypeSet>
|
||||
void guardTypeSet(const Source &address, const TypeSet *types, Register scratch, Label *miss);
|
||||
void guardTypeSet(const Source &address, const TypeSet *types, BarrierKind kind, Register scratch, Label *miss);
|
||||
template <typename TypeSet>
|
||||
void guardObjectType(Register obj, const TypeSet *types, Register scratch, Label *miss);
|
||||
template <typename Source>
|
||||
|
@ -7,6 +7,8 @@
|
||||
#ifndef jit_IonTypes_h
|
||||
#define jit_IonTypes_h
|
||||
|
||||
#include "mozilla/TypedEnum.h"
|
||||
|
||||
#include "jstypes.h"
|
||||
|
||||
#include "js/Value.h"
|
||||
@ -324,6 +326,19 @@ enum ABIFunctionType
|
||||
(ArgType_General << (ArgType_Shift * 2))
|
||||
};
|
||||
|
||||
MOZ_BEGIN_ENUM_CLASS(BarrierKind, uint32_t)
|
||||
// No barrier is needed.
|
||||
NoBarrier,
|
||||
|
||||
// The barrier only has to check the value's type tag is in the TypeSet.
|
||||
// Specific object types don't have to be checked.
|
||||
TypeTagOnly,
|
||||
|
||||
// Check if the value is in the TypeSet, including the object type if it's
|
||||
// an object.
|
||||
TypeSet
|
||||
MOZ_END_ENUM_CLASS(BarrierKind)
|
||||
|
||||
} // namespace jit
|
||||
} // namespace js
|
||||
|
||||
|
@ -258,6 +258,7 @@ class SnapshotIterator
|
||||
IonJSFrameLayout *fp_;
|
||||
MachineState machine_;
|
||||
IonScript *ionScript_;
|
||||
AutoValueVector *instructionResults_;
|
||||
|
||||
private:
|
||||
// Read a spilled register from the machine state.
|
||||
@ -281,6 +282,11 @@ class SnapshotIterator
|
||||
}
|
||||
uintptr_t fromStack(int32_t offset) const;
|
||||
|
||||
bool hasInstructionResult(uint32_t index) const {
|
||||
return instructionResults_;
|
||||
}
|
||||
Value fromInstructionResult(uint32_t index) const;
|
||||
|
||||
Value allocationValue(const RValueAllocation &a);
|
||||
bool allocationReadable(const RValueAllocation &a);
|
||||
void warnUnreadableAllocation();
|
||||
@ -338,6 +344,14 @@ class SnapshotIterator
|
||||
return recover_.moreInstructions();
|
||||
}
|
||||
|
||||
// Register a vector used for storing the results of the evaluation of
|
||||
// recover instructions. This vector should be registered before the
|
||||
// beginning of the iteration. This function is in charge of allocating
|
||||
// enough space for all instructions results, and return false iff it fails.
|
||||
bool initIntructionResults(AutoValueVector &results);
|
||||
|
||||
void storeInstructionResult(Value v);
|
||||
|
||||
public:
|
||||
// Handle iterating over frames of the snapshots.
|
||||
void nextFrame();
|
||||
|
@ -110,9 +110,6 @@ JitOptions::JitOptions()
|
||||
|
||||
// How many uses of a parallel kernel before we attempt compilation.
|
||||
usesBeforeCompilePar = 1;
|
||||
|
||||
// Whether to profile inlined functions in Ion or not.
|
||||
profileInlineFrames = false;
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -69,7 +69,6 @@ struct JitOptions
|
||||
uint32_t osrPcMismatchesBeforeRecompile;
|
||||
uint32_t smallFunctionMaxBytecodeLength_;
|
||||
uint32_t usesBeforeCompilePar;
|
||||
bool profileInlineFrames;
|
||||
|
||||
JitOptions();
|
||||
bool isSmallFunction(JSScript *script) const;
|
||||
|
@ -111,11 +111,16 @@ LBlock::getExitMoveGroup(TempAllocator &alloc)
|
||||
}
|
||||
|
||||
static size_t
|
||||
TotalOperandCount(MResumePoint *mir)
|
||||
TotalOperandCount(LRecoverInfo *recoverInfo)
|
||||
{
|
||||
size_t accum = mir->numOperands();
|
||||
while ((mir = mir->caller()))
|
||||
accum += mir->numOperands();
|
||||
LRecoverInfo::OperandIter it(recoverInfo->begin());
|
||||
LRecoverInfo::OperandIter end(recoverInfo->end());
|
||||
size_t accum = 0;
|
||||
|
||||
for (; it != end; ++it) {
|
||||
if (!it->isRecoveredOnBailout())
|
||||
accum++;
|
||||
}
|
||||
return accum;
|
||||
}
|
||||
|
||||
@ -137,28 +142,71 @@ LRecoverInfo::New(MIRGenerator *gen, MResumePoint *mir)
|
||||
return recoverInfo;
|
||||
}
|
||||
|
||||
bool
|
||||
LRecoverInfo::appendOperands(MNode *ins)
|
||||
{
|
||||
for (size_t i = 0, end = ins->numOperands(); i < end; i++) {
|
||||
MDefinition *def = ins->getOperand(i);
|
||||
|
||||
// As there is no cycle in the data-flow (without MPhi), checking for
|
||||
// isInWorkList implies that the definition is already in the
|
||||
// instruction vector, and not processed by a caller of the current
|
||||
// function.
|
||||
if (def->isRecoveredOnBailout() && !def->isInWorklist()) {
|
||||
if (!appendDefinition(def))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
LRecoverInfo::appendDefinition(MDefinition *def)
|
||||
{
|
||||
MOZ_ASSERT(def->isRecoveredOnBailout());
|
||||
def->setInWorklist();
|
||||
if (!appendOperands(def))
|
||||
return false;
|
||||
return instructions_.append(def);
|
||||
}
|
||||
|
||||
bool
|
||||
LRecoverInfo::appendResumePoint(MResumePoint *rp)
|
||||
{
|
||||
if (rp->caller() && !appendResumePoint(rp->caller()))
|
||||
return false;
|
||||
|
||||
if (!appendOperands(rp))
|
||||
return false;
|
||||
|
||||
return instructions_.append(rp);
|
||||
}
|
||||
|
||||
bool
|
||||
LRecoverInfo::init(MResumePoint *rp)
|
||||
{
|
||||
MResumePoint *it = rp;
|
||||
|
||||
// Sort operations in the order in which we need to restore the stack. This
|
||||
// implies that outer frames, as well as operations needed to recover the
|
||||
// current frame, are located before the current frame. The inner-most
|
||||
// resume point should be the last element in the list.
|
||||
do {
|
||||
if (!instructions_.append(it))
|
||||
return false;
|
||||
it = it->caller();
|
||||
} while (it);
|
||||
if (!appendResumePoint(rp))
|
||||
return false;
|
||||
|
||||
// Remove temporary flags from all definitions.
|
||||
for (MNode **it = begin(); it != end(); it++) {
|
||||
if (!(*it)->isDefinition())
|
||||
continue;
|
||||
|
||||
(*it)->toDefinition()->setNotInWorklist();
|
||||
}
|
||||
|
||||
Reverse(instructions_.begin(), instructions_.end());
|
||||
MOZ_ASSERT(mir() == rp);
|
||||
return true;
|
||||
}
|
||||
|
||||
LSnapshot::LSnapshot(LRecoverInfo *recoverInfo, BailoutKind kind)
|
||||
: numSlots_(TotalOperandCount(recoverInfo->mir()) * BOX_PIECES),
|
||||
: numSlots_(TotalOperandCount(recoverInfo) * BOX_PIECES),
|
||||
slots_(nullptr),
|
||||
recoverInfo_(recoverInfo),
|
||||
snapshotOffset_(INVALID_SNAPSHOT_OFFSET),
|
||||
@ -368,8 +416,6 @@ LInstruction::dump(FILE *fp)
|
||||
fprintf(fp, "} <- ");
|
||||
|
||||
printName(fp);
|
||||
|
||||
|
||||
printInfo(fp);
|
||||
|
||||
if (numTemps()) {
|
||||
@ -381,13 +427,13 @@ LInstruction::dump(FILE *fp)
|
||||
}
|
||||
fprintf(fp, ")");
|
||||
}
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
|
||||
void
|
||||
LInstruction::dump()
|
||||
{
|
||||
return dump(stderr);
|
||||
dump(stderr);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -879,7 +879,7 @@ class LCallInstructionHelper : public LInstructionHelper<Defs, Operands, Temps>
|
||||
class LRecoverInfo : public TempObject
|
||||
{
|
||||
public:
|
||||
typedef Vector<MResumePoint *, 2, IonAllocPolicy> Instructions;
|
||||
typedef Vector<MNode *, 2, IonAllocPolicy> Instructions;
|
||||
|
||||
private:
|
||||
// List of instructions needed to recover the stack frames.
|
||||
@ -892,12 +892,17 @@ class LRecoverInfo : public TempObject
|
||||
LRecoverInfo(TempAllocator &alloc);
|
||||
bool init(MResumePoint *mir);
|
||||
|
||||
// Fill the instruction vector such as all instructions needed for the
|
||||
// recovery are pushed before the current instruction.
|
||||
bool appendOperands(MNode *ins);
|
||||
bool appendDefinition(MDefinition *def);
|
||||
bool appendResumePoint(MResumePoint *rp);
|
||||
public:
|
||||
static LRecoverInfo *New(MIRGenerator *gen, MResumePoint *mir);
|
||||
|
||||
// Resume point of the inner most function.
|
||||
MResumePoint *mir() const {
|
||||
return instructions_.back();
|
||||
return instructions_.back()->toResumePoint();
|
||||
}
|
||||
RecoverOffset recoverOffset() const {
|
||||
return recoverOffset_;
|
||||
@ -907,12 +912,47 @@ class LRecoverInfo : public TempObject
|
||||
recoverOffset_ = offset;
|
||||
}
|
||||
|
||||
MResumePoint **begin() {
|
||||
MNode **begin() {
|
||||
return instructions_.begin();
|
||||
}
|
||||
MResumePoint **end() {
|
||||
MNode **end() {
|
||||
return instructions_.end();
|
||||
}
|
||||
size_t numInstructions() const {
|
||||
return instructions_.length();
|
||||
}
|
||||
|
||||
class OperandIter
|
||||
{
|
||||
private:
|
||||
MNode **it_;
|
||||
size_t op_;
|
||||
|
||||
public:
|
||||
OperandIter(MNode **it)
|
||||
: it_(it), op_(0)
|
||||
{ }
|
||||
|
||||
MDefinition *operator *() {
|
||||
return (*it_)->getOperand(op_);
|
||||
}
|
||||
MDefinition *operator ->() {
|
||||
return (*it_)->getOperand(op_);
|
||||
}
|
||||
|
||||
OperandIter &operator ++() {
|
||||
++op_;
|
||||
if (op_ == (*it_)->numOperands()) {
|
||||
op_ = 0;
|
||||
++it_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator !=(const OperandIter &where) const {
|
||||
return it_ != where.it_ || op_ != where.op_;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// An LSnapshot is the reflection of an MResumePoint in LIR. Unlike MResumePoints,
|
||||
|
@ -2303,7 +2303,8 @@ LIRGenerator::visitTypeBarrier(MTypeBarrier *ins)
|
||||
}
|
||||
|
||||
// Handle typebarrier with specific TypeObject/SingleObjects.
|
||||
if (inputType == MIRType_Object && !types->hasType(types::Type::AnyObjectType()))
|
||||
if (inputType == MIRType_Object && !types->hasType(types::Type::AnyObjectType()) &&
|
||||
ins->barrierKind() != BarrierKind::TypeTagOnly)
|
||||
{
|
||||
LDefinition tmp = needTemp ? temp() : LDefinition::BogusTemp();
|
||||
LTypeBarrierO *barrier = new(alloc()) LTypeBarrierO(useRegister(ins->getOperand(0)), tmp);
|
||||
@ -3597,6 +3598,9 @@ SpewResumePoint(MBasicBlock *block, MInstruction *ins, MResumePoint *resumePoint
|
||||
bool
|
||||
LIRGenerator::visitInstruction(MInstruction *ins)
|
||||
{
|
||||
if (ins->isRecoveredOnBailout())
|
||||
return true;
|
||||
|
||||
if (!gen->ensureBallast())
|
||||
return false;
|
||||
if (!ins->accept(this))
|
||||
|
@ -385,9 +385,9 @@ IonBuilder::inlineArrayPopShift(CallInfo &callInfo, MArrayPopShift::Mode mode)
|
||||
bool needsHoleCheck = thisTypes->hasObjectFlags(constraints(), types::OBJECT_FLAG_NON_PACKED);
|
||||
bool maybeUndefined = returnTypes->hasType(types::Type::UndefinedType());
|
||||
|
||||
bool barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(),
|
||||
callInfo.thisArg(), nullptr, returnTypes);
|
||||
if (barrier)
|
||||
BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(),
|
||||
callInfo.thisArg(), nullptr, returnTypes);
|
||||
if (barrier != BarrierKind::NoBarrier)
|
||||
returnType = MIRType_Value;
|
||||
|
||||
MArrayPopShift *ins = MArrayPopShift::New(alloc(), callInfo.thisArg(), mode,
|
||||
@ -1231,7 +1231,7 @@ IonBuilder::inlineRegExpExec(CallInfo &callInfo)
|
||||
if (!resumeAfter(exec))
|
||||
return InliningStatus_Error;
|
||||
|
||||
if (!pushTypeBarrier(exec, getInlineReturnTypeSet(), true))
|
||||
if (!pushTypeBarrier(exec, getInlineReturnTypeSet(), BarrierKind::TypeSet))
|
||||
return InliningStatus_Error;
|
||||
|
||||
return InliningStatus_Inlined;
|
||||
@ -1782,7 +1782,7 @@ IonBuilder::inlineUnsafeGetReservedSlot(CallInfo &callInfo)
|
||||
current->push(load);
|
||||
|
||||
// We don't track reserved slot types, so always emit a barrier.
|
||||
if (!pushTypeBarrier(load, getInlineReturnTypeSet(), true))
|
||||
if (!pushTypeBarrier(load, getInlineReturnTypeSet(), BarrierKind::TypeSet))
|
||||
return InliningStatus_Error;
|
||||
|
||||
return InliningStatus_Inlined;
|
||||
|
@ -359,6 +359,20 @@ MDefinition::hasDefUses() const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
MDefinition::hasLiveDefUses() const
|
||||
{
|
||||
for (MUseIterator i(uses_.begin()); i != uses_.end(); i++) {
|
||||
MNode *ins = (*i)->consumer();
|
||||
if (!ins->isDefinition())
|
||||
continue;
|
||||
if (!ins->toDefinition()->isRecoveredOnBailout())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
MUseIterator
|
||||
MDefinition::removeUse(MUseIterator use)
|
||||
{
|
||||
@ -3089,7 +3103,7 @@ jit::DenseNativeElementType(types::CompilerConstraintList *constraints, MDefinit
|
||||
return elementType;
|
||||
}
|
||||
|
||||
static bool
|
||||
static BarrierKind
|
||||
PropertyReadNeedsTypeBarrier(types::CompilerConstraintList *constraints,
|
||||
types::TypeObjectKey *object, PropertyName *name,
|
||||
types::TypeSet *observed)
|
||||
@ -3105,13 +3119,22 @@ PropertyReadNeedsTypeBarrier(types::CompilerConstraintList *constraints,
|
||||
if (object->unknownProperties() || observed->empty() ||
|
||||
object->clasp()->isProxy())
|
||||
{
|
||||
return true;
|
||||
return BarrierKind::TypeSet;
|
||||
}
|
||||
|
||||
jsid id = name ? NameToId(name) : JSID_VOID;
|
||||
types::HeapTypeSetKey property = object->property(id);
|
||||
if (property.maybeTypes() && !TypeSetIncludes(observed, MIRType_Value, property.maybeTypes()))
|
||||
return true;
|
||||
if (property.maybeTypes()) {
|
||||
if (!TypeSetIncludes(observed, MIRType_Value, property.maybeTypes())) {
|
||||
// If all possible objects have been observed, we don't have to
|
||||
// guard on the specific object types.
|
||||
if (property.maybeTypes()->objectsAreSubset(observed)) {
|
||||
property.freeze(constraints);
|
||||
return BarrierKind::TypeTagOnly;
|
||||
}
|
||||
return BarrierKind::TypeSet;
|
||||
}
|
||||
}
|
||||
|
||||
// Type information for global objects is not required to reflect the
|
||||
// initial 'undefined' value for properties, in particular global
|
||||
@ -3121,15 +3144,15 @@ PropertyReadNeedsTypeBarrier(types::CompilerConstraintList *constraints,
|
||||
if (name && types::CanHaveEmptyPropertyTypesForOwnProperty(obj) &&
|
||||
(!property.maybeTypes() || property.maybeTypes()->empty()))
|
||||
{
|
||||
return true;
|
||||
return BarrierKind::TypeSet;
|
||||
}
|
||||
}
|
||||
|
||||
property.freeze(constraints);
|
||||
return false;
|
||||
return BarrierKind::NoBarrier;
|
||||
}
|
||||
|
||||
bool
|
||||
BarrierKind
|
||||
jit::PropertyReadNeedsTypeBarrier(JSContext *propertycx,
|
||||
types::CompilerConstraintList *constraints,
|
||||
types::TypeObjectKey *object, PropertyName *name,
|
||||
@ -3177,45 +3200,55 @@ jit::PropertyReadNeedsTypeBarrier(JSContext *propertycx,
|
||||
return PropertyReadNeedsTypeBarrier(constraints, object, name, observed);
|
||||
}
|
||||
|
||||
bool
|
||||
BarrierKind
|
||||
jit::PropertyReadNeedsTypeBarrier(JSContext *propertycx,
|
||||
types::CompilerConstraintList *constraints,
|
||||
MDefinition *obj, PropertyName *name,
|
||||
types::TemporaryTypeSet *observed)
|
||||
{
|
||||
if (observed->unknown())
|
||||
return false;
|
||||
return BarrierKind::NoBarrier;
|
||||
|
||||
types::TypeSet *types = obj->resultTypeSet();
|
||||
if (!types || types->unknownObject())
|
||||
return true;
|
||||
return BarrierKind::TypeSet;
|
||||
|
||||
BarrierKind res = BarrierKind::NoBarrier;
|
||||
|
||||
bool updateObserved = types->getObjectCount() == 1;
|
||||
for (size_t i = 0; i < types->getObjectCount(); i++) {
|
||||
types::TypeObjectKey *object = types->getObject(i);
|
||||
if (object) {
|
||||
if (PropertyReadNeedsTypeBarrier(propertycx, constraints, object, name,
|
||||
observed, updateObserved))
|
||||
{
|
||||
return true;
|
||||
BarrierKind kind = PropertyReadNeedsTypeBarrier(propertycx, constraints, object, name,
|
||||
observed, updateObserved);
|
||||
if (kind == BarrierKind::TypeSet)
|
||||
return BarrierKind::TypeSet;
|
||||
|
||||
if (kind == BarrierKind::TypeTagOnly) {
|
||||
MOZ_ASSERT(res == BarrierKind::NoBarrier || res == BarrierKind::TypeTagOnly);
|
||||
res = BarrierKind::TypeTagOnly;
|
||||
} else {
|
||||
MOZ_ASSERT(kind == BarrierKind::NoBarrier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
BarrierKind
|
||||
jit::PropertyReadOnPrototypeNeedsTypeBarrier(types::CompilerConstraintList *constraints,
|
||||
MDefinition *obj, PropertyName *name,
|
||||
types::TemporaryTypeSet *observed)
|
||||
{
|
||||
if (observed->unknown())
|
||||
return false;
|
||||
return BarrierKind::NoBarrier;
|
||||
|
||||
types::TypeSet *types = obj->resultTypeSet();
|
||||
if (!types || types->unknownObject())
|
||||
return true;
|
||||
return BarrierKind::TypeSet;
|
||||
|
||||
BarrierKind res = BarrierKind::NoBarrier;
|
||||
|
||||
for (size_t i = 0; i < types->getObjectCount(); i++) {
|
||||
types::TypeObjectKey *object = types->getObject(i);
|
||||
@ -3223,16 +3256,24 @@ jit::PropertyReadOnPrototypeNeedsTypeBarrier(types::CompilerConstraintList *cons
|
||||
continue;
|
||||
while (true) {
|
||||
if (!object->hasTenuredProto())
|
||||
return true;
|
||||
return BarrierKind::TypeSet;
|
||||
if (!object->proto().isObject())
|
||||
break;
|
||||
object = types::TypeObjectKey::get(object->proto().toObject());
|
||||
if (PropertyReadNeedsTypeBarrier(constraints, object, name, observed))
|
||||
return true;
|
||||
BarrierKind kind = PropertyReadNeedsTypeBarrier(constraints, object, name, observed);
|
||||
if (kind == BarrierKind::TypeSet)
|
||||
return BarrierKind::TypeSet;
|
||||
|
||||
if (kind == BarrierKind::TypeTagOnly) {
|
||||
MOZ_ASSERT(res == BarrierKind::NoBarrier || res == BarrierKind::TypeTagOnly);
|
||||
res = BarrierKind::TypeTagOnly;
|
||||
} else {
|
||||
MOZ_ASSERT(kind == BarrierKind::NoBarrier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
@ -3382,7 +3423,13 @@ TryAddTypeBarrierForWrite(TempAllocator &alloc, types::CompilerConstraintList *c
|
||||
if (!types)
|
||||
return false;
|
||||
|
||||
MInstruction *ins = MMonitorTypes::New(alloc, *pvalue, types);
|
||||
// If all possible objects can be stored without a barrier, we don't have to
|
||||
// guard on the specific object types.
|
||||
BarrierKind kind = BarrierKind::TypeSet;
|
||||
if ((*pvalue)->resultTypeSet() && (*pvalue)->resultTypeSet()->objectsAreSubset(types))
|
||||
kind = BarrierKind::TypeTagOnly;
|
||||
|
||||
MInstruction *ins = MMonitorTypes::New(alloc, *pvalue, types, kind);
|
||||
current->add(ins);
|
||||
return true;
|
||||
}
|
||||
|
@ -89,7 +89,14 @@ MIRType MIRTypeFromValue(const js::Value &vp)
|
||||
* Truncate Doubles. So every time removeUse is called, UseRemoved needs
|
||||
* to get set.
|
||||
*/ \
|
||||
_(UseRemoved)
|
||||
_(UseRemoved) \
|
||||
\
|
||||
/* Marks if the current instruction should go to the bailout paths instead
|
||||
* of producing code as part of the control flow. This flag can only be set
|
||||
* on instructions which are only used by ResumePoint or by other flagged
|
||||
* instructions.
|
||||
*/ \
|
||||
_(RecoveredOnBailout)
|
||||
|
||||
class MDefinition;
|
||||
class MInstruction;
|
||||
@ -208,6 +215,8 @@ class MNode : public TempObject
|
||||
inline MDefinition *toDefinition();
|
||||
inline MResumePoint *toResumePoint();
|
||||
|
||||
virtual bool writeRecoverData(CompactBufferWriter &writer) const;
|
||||
|
||||
protected:
|
||||
// Sets an unset operand, updating use information.
|
||||
virtual void setOperand(size_t index, MDefinition *operand) = 0;
|
||||
@ -531,6 +540,10 @@ class MDefinition : public MNode
|
||||
// (only counting MDefinitions, ignoring MResumePoints)
|
||||
bool hasDefUses() const;
|
||||
|
||||
// Test whether this MDefinition has at least one non-recovered use.
|
||||
// (only counting MDefinitions, ignoring MResumePoints)
|
||||
bool hasLiveDefUses() const;
|
||||
|
||||
bool hasUses() const {
|
||||
return !uses_.empty();
|
||||
}
|
||||
@ -606,6 +619,10 @@ class MDefinition : public MNode
|
||||
JS_ASSERT(getAliasSet().flags() & store->getAliasSet().flags());
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool canRecoverOnBailout() const {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// An MUseDefIterator walks over uses in a definition, skipping any use that is
|
||||
@ -4000,6 +4017,11 @@ class MAdd : public MBinaryArithInstruction
|
||||
void computeRange(TempAllocator &alloc);
|
||||
bool truncate();
|
||||
bool isOperandTruncated(size_t index) const;
|
||||
|
||||
bool writeRecoverData(CompactBufferWriter &writer) const;
|
||||
bool canRecoverOnBailout() const {
|
||||
return specialization_ < MIRType_Object;
|
||||
}
|
||||
};
|
||||
|
||||
class MSub : public MBinaryArithInstruction
|
||||
@ -9070,10 +9092,15 @@ class MTypeBarrier
|
||||
: public MUnaryInstruction,
|
||||
public TypeBarrierPolicy
|
||||
{
|
||||
MTypeBarrier(MDefinition *def, types::TemporaryTypeSet *types)
|
||||
: MUnaryInstruction(def)
|
||||
BarrierKind barrierKind_;
|
||||
|
||||
MTypeBarrier(MDefinition *def, types::TemporaryTypeSet *types, BarrierKind kind)
|
||||
: MUnaryInstruction(def),
|
||||
barrierKind_(kind)
|
||||
{
|
||||
JS_ASSERT(!types->unknown());
|
||||
MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
|
||||
|
||||
MOZ_ASSERT(!types->unknown());
|
||||
setResultType(types->getKnownMIRType());
|
||||
setResultTypeSet(types);
|
||||
|
||||
@ -9084,8 +9111,9 @@ class MTypeBarrier
|
||||
public:
|
||||
INSTRUCTION_HEADER(TypeBarrier)
|
||||
|
||||
static MTypeBarrier *New(TempAllocator &alloc, MDefinition *def, types::TemporaryTypeSet *types) {
|
||||
return new(alloc) MTypeBarrier(def, types);
|
||||
static MTypeBarrier *New(TempAllocator &alloc, MDefinition *def, types::TemporaryTypeSet *types,
|
||||
BarrierKind kind = BarrierKind::TypeSet) {
|
||||
return new(alloc) MTypeBarrier(def, types, kind);
|
||||
}
|
||||
|
||||
void printOpcode(FILE *fp) const;
|
||||
@ -9103,6 +9131,9 @@ class MTypeBarrier
|
||||
virtual bool neverHoist() const {
|
||||
return resultTypeSet()->empty();
|
||||
}
|
||||
BarrierKind barrierKind() const {
|
||||
return barrierKind_;
|
||||
}
|
||||
|
||||
bool alwaysBails() const {
|
||||
// If mirtype of input doesn't agree with mirtype of barrier,
|
||||
@ -9122,20 +9153,25 @@ class MTypeBarrier
|
||||
class MMonitorTypes : public MUnaryInstruction, public BoxInputsPolicy
|
||||
{
|
||||
const types::TemporaryTypeSet *typeSet_;
|
||||
BarrierKind barrierKind_;
|
||||
|
||||
MMonitorTypes(MDefinition *def, const types::TemporaryTypeSet *types)
|
||||
MMonitorTypes(MDefinition *def, const types::TemporaryTypeSet *types, BarrierKind kind)
|
||||
: MUnaryInstruction(def),
|
||||
typeSet_(types)
|
||||
typeSet_(types),
|
||||
barrierKind_(kind)
|
||||
{
|
||||
MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
|
||||
|
||||
setGuard();
|
||||
JS_ASSERT(!types->unknown());
|
||||
MOZ_ASSERT(!types->unknown());
|
||||
}
|
||||
|
||||
public:
|
||||
INSTRUCTION_HEADER(MonitorTypes)
|
||||
|
||||
static MMonitorTypes *New(TempAllocator &alloc, MDefinition *def, const types::TemporaryTypeSet *types) {
|
||||
return new(alloc) MMonitorTypes(def, types);
|
||||
static MMonitorTypes *New(TempAllocator &alloc, MDefinition *def, const types::TemporaryTypeSet *types,
|
||||
BarrierKind kind) {
|
||||
return new(alloc) MMonitorTypes(def, types, kind);
|
||||
}
|
||||
|
||||
TypePolicy *typePolicy() {
|
||||
@ -9145,6 +9181,10 @@ class MMonitorTypes : public MUnaryInstruction, public BoxInputsPolicy
|
||||
const types::TemporaryTypeSet *typeSet() const {
|
||||
return typeSet_;
|
||||
}
|
||||
BarrierKind barrierKind() const {
|
||||
return barrierKind_;
|
||||
}
|
||||
|
||||
AliasSet getAliasSet() const {
|
||||
return AliasSet::None();
|
||||
}
|
||||
@ -10094,17 +10134,17 @@ bool ElementAccessIsPacked(types::CompilerConstraintList *constraints, MDefiniti
|
||||
bool ElementAccessHasExtraIndexedProperty(types::CompilerConstraintList *constraints,
|
||||
MDefinition *obj);
|
||||
MIRType DenseNativeElementType(types::CompilerConstraintList *constraints, MDefinition *obj);
|
||||
bool PropertyReadNeedsTypeBarrier(JSContext *propertycx,
|
||||
types::CompilerConstraintList *constraints,
|
||||
types::TypeObjectKey *object, PropertyName *name,
|
||||
types::TemporaryTypeSet *observed, bool updateObserved);
|
||||
bool PropertyReadNeedsTypeBarrier(JSContext *propertycx,
|
||||
types::CompilerConstraintList *constraints,
|
||||
MDefinition *obj, PropertyName *name,
|
||||
types::TemporaryTypeSet *observed);
|
||||
bool PropertyReadOnPrototypeNeedsTypeBarrier(types::CompilerConstraintList *constraints,
|
||||
MDefinition *obj, PropertyName *name,
|
||||
types::TemporaryTypeSet *observed);
|
||||
BarrierKind PropertyReadNeedsTypeBarrier(JSContext *propertycx,
|
||||
types::CompilerConstraintList *constraints,
|
||||
types::TypeObjectKey *object, PropertyName *name,
|
||||
types::TemporaryTypeSet *observed, bool updateObserved);
|
||||
BarrierKind PropertyReadNeedsTypeBarrier(JSContext *propertycx,
|
||||
types::CompilerConstraintList *constraints,
|
||||
MDefinition *obj, PropertyName *name,
|
||||
types::TemporaryTypeSet *observed);
|
||||
BarrierKind PropertyReadOnPrototypeNeedsTypeBarrier(types::CompilerConstraintList *constraints,
|
||||
MDefinition *obj, PropertyName *name,
|
||||
types::TemporaryTypeSet *observed);
|
||||
bool PropertyReadIsIdempotent(types::CompilerConstraintList *constraints,
|
||||
MDefinition *obj, PropertyName *name);
|
||||
void AddObjectsForPropertyRead(MDefinition *obj, PropertyName *name,
|
||||
|
@ -6,21 +6,42 @@
|
||||
|
||||
#include "jit/Recover.h"
|
||||
|
||||
#include "jscntxt.h"
|
||||
#include "jsmath.h"
|
||||
|
||||
#include "jit/IonSpewer.h"
|
||||
#include "jit/JitFrameIterator.h"
|
||||
#include "jit/MIR.h"
|
||||
#include "jit/MIRGraph.h"
|
||||
|
||||
#include "vm/Interpreter.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::jit;
|
||||
|
||||
bool
|
||||
MNode::writeRecoverData(CompactBufferWriter &writer) const
|
||||
{
|
||||
MOZ_ASSUME_UNREACHABLE("This instruction is not serializable");
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
RInstruction::readRecoverData(CompactBufferReader &reader, RInstructionStorage *raw)
|
||||
{
|
||||
uint32_t op = reader.readUnsigned();
|
||||
switch (Opcode(op)) {
|
||||
case Recover_ResumePoint:
|
||||
new (raw->addr()) RResumePoint(reader);
|
||||
# define MATCH_OPCODES_(op) \
|
||||
case Recover_##op: \
|
||||
static_assert(sizeof(R##op) <= sizeof(RInstructionStorage), \
|
||||
"Storage space is too small to decode R" #op " instructions."); \
|
||||
new (raw->addr()) R##op(reader); \
|
||||
break;
|
||||
|
||||
RECOVER_OPCODE_LIST(MATCH_OPCODES_)
|
||||
# undef DEFINE_OPCODES_
|
||||
|
||||
case Recover_Invalid:
|
||||
default:
|
||||
MOZ_ASSUME_UNREACHABLE("Bad decoding of the previous instruction?");
|
||||
break;
|
||||
@ -101,10 +122,48 @@ MResumePoint::writeRecoverData(CompactBufferWriter &writer) const
|
||||
|
||||
RResumePoint::RResumePoint(CompactBufferReader &reader)
|
||||
{
|
||||
static_assert(sizeof(*this) <= sizeof(RInstructionStorage),
|
||||
"Storage space is too small to decode this recover instruction.");
|
||||
pcOffset_ = reader.readUnsigned();
|
||||
numOperands_ = reader.readUnsigned();
|
||||
IonSpew(IonSpew_Snapshots, "Read RResumePoint (pc offset %u, nslots %u)",
|
||||
pcOffset_, numOperands_);
|
||||
}
|
||||
|
||||
bool
|
||||
RResumePoint::recover(JSContext *cx, SnapshotIterator &iter) const
|
||||
{
|
||||
MOZ_ASSUME_UNREACHABLE("This instruction is not recoverable.");
|
||||
}
|
||||
|
||||
bool
|
||||
MAdd::writeRecoverData(CompactBufferWriter &writer) const
|
||||
{
|
||||
MOZ_ASSERT(canRecoverOnBailout());
|
||||
writer.writeUnsigned(uint32_t(RInstruction::Recover_Add));
|
||||
writer.writeByte(specialization_ == MIRType_Float32);
|
||||
return true;
|
||||
}
|
||||
|
||||
RAdd::RAdd(CompactBufferReader &reader)
|
||||
{
|
||||
isFloatOperation_ = reader.readByte();
|
||||
}
|
||||
|
||||
bool
|
||||
RAdd::recover(JSContext *cx, SnapshotIterator &iter) const
|
||||
{
|
||||
RootedValue lhs(cx, iter.read());
|
||||
RootedValue rhs(cx, iter.read());
|
||||
RootedValue result(cx);
|
||||
|
||||
MOZ_ASSERT(!lhs.isObject() && !rhs.isObject());
|
||||
if (!js::AddValues(cx, &lhs, &rhs, &result))
|
||||
return false;
|
||||
|
||||
// MIRType_Float32 is a specialization embedding the fact that the result is
|
||||
// rounded to a Float32.
|
||||
if (isFloatOperation_ && !RoundFloat32(cx, result, &result))
|
||||
return false;
|
||||
|
||||
iter.storeInstructionResult(result);
|
||||
return true;
|
||||
}
|
||||
|
@ -11,44 +11,71 @@
|
||||
|
||||
#include "jit/Snapshots.h"
|
||||
|
||||
class JSContext;
|
||||
|
||||
namespace js {
|
||||
namespace jit {
|
||||
|
||||
#define RECOVER_OPCODE_LIST(_) \
|
||||
_(ResumePoint) \
|
||||
_(Add)
|
||||
|
||||
class RResumePoint;
|
||||
class SnapshotIterator;
|
||||
|
||||
class RInstruction
|
||||
{
|
||||
public:
|
||||
enum Opcode
|
||||
{
|
||||
Recover_ResumePoint = 0
|
||||
# define DEFINE_OPCODES_(op) Recover_##op,
|
||||
RECOVER_OPCODE_LIST(DEFINE_OPCODES_)
|
||||
# undef DEFINE_OPCODES_
|
||||
Recover_Invalid
|
||||
};
|
||||
|
||||
virtual Opcode opcode() const = 0;
|
||||
|
||||
// As opposed to the MIR, there is no need to add more methods as every
|
||||
// other instruction is well abstracted under the "recover" method.
|
||||
bool isResumePoint() const {
|
||||
return opcode() == Recover_ResumePoint;
|
||||
}
|
||||
inline const RResumePoint *toResumePoint() const;
|
||||
|
||||
// Number of allocations which are encoded in the Snapshot for recovering
|
||||
// the current instruction.
|
||||
virtual uint32_t numOperands() const = 0;
|
||||
|
||||
// Function used to recover the value computed by this instruction. This
|
||||
// function reads its arguments from the allocations listed on the snapshot
|
||||
// iterator and stores its returned value on the snapshot iterator too.
|
||||
virtual bool recover(JSContext *cx, SnapshotIterator &iter) const = 0;
|
||||
|
||||
// Decode an RInstruction on top of the reserved storage space, based on the
|
||||
// tag written by the writeRecoverData function of the corresponding MIR
|
||||
// instruction.
|
||||
static void readRecoverData(CompactBufferReader &reader, RInstructionStorage *raw);
|
||||
};
|
||||
|
||||
#define RINSTRUCTION_HEADER_(op) \
|
||||
private: \
|
||||
friend class RInstruction; \
|
||||
R##op(CompactBufferReader &reader); \
|
||||
\
|
||||
public: \
|
||||
Opcode opcode() const { \
|
||||
return RInstruction::Recover_##op; \
|
||||
}
|
||||
|
||||
class RResumePoint MOZ_FINAL : public RInstruction
|
||||
{
|
||||
private:
|
||||
uint32_t pcOffset_; // Offset from script->code.
|
||||
uint32_t numOperands_; // Number of slots.
|
||||
|
||||
friend class RInstruction;
|
||||
RResumePoint(CompactBufferReader &reader);
|
||||
|
||||
public:
|
||||
virtual Opcode opcode() const {
|
||||
return Recover_ResumePoint;
|
||||
}
|
||||
RINSTRUCTION_HEADER_(ResumePoint)
|
||||
|
||||
uint32_t pcOffset() const {
|
||||
return pcOffset_;
|
||||
@ -56,8 +83,26 @@ class RResumePoint MOZ_FINAL : public RInstruction
|
||||
virtual uint32_t numOperands() const {
|
||||
return numOperands_;
|
||||
}
|
||||
bool recover(JSContext *cx, SnapshotIterator &iter) const;
|
||||
};
|
||||
|
||||
class RAdd MOZ_FINAL : public RInstruction
|
||||
{
|
||||
private:
|
||||
bool isFloatOperation_;
|
||||
|
||||
public:
|
||||
RINSTRUCTION_HEADER_(Add)
|
||||
|
||||
virtual uint32_t numOperands() const {
|
||||
return 2;
|
||||
}
|
||||
|
||||
bool recover(JSContext *cx, SnapshotIterator &iter) const;
|
||||
};
|
||||
|
||||
#undef RINSTRUCTION_HEADER_
|
||||
|
||||
const RResumePoint *
|
||||
RInstruction::toResumePoint() const
|
||||
{
|
||||
|
@ -76,6 +76,9 @@ using namespace js::jit;
|
||||
// first register/stack-offset correspond to the holder of the type,
|
||||
// and the second correspond to the payload of the JS Value.
|
||||
//
|
||||
// RECOVER_INSTRUCTION [INDEX]
|
||||
// Index into the list of recovered instruction results.
|
||||
//
|
||||
// TYPED_REG [PACKED_TAG, GPR_REG]:
|
||||
// Value with statically known type, which payload is stored in a
|
||||
// register.
|
||||
@ -219,6 +222,15 @@ RValueAllocation::layoutFromMode(Mode mode)
|
||||
return layout;
|
||||
}
|
||||
#endif
|
||||
case RECOVER_INSTRUCTION: {
|
||||
static const RValueAllocation::Layout layout = {
|
||||
PAYLOAD_INDEX,
|
||||
PAYLOAD_NONE,
|
||||
"instruction"
|
||||
};
|
||||
return layout;
|
||||
}
|
||||
|
||||
default: {
|
||||
static const RValueAllocation::Layout regLayout = {
|
||||
PAYLOAD_PACKED_TAG,
|
||||
@ -662,20 +674,20 @@ SnapshotWriter::endSnapshot()
|
||||
}
|
||||
|
||||
RecoverOffset
|
||||
RecoverWriter::startRecover(uint32_t frameCount, bool resumeAfter)
|
||||
RecoverWriter::startRecover(uint32_t instructionCount, bool resumeAfter)
|
||||
{
|
||||
MOZ_ASSERT(frameCount);
|
||||
nframes_ = frameCount;
|
||||
framesWritten_ = 0;
|
||||
MOZ_ASSERT(instructionCount);
|
||||
instructionCount_ = instructionCount;
|
||||
instructionsWritten_ = 0;
|
||||
|
||||
IonSpew(IonSpew_Snapshots, "starting recover with frameCount %u",
|
||||
frameCount);
|
||||
IonSpew(IonSpew_Snapshots, "starting recover with %u instruction(s)",
|
||||
instructionCount);
|
||||
|
||||
MOZ_ASSERT(!(uint32_t(resumeAfter) &~ RECOVER_RESUMEAFTER_MASK));
|
||||
MOZ_ASSERT(frameCount < uint32_t(1 << RECOVER_RINSCOUNT_BITS));
|
||||
MOZ_ASSERT(instructionCount < uint32_t(1 << RECOVER_RINSCOUNT_BITS));
|
||||
uint32_t bits =
|
||||
(uint32_t(resumeAfter) << RECOVER_RESUMEAFTER_SHIFT) |
|
||||
(frameCount << RECOVER_RINSCOUNT_SHIFT);
|
||||
(instructionCount << RECOVER_RINSCOUNT_SHIFT);
|
||||
|
||||
RecoverOffset recoverOffset = writer_.length();
|
||||
writer_.writeUnsigned(bits);
|
||||
@ -683,16 +695,16 @@ RecoverWriter::startRecover(uint32_t frameCount, bool resumeAfter)
|
||||
}
|
||||
|
||||
bool
|
||||
RecoverWriter::writeFrame(const MResumePoint *rp)
|
||||
RecoverWriter::writeInstruction(const MNode *rp)
|
||||
{
|
||||
if (!rp->writeRecoverData(writer_))
|
||||
return false;
|
||||
framesWritten_++;
|
||||
instructionsWritten_++;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
RecoverWriter::endRecover()
|
||||
{
|
||||
JS_ASSERT(nframes_ == framesWritten_);
|
||||
MOZ_ASSERT(instructionCount_ == instructionsWritten_);
|
||||
}
|
||||
|
@ -54,6 +54,8 @@ class RValueAllocation
|
||||
UNTYPED_REG = 0x06,
|
||||
UNTYPED_STACK = 0x07,
|
||||
#endif
|
||||
RECOVER_INSTRUCTION = 0x0a,
|
||||
|
||||
// The JSValueType is packed in the Mode.
|
||||
TYPED_REG_MIN = 0x10,
|
||||
TYPED_REG_MAX = 0x17,
|
||||
@ -236,6 +238,11 @@ class RValueAllocation
|
||||
return RValueAllocation(CONSTANT, payloadOfIndex(index));
|
||||
}
|
||||
|
||||
// Recover instruction's index
|
||||
static RValueAllocation RecoverInstruction(uint32_t index) {
|
||||
return RValueAllocation(RECOVER_INSTRUCTION, payloadOfIndex(index));
|
||||
}
|
||||
|
||||
void writeHeader(CompactBufferWriter &writer, JSValueType type, uint32_t regCode) const;
|
||||
public:
|
||||
static RValueAllocation read(CompactBufferReader &reader);
|
||||
@ -360,19 +367,19 @@ class SnapshotWriter
|
||||
}
|
||||
};
|
||||
|
||||
class MResumePoint;
|
||||
class MNode;
|
||||
|
||||
class RecoverWriter
|
||||
{
|
||||
CompactBufferWriter writer_;
|
||||
|
||||
uint32_t nframes_;
|
||||
uint32_t framesWritten_;
|
||||
uint32_t instructionCount_;
|
||||
uint32_t instructionsWritten_;
|
||||
|
||||
public:
|
||||
SnapshotOffset startRecover(uint32_t frameCount, bool resumeAfter);
|
||||
SnapshotOffset startRecover(uint32_t instructionCount, bool resumeAfter);
|
||||
|
||||
bool writeFrame(const MResumePoint *rp);
|
||||
bool writeInstruction(const MNode *rp);
|
||||
|
||||
void endRecover();
|
||||
|
||||
@ -473,6 +480,13 @@ class RecoverReader
|
||||
public:
|
||||
RecoverReader(SnapshotReader &snapshot, const uint8_t *recovers, uint32_t size);
|
||||
|
||||
uint32_t numInstructions() const {
|
||||
return numInstructions_;
|
||||
}
|
||||
uint32_t numInstructionsRead() const {
|
||||
return numInstructionsRead_;
|
||||
}
|
||||
|
||||
bool moreInstructions() const {
|
||||
return numInstructionsRead_ < numInstructions_;
|
||||
}
|
||||
|
@ -1402,6 +1402,10 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM
|
||||
ma_mov(Imm32(0), reg, NoSetCond, Signed);
|
||||
}
|
||||
|
||||
void incrementInt32Value(const Address &addr) {
|
||||
add32(Imm32(1), ToPayload(addr));
|
||||
}
|
||||
|
||||
void cmp32(const Register &lhs, const Imm32 &rhs);
|
||||
void cmp32(const Register &lhs, const Register &rhs);
|
||||
void cmp32(const Operand &lhs, const Imm32 &rhs);
|
||||
|
@ -137,106 +137,116 @@ ToStackIndex(LAllocation *a)
|
||||
}
|
||||
|
||||
bool
|
||||
CodeGeneratorShared::encodeAllocations(LSnapshot *snapshot, MResumePoint *resumePoint,
|
||||
uint32_t *startIndex)
|
||||
CodeGeneratorShared::encodeAllocation(LSnapshot *snapshot, MDefinition *mir,
|
||||
uint32_t *allocIndex)
|
||||
{
|
||||
IonSpew(IonSpew_Codegen, "Encoding %u of resume point %p's operands starting from %u",
|
||||
resumePoint->numOperands(), (void *) resumePoint, *startIndex);
|
||||
for (uint32_t allocno = 0, e = resumePoint->numOperands(); allocno < e; allocno++) {
|
||||
uint32_t i = allocno + *startIndex;
|
||||
MDefinition *mir = resumePoint->getOperand(allocno);
|
||||
if (mir->isBox())
|
||||
mir = mir->toBox()->getOperand(0);
|
||||
|
||||
if (mir->isBox())
|
||||
mir = mir->toBox()->getOperand(0);
|
||||
MIRType type =
|
||||
mir->isRecoveredOnBailout() ? MIRType_None :
|
||||
mir->isUnused() ? MIRType_MagicOptimizedOut :
|
||||
mir->type();
|
||||
|
||||
MIRType type = mir->isUnused()
|
||||
? MIRType_MagicOptimizedOut
|
||||
: mir->type();
|
||||
RValueAllocation alloc;
|
||||
|
||||
RValueAllocation alloc;
|
||||
|
||||
switch (type) {
|
||||
case MIRType_Undefined:
|
||||
alloc = RValueAllocation::Undefined();
|
||||
break;
|
||||
case MIRType_Null:
|
||||
alloc = RValueAllocation::Null();
|
||||
break;
|
||||
case MIRType_Int32:
|
||||
case MIRType_String:
|
||||
case MIRType_Object:
|
||||
case MIRType_Boolean:
|
||||
case MIRType_Double:
|
||||
case MIRType_Float32:
|
||||
{
|
||||
LAllocation *payload = snapshot->payloadOfSlot(i);
|
||||
JSValueType valueType = ValueTypeFromMIRType(type);
|
||||
if (payload->isMemory()) {
|
||||
if (type == MIRType_Float32)
|
||||
alloc = RValueAllocation::Float32(ToStackIndex(payload));
|
||||
else
|
||||
alloc = RValueAllocation::Typed(valueType, ToStackIndex(payload));
|
||||
} else if (payload->isGeneralReg()) {
|
||||
alloc = RValueAllocation::Typed(valueType, ToRegister(payload));
|
||||
} else if (payload->isFloatReg()) {
|
||||
FloatRegister reg = ToFloatRegister(payload);
|
||||
if (type == MIRType_Float32)
|
||||
alloc = RValueAllocation::Float32(reg);
|
||||
else
|
||||
alloc = RValueAllocation::Double(reg);
|
||||
} else {
|
||||
MConstant *constant = mir->toConstant();
|
||||
uint32_t index;
|
||||
if (!graph.addConstantToPool(constant->value(), &index))
|
||||
return false;
|
||||
alloc = RValueAllocation::ConstantPool(index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MIRType_MagicOptimizedArguments:
|
||||
case MIRType_MagicOptimizedOut:
|
||||
{
|
||||
uint32_t index;
|
||||
JSWhyMagic why = (type == MIRType_MagicOptimizedArguments
|
||||
? JS_OPTIMIZED_ARGUMENTS
|
||||
: JS_OPTIMIZED_OUT);
|
||||
Value v = MagicValue(why);
|
||||
if (!graph.addConstantToPool(v, &index))
|
||||
return false;
|
||||
alloc = RValueAllocation::ConstantPool(index);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
JS_ASSERT(mir->type() == MIRType_Value);
|
||||
LAllocation *payload = snapshot->payloadOfSlot(i);
|
||||
#ifdef JS_NUNBOX32
|
||||
LAllocation *type = snapshot->typeOfSlot(i);
|
||||
if (type->isRegister()) {
|
||||
if (payload->isRegister())
|
||||
alloc = RValueAllocation::Untyped(ToRegister(type), ToRegister(payload));
|
||||
else
|
||||
alloc = RValueAllocation::Untyped(ToRegister(type), ToStackIndex(payload));
|
||||
} else {
|
||||
if (payload->isRegister())
|
||||
alloc = RValueAllocation::Untyped(ToStackIndex(type), ToRegister(payload));
|
||||
else
|
||||
alloc = RValueAllocation::Untyped(ToStackIndex(type), ToStackIndex(payload));
|
||||
}
|
||||
#elif JS_PUNBOX64
|
||||
if (payload->isRegister())
|
||||
alloc = RValueAllocation::Untyped(ToRegister(payload));
|
||||
else
|
||||
alloc = RValueAllocation::Untyped(ToStackIndex(payload));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
switch (type) {
|
||||
case MIRType_None:
|
||||
{
|
||||
MOZ_ASSERT(mir->isRecoveredOnBailout());
|
||||
uint32_t index = 0;
|
||||
LRecoverInfo *recoverInfo = snapshot->recoverInfo();
|
||||
MNode **it = recoverInfo->begin(), **end = recoverInfo->end();
|
||||
while (it != end && mir != *it) {
|
||||
++it;
|
||||
++index;
|
||||
}
|
||||
|
||||
snapshots_.add(alloc);
|
||||
// This MDefinition is recovered, thus it should be listed in the
|
||||
// LRecoverInfo.
|
||||
MOZ_ASSERT(it != end && mir == *it);
|
||||
alloc = RValueAllocation::RecoverInstruction(index);
|
||||
break;
|
||||
}
|
||||
case MIRType_Undefined:
|
||||
alloc = RValueAllocation::Undefined();
|
||||
break;
|
||||
case MIRType_Null:
|
||||
alloc = RValueAllocation::Null();
|
||||
break;
|
||||
case MIRType_Int32:
|
||||
case MIRType_String:
|
||||
case MIRType_Object:
|
||||
case MIRType_Boolean:
|
||||
case MIRType_Double:
|
||||
case MIRType_Float32:
|
||||
{
|
||||
LAllocation *payload = snapshot->payloadOfSlot(*allocIndex);
|
||||
JSValueType valueType = ValueTypeFromMIRType(type);
|
||||
if (payload->isMemory()) {
|
||||
if (type == MIRType_Float32)
|
||||
alloc = RValueAllocation::Float32(ToStackIndex(payload));
|
||||
else
|
||||
alloc = RValueAllocation::Typed(valueType, ToStackIndex(payload));
|
||||
} else if (payload->isGeneralReg()) {
|
||||
alloc = RValueAllocation::Typed(valueType, ToRegister(payload));
|
||||
} else if (payload->isFloatReg()) {
|
||||
FloatRegister reg = ToFloatRegister(payload);
|
||||
if (type == MIRType_Float32)
|
||||
alloc = RValueAllocation::Float32(reg);
|
||||
else
|
||||
alloc = RValueAllocation::Double(reg);
|
||||
} else {
|
||||
MConstant *constant = mir->toConstant();
|
||||
uint32_t index;
|
||||
if (!graph.addConstantToPool(constant->value(), &index))
|
||||
return false;
|
||||
alloc = RValueAllocation::ConstantPool(index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MIRType_MagicOptimizedArguments:
|
||||
case MIRType_MagicOptimizedOut:
|
||||
{
|
||||
uint32_t index;
|
||||
JSWhyMagic why = (type == MIRType_MagicOptimizedArguments
|
||||
? JS_OPTIMIZED_ARGUMENTS
|
||||
: JS_OPTIMIZED_OUT);
|
||||
Value v = MagicValue(why);
|
||||
if (!graph.addConstantToPool(v, &index))
|
||||
return false;
|
||||
alloc = RValueAllocation::ConstantPool(index);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
JS_ASSERT(mir->type() == MIRType_Value);
|
||||
LAllocation *payload = snapshot->payloadOfSlot(*allocIndex);
|
||||
#ifdef JS_NUNBOX32
|
||||
LAllocation *type = snapshot->typeOfSlot(*allocIndex);
|
||||
if (type->isRegister()) {
|
||||
if (payload->isRegister())
|
||||
alloc = RValueAllocation::Untyped(ToRegister(type), ToRegister(payload));
|
||||
else
|
||||
alloc = RValueAllocation::Untyped(ToRegister(type), ToStackIndex(payload));
|
||||
} else {
|
||||
if (payload->isRegister())
|
||||
alloc = RValueAllocation::Untyped(ToStackIndex(type), ToRegister(payload));
|
||||
else
|
||||
alloc = RValueAllocation::Untyped(ToStackIndex(type), ToStackIndex(payload));
|
||||
}
|
||||
#elif JS_PUNBOX64
|
||||
if (payload->isRegister())
|
||||
alloc = RValueAllocation::Untyped(ToRegister(payload));
|
||||
else
|
||||
alloc = RValueAllocation::Untyped(ToStackIndex(payload));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*startIndex += resumePoint->numOperands();
|
||||
snapshots_.add(alloc);
|
||||
*allocIndex += mir->isRecoveredOnBailout() ? 0 : 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -246,21 +256,18 @@ CodeGeneratorShared::encode(LRecoverInfo *recover)
|
||||
if (recover->recoverOffset() != INVALID_RECOVER_OFFSET)
|
||||
return true;
|
||||
|
||||
uint32_t frameCount = recover->mir()->frameCount();
|
||||
IonSpew(IonSpew_Snapshots, "Encoding LRecoverInfo %p (frameCount %u)",
|
||||
(void *)recover, frameCount);
|
||||
uint32_t numInstructions = recover->numInstructions();
|
||||
IonSpew(IonSpew_Snapshots, "Encoding LRecoverInfo %p (frameCount %u, instructions %u)",
|
||||
(void *)recover, recover->mir()->frameCount(), numInstructions);
|
||||
|
||||
MResumePoint::Mode mode = recover->mir()->mode();
|
||||
JS_ASSERT(mode != MResumePoint::Outer);
|
||||
bool resumeAfter = (mode == MResumePoint::ResumeAfter);
|
||||
|
||||
RecoverOffset offset = recovers_.startRecover(frameCount, resumeAfter);
|
||||
RecoverOffset offset = recovers_.startRecover(numInstructions, resumeAfter);
|
||||
|
||||
for (MResumePoint **it = recover->begin(), **end = recover->end();
|
||||
it != end;
|
||||
++it)
|
||||
{
|
||||
if (!recovers_.writeFrame(*it))
|
||||
for (MNode **it = recover->begin(), **end = recover->end(); it != end; ++it) {
|
||||
if (!recovers_.writeInstruction(*it))
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -307,17 +314,17 @@ CodeGeneratorShared::encode(LSnapshot *snapshot)
|
||||
snapshots_.trackSnapshot(pcOpcode, mirOpcode, mirId, lirOpcode, lirId);
|
||||
#endif
|
||||
|
||||
uint32_t startIndex = 0;
|
||||
for (MResumePoint **it = recoverInfo->begin(), **end = recoverInfo->end();
|
||||
it != end;
|
||||
++it)
|
||||
{
|
||||
MResumePoint *mir = *it;
|
||||
if (!encodeAllocations(snapshot, mir, &startIndex))
|
||||
uint32_t allocIndex = 0;
|
||||
LRecoverInfo::OperandIter it(recoverInfo->begin());
|
||||
LRecoverInfo::OperandIter end(recoverInfo->end());
|
||||
for (; it != end; ++it) {
|
||||
DebugOnly<uint32_t> allocWritten = snapshots_.allocWritten();
|
||||
if (!encodeAllocation(snapshot, *it, &allocIndex))
|
||||
return false;
|
||||
MOZ_ASSERT(allocWritten + 1 == snapshots_.allocWritten());
|
||||
}
|
||||
|
||||
MOZ_ASSERT(snapshots_.allocWritten() == snapshot->numSlots());
|
||||
MOZ_ASSERT(allocIndex == snapshot->numSlots());
|
||||
snapshots_.endSnapshot();
|
||||
snapshot->setSnapshotOffset(offset);
|
||||
return !snapshots_.oom();
|
||||
|
@ -272,7 +272,7 @@ class CodeGeneratorShared : public LInstructionVisitor
|
||||
// false on failure.
|
||||
bool encode(LRecoverInfo *recover);
|
||||
bool encode(LSnapshot *snapshot);
|
||||
bool encodeAllocations(LSnapshot *snapshot, MResumePoint *resumePoint, uint32_t *startIndex);
|
||||
bool encodeAllocation(LSnapshot *snapshot, MDefinition *def, uint32_t *startIndex);
|
||||
|
||||
// Attempts to assign a BailoutId to a snapshot, if one isn't already set.
|
||||
// If the bailout table is full, this returns false, which is not a fatal
|
||||
|
@ -74,49 +74,51 @@ LIRGeneratorShared::getRecoverInfo(MResumePoint *rp)
|
||||
LSnapshot *
|
||||
LIRGeneratorShared::buildSnapshot(LInstruction *ins, MResumePoint *rp, BailoutKind kind)
|
||||
{
|
||||
LRecoverInfo *recover = getRecoverInfo(rp);
|
||||
if (!recover)
|
||||
LRecoverInfo *recoverInfo = getRecoverInfo(rp);
|
||||
if (!recoverInfo)
|
||||
return nullptr;
|
||||
|
||||
LSnapshot *snapshot = LSnapshot::New(gen, recover, kind);
|
||||
LSnapshot *snapshot = LSnapshot::New(gen, recoverInfo, kind);
|
||||
if (!snapshot)
|
||||
return nullptr;
|
||||
|
||||
size_t i = 0;
|
||||
for (MResumePoint **it = recover->begin(), **end = recover->end(); it != end; ++it) {
|
||||
MResumePoint *mir = *it;
|
||||
for (size_t j = 0, e = mir->numOperands(); j < e; ++i, ++j) {
|
||||
MDefinition *ins = mir->getOperand(j);
|
||||
size_t index = 0;
|
||||
LRecoverInfo::OperandIter it(recoverInfo->begin());
|
||||
LRecoverInfo::OperandIter end(recoverInfo->end());
|
||||
for (; it != end; ++it) {
|
||||
MDefinition *ins = *it;
|
||||
if (ins->isRecoveredOnBailout())
|
||||
continue;
|
||||
|
||||
LAllocation *type = snapshot->typeOfSlot(i);
|
||||
LAllocation *payload = snapshot->payloadOfSlot(i);
|
||||
LAllocation *type = snapshot->typeOfSlot(index);
|
||||
LAllocation *payload = snapshot->payloadOfSlot(index);
|
||||
++index;
|
||||
|
||||
if (ins->isBox())
|
||||
ins = ins->toBox()->getOperand(0);
|
||||
if (ins->isBox())
|
||||
ins = ins->toBox()->getOperand(0);
|
||||
|
||||
// Guards should never be eliminated.
|
||||
JS_ASSERT_IF(ins->isUnused(), !ins->isGuard());
|
||||
// Guards should never be eliminated.
|
||||
JS_ASSERT_IF(ins->isUnused(), !ins->isGuard());
|
||||
|
||||
// Snapshot operands other than constants should never be
|
||||
// emitted-at-uses. Try-catch support depends on there being no
|
||||
// code between an instruction and the LOsiPoint that follows it.
|
||||
JS_ASSERT_IF(!ins->isConstant(), !ins->isEmittedAtUses());
|
||||
// Snapshot operands other than constants should never be
|
||||
// emitted-at-uses. Try-catch support depends on there being no
|
||||
// code between an instruction and the LOsiPoint that follows it.
|
||||
JS_ASSERT_IF(!ins->isConstant(), !ins->isEmittedAtUses());
|
||||
|
||||
// The register allocation will fill these fields in with actual
|
||||
// register/stack assignments. During code generation, we can restore
|
||||
// interpreter state with the given information. Note that for
|
||||
// constants, including known types, we record a dummy placeholder,
|
||||
// since we can recover the same information, much cleaner, from MIR.
|
||||
if (ins->isConstant() || ins->isUnused()) {
|
||||
*type = LConstantIndex::Bogus();
|
||||
*payload = LConstantIndex::Bogus();
|
||||
} else if (ins->type() != MIRType_Value) {
|
||||
*type = LConstantIndex::Bogus();
|
||||
*payload = use(ins, LUse::KEEPALIVE);
|
||||
} else {
|
||||
*type = useType(ins, LUse::KEEPALIVE);
|
||||
*payload = usePayload(ins, LUse::KEEPALIVE);
|
||||
}
|
||||
// The register allocation will fill these fields in with actual
|
||||
// register/stack assignments. During code generation, we can restore
|
||||
// interpreter state with the given information. Note that for
|
||||
// constants, including known types, we record a dummy placeholder,
|
||||
// since we can recover the same information, much cleaner, from MIR.
|
||||
if (ins->isConstant() || ins->isUnused()) {
|
||||
*type = LConstantIndex::Bogus();
|
||||
*payload = LConstantIndex::Bogus();
|
||||
} else if (ins->type() != MIRType_Value) {
|
||||
*type = LConstantIndex::Bogus();
|
||||
*payload = use(ins, LUse::KEEPALIVE);
|
||||
} else {
|
||||
*type = useType(ins, LUse::KEEPALIVE);
|
||||
*payload = usePayload(ins, LUse::KEEPALIVE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,40 +130,42 @@ LIRGeneratorShared::buildSnapshot(LInstruction *ins, MResumePoint *rp, BailoutKi
|
||||
LSnapshot *
|
||||
LIRGeneratorShared::buildSnapshot(LInstruction *ins, MResumePoint *rp, BailoutKind kind)
|
||||
{
|
||||
LRecoverInfo *recover = getRecoverInfo(rp);
|
||||
if (!recover)
|
||||
LRecoverInfo *recoverInfo = getRecoverInfo(rp);
|
||||
if (!recoverInfo)
|
||||
return nullptr;
|
||||
|
||||
LSnapshot *snapshot = LSnapshot::New(gen, recover, kind);
|
||||
LSnapshot *snapshot = LSnapshot::New(gen, recoverInfo, kind);
|
||||
if (!snapshot)
|
||||
return nullptr;
|
||||
|
||||
size_t i = 0;
|
||||
for (MResumePoint **it = recover->begin(), **end = recover->end(); it != end; ++it) {
|
||||
MResumePoint *mir = *it;
|
||||
for (size_t j = 0, e = mir->numOperands(); j < e; ++i, ++j) {
|
||||
MDefinition *def = mir->getOperand(j);
|
||||
size_t index = 0;
|
||||
LRecoverInfo::OperandIter it(recoverInfo->begin());
|
||||
LRecoverInfo::OperandIter end(recoverInfo->end());
|
||||
for (; it != end; ++it) {
|
||||
MDefinition *def = *it;
|
||||
|
||||
if (def->isBox())
|
||||
def = def->toBox()->getOperand(0);
|
||||
if (def->isRecoveredOnBailout())
|
||||
continue;
|
||||
|
||||
// Guards should never be eliminated.
|
||||
JS_ASSERT_IF(def->isUnused(), !def->isGuard());
|
||||
if (def->isBox())
|
||||
def = def->toBox()->getOperand(0);
|
||||
|
||||
// Snapshot operands other than constants should never be
|
||||
// emitted-at-uses. Try-catch support depends on there being no
|
||||
// code between an instruction and the LOsiPoint that follows it.
|
||||
JS_ASSERT_IF(!def->isConstant(), !def->isEmittedAtUses());
|
||||
// Guards should never be eliminated.
|
||||
JS_ASSERT_IF(def->isUnused(), !def->isGuard());
|
||||
|
||||
LAllocation *a = snapshot->getEntry(i);
|
||||
// Snapshot operands other than constants should never be
|
||||
// emitted-at-uses. Try-catch support depends on there being no
|
||||
// code between an instruction and the LOsiPoint that follows it.
|
||||
JS_ASSERT_IF(!def->isConstant(), !def->isEmittedAtUses());
|
||||
|
||||
if (def->isUnused()) {
|
||||
*a = LConstantIndex::Bogus();
|
||||
continue;
|
||||
}
|
||||
LAllocation *a = snapshot->getEntry(index++);
|
||||
|
||||
*a = useKeepaliveOrConstant(def);
|
||||
if (def->isUnused()) {
|
||||
*a = LConstantIndex::Bogus();
|
||||
continue;
|
||||
}
|
||||
|
||||
*a = useKeepaliveOrConstant(def);
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
|
@ -1237,6 +1237,10 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared
|
||||
}
|
||||
}
|
||||
|
||||
void incrementInt32Value(const Address &addr) {
|
||||
addPtr(Imm32(1), addr);
|
||||
}
|
||||
|
||||
// If source is a double, load it into dest. If source is int32,
|
||||
// convert it to double. Else, branch to failure.
|
||||
void ensureDouble(const ValueOperand &source, FloatRegister dest, Label *failure) {
|
||||
|
@ -1035,6 +1035,9 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared
|
||||
bind(&noOverflow);
|
||||
}
|
||||
|
||||
void incrementInt32Value(const Address &addr) {
|
||||
addl(Imm32(1), payloadOf(addr));
|
||||
}
|
||||
|
||||
// If source is a double, load it into dest. If source is int32,
|
||||
// convert it to double. Else, branch to failure.
|
||||
|
@ -235,13 +235,10 @@ class AutoVectorRooter : protected AutoGCRooter
|
||||
return vector.reserve(newLength);
|
||||
}
|
||||
|
||||
T &operator[](size_t i) { return vector[i]; }
|
||||
const T &operator[](size_t i) const { return vector[i]; }
|
||||
|
||||
JS::MutableHandle<T> handleAt(size_t i) {
|
||||
JS::MutableHandle<T> operator[](size_t i) {
|
||||
return JS::MutableHandle<T>::fromMarkedLocation(&vector[i]);
|
||||
}
|
||||
JS::Handle<T> handleAt(size_t i) const {
|
||||
JS::Handle<T> operator[](size_t i) const {
|
||||
return JS::Handle<T>::fromMarkedLocation(&vector[i]);
|
||||
}
|
||||
|
||||
|
@ -1744,14 +1744,14 @@ MergeSortByKey(K keys, size_t len, K scratch, C comparator, AutoValueVector *vec
|
||||
do {
|
||||
size_t k = keys[j].elementIndex;
|
||||
keys[j].elementIndex = j;
|
||||
(*vec)[j] = (*vec)[k];
|
||||
(*vec)[j].set((*vec)[k]);
|
||||
j = k;
|
||||
} while (j != i);
|
||||
|
||||
// We could assert the loop invariant that |i == keys[i].elementIndex|
|
||||
// here if we synced |keys[i].elementIndex|. But doing so would render
|
||||
// the assertion vacuous, so don't bother, even in debug builds.
|
||||
(*vec)[i] = tv;
|
||||
(*vec)[i].set(tv);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -1820,7 +1820,7 @@ SortNumerically(JSContext *cx, AutoValueVector *vec, size_t len, ComparatorMatch
|
||||
return false;
|
||||
|
||||
double dv;
|
||||
if (!ToNumber(cx, vec->handleAt(i), &dv))
|
||||
if (!ToNumber(cx, (*vec)[i], &dv))
|
||||
return false;
|
||||
|
||||
NumericElement el = { dv, i };
|
||||
|
@ -2771,8 +2771,10 @@ BeginMarkPhase(JSRuntime *rt)
|
||||
}
|
||||
|
||||
if (!rt->gcShouldCleanUpEverything) {
|
||||
#ifdef JS_ION
|
||||
if (JSCompartment *comp = jit::TopmostJitActivationCompartment(rt))
|
||||
comp->zone()->setPreservingCode(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -351,6 +351,26 @@ TypeSet::mightBeMIRType(jit::MIRType type)
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
TypeSet::objectsAreSubset(TypeSet *other)
|
||||
{
|
||||
if (other->unknownObject())
|
||||
return true;
|
||||
|
||||
if (unknownObject())
|
||||
return false;
|
||||
|
||||
for (unsigned i = 0; i < getObjectCount(); i++) {
|
||||
TypeObjectKey *obj = getObject(i);
|
||||
if (!obj)
|
||||
continue;
|
||||
if (!other->hasType(Type::ObjectType(obj)))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TypeSet::isSubset(TypeSet *other)
|
||||
{
|
||||
|
@ -584,6 +584,12 @@ class TypeSet
|
||||
*/
|
||||
bool isSubset(TypeSet *other);
|
||||
|
||||
/*
|
||||
* Get whether the objects in this TypeSet are a subset of the objects
|
||||
* in other.
|
||||
*/
|
||||
bool objectsAreSubset(TypeSet *other);
|
||||
|
||||
/* Forward all types in this set to the specified constraint. */
|
||||
bool addTypesToConstraint(JSContext *cx, TypeConstraint *constraint);
|
||||
|
||||
|
@ -463,7 +463,7 @@ js::math_imul(JSContext *cx, unsigned argc, Value *vp)
|
||||
|
||||
// Implements Math.fround (20.2.2.16) up to step 3
|
||||
bool
|
||||
js::RoundFloat32(JSContext *cx, Handle<Value> v, float *out)
|
||||
js::RoundFloat32(JSContext *cx, HandleValue v, float *out)
|
||||
{
|
||||
double d;
|
||||
bool success = ToNumber(cx, v, &d);
|
||||
@ -471,6 +471,17 @@ js::RoundFloat32(JSContext *cx, Handle<Value> v, float *out)
|
||||
return success;
|
||||
}
|
||||
|
||||
bool
|
||||
js::RoundFloat32(JSContext *cx, HandleValue arg, MutableHandleValue res)
|
||||
{
|
||||
float f;
|
||||
if (!RoundFloat32(cx, arg, &f))
|
||||
return false;
|
||||
|
||||
res.setDouble(static_cast<double>(f));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
js::math_fround(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
|
@ -110,7 +110,10 @@ extern bool
|
||||
math_imul(JSContext *cx, unsigned argc, js::Value *vp);
|
||||
|
||||
extern bool
|
||||
RoundFloat32(JSContext *cx, Handle<Value> v, float *out);
|
||||
RoundFloat32(JSContext *cx, HandleValue v, float *out);
|
||||
|
||||
extern bool
|
||||
RoundFloat32(JSContext *cx, HandleValue arg, MutableHandleValue res);
|
||||
|
||||
extern bool
|
||||
math_fround(JSContext *cx, unsigned argc, js::Value *vp);
|
||||
|
@ -1003,7 +1003,7 @@ js::DefineProperties(JSContext *cx, HandleObject obj, HandleObject props)
|
||||
bool dummy;
|
||||
Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
|
||||
for (size_t i = 0, len = ids.length(); i < len; i++) {
|
||||
if (!DefinePropertyOnArray(cx, arr, ids.handleAt(i), descs[i], true, &dummy))
|
||||
if (!DefinePropertyOnArray(cx, arr, ids[i], descs[i], true, &dummy))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -1017,7 +1017,7 @@ js::DefineProperties(JSContext *cx, HandleObject obj, HandleObject props)
|
||||
if (obj->is<ProxyObject>()) {
|
||||
for (size_t i = 0, len = ids.length(); i < len; i++) {
|
||||
RootedValue pd(cx, descs[i].pd());
|
||||
if (!Proxy::defineProperty(cx, obj, ids.handleAt(i), pd))
|
||||
if (!Proxy::defineProperty(cx, obj, ids[i], pd))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -1028,7 +1028,7 @@ js::DefineProperties(JSContext *cx, HandleObject obj, HandleObject props)
|
||||
|
||||
bool dummy;
|
||||
for (size_t i = 0, len = ids.length(); i < len; i++) {
|
||||
if (!DefinePropertyOnObject(cx, obj, ids.handleAt(i), descs[i], true, &dummy))
|
||||
if (!DefinePropertyOnObject(cx, obj, ids[i], descs[i], true, &dummy))
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1743,7 +1743,7 @@ JS_CopyPropertiesFrom(JSContext *cx, HandleObject target, HandleObject obj)
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < props.length(); ++i) {
|
||||
if (!JS_CopyPropertyFrom(cx, props.handleAt(i), target, obj))
|
||||
if (!JS_CopyPropertyFrom(cx, props[i], target, obj))
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2013,7 +2013,7 @@ js::XDRObjectLiteral(XDRState<mode> *xdr, MutableHandleObject obj)
|
||||
}
|
||||
|
||||
JS_ASSERT(it.front().hasDefaultGetter());
|
||||
ids[it.front().slot()] = it.front().propid();
|
||||
ids[it.front().slot()].set(it.front().propid());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -243,7 +243,7 @@ BaseProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
|
||||
if (!getOwnPropertyDescriptor(cx, proxy, id, &desc))
|
||||
return false;
|
||||
if (desc.object() && desc.isEnumerable())
|
||||
props[i++] = id;
|
||||
props[i++].set(id);
|
||||
}
|
||||
|
||||
JS_ASSERT(i <= props.length());
|
||||
@ -1582,7 +1582,7 @@ ArrayToIdVector(JSContext *cx, HandleObject proxy, HandleObject target, HandleVa
|
||||
|
||||
// step iii
|
||||
for (uint32_t j = 0; j < i; ++j) {
|
||||
if (props[j] == id) {
|
||||
if (props[j].get() == id) {
|
||||
ReportInvalidTrapResult(cx, proxy, trapName);
|
||||
return false;
|
||||
}
|
||||
@ -1618,7 +1618,7 @@ ArrayToIdVector(JSContext *cx, HandleObject proxy, HandleObject target, HandleVa
|
||||
|
||||
bool found = false;
|
||||
for (size_t j = 0; j < props.length(); ++j) {
|
||||
if (props[j] == id) {
|
||||
if (props[j].get() == id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@ -2453,7 +2453,7 @@ js::AppendUnique(JSContext *cx, AutoIdVector &base, AutoIdVector &others)
|
||||
for (size_t i = 0; i < others.length(); ++i) {
|
||||
bool unique = true;
|
||||
for (size_t j = 0; j < base.length(); ++j) {
|
||||
if (others[i] == base[j]) {
|
||||
if (others[i].get() == base[j]) {
|
||||
unique = false;
|
||||
break;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user