author | Ryan VanderMeulen <ryanvm@gmail.com> |
Mon, 06 Oct 2014 17:05:56 -0400 | |
changeset 209043 | eaa80e4597a28369a22b5990d0a6bc24f39bdb45 |
parent 209042 | 76656d86452a05df59859dac6a76bef13da02b80 (current diff) |
parent 208989 | b19c3336ea3f1e82d087621ae5b50e99887269ae (diff) |
child 209044 | fbb1b7c89a32334396fbe995fdd2db88179e540f |
child 209059 | 0208159db84df29d10dd18705a53c6d5334db613 |
child 209089 | 650603771acde4563e4b8b20d3efd213c746d9cb |
child 209133 | abafd004850c8dff66f9b977d5f7bedec56a4931 |
push id | 50068 |
push user | ryanvm@gmail.com |
push date | Mon, 06 Oct 2014 21:29:55 +0000 |
treeherder | mozilla-inbound@fbb1b7c89a32 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 35.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
gfx/angle/src/libGLESv2/constants.h | file | annotate | diff | comparison | revisions |
--- a/content/base/src/ImportManager.cpp +++ b/content/base/src/ImportManager.cpp @@ -86,16 +86,20 @@ ImportLoader::Updater::GetReferrerChain( for (uint32_t i = 0; i < l / 2; i++) { Swap(aResult[i], aResult[l - i - 1]); } } bool ImportLoader::Updater::ShouldUpdate(nsTArray<nsINode*>& aNewPath) { + if (mLoader->Manager()->GetNearestPredecessor(mLoader->GetMainReferrer()) != + mLoader->mBlockingPredecessor) { + return true; + } // Let's walk down on the main referrer chains of both the current main and // the new link, and find the last pair of links that are from the same // document. This is the junction point between the two referrer chain. Their // order in the subimport list of that document will determine if we have to // update the spanning tree or this new edge changes nothing in the script // execution order. nsTArray<nsINode*> oldPath; GetReferrerChain(mLoader->mLinks[mLoader->mMainReferrer], oldPath); @@ -737,32 +741,40 @@ ImportManager::AddLoaderWithNewURI(Impor } nsRefPtr<ImportLoader> ImportManager::GetNearestPredecessor(nsINode* aNode) { // Return the previous link if there is any in the same document. nsIDocument* doc = aNode->OwnerDoc(); int32_t idx = doc->IndexOfSubImportLink(aNode); MOZ_ASSERT(idx != -1, "aNode must be a sub import link of its owner document"); + + for (; idx > 0; idx--) { + HTMLLinkElement* link = + static_cast<HTMLLinkElement*>(doc->GetSubImportLink(idx - 1)); + nsCOMPtr<nsIURI> uri = link->GetHrefURI(); + nsRefPtr<ImportLoader> ret; + mImports.Get(uri, getter_AddRefs(ret)); + // Only main referrer links are interesting. + if (ret->GetMainReferrer() == link) { + return ret; + } + } + if (idx == 0) { if (doc->IsMasterDocument()) { // If there is no previous one, and it was the master document, then // there is no predecessor. return nullptr; } // Else we find the main referrer of the import parent of the link's document. // And do a recursion. ImportLoader* owner = Find(doc); MOZ_ASSERT(owner); nsCOMPtr<nsINode> mainReferrer = owner->GetMainReferrer(); return GetNearestPredecessor(mainReferrer); } - MOZ_ASSERT(idx > 0); - HTMLLinkElement* link = - static_cast<HTMLLinkElement*>(doc->GetSubImportLink(idx - 1)); - nsCOMPtr<nsIURI> uri = link->GetHrefURI(); - nsRefPtr<ImportLoader> ret; - mImports.Get(uri, getter_AddRefs(ret)); - return ret; + + return nullptr; } } // namespace dom } // namespace mozilla
--- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -3515,16 +3515,20 @@ nsDocument::GetBaseTarget(nsAString &aBa void nsDocument::SetDocumentCharacterSet(const nsACString& aCharSetID) { // XXX it would be a good idea to assert the sanity of the argument, // but before we figure out what to do about non-Encoding Standard // encodings in the charset menu and in mailnews, assertions are futile. if (!mCharacterSet.Equals(aCharSetID)) { + if (mMasterDocument && !aCharSetID.EqualsLiteral("UTF-8")) { + // Imports are always UTF-8 + return; + } mCharacterSet = aCharSetID; int32_t n = mCharSetObservers.Length(); for (int32_t i = 0; i < n; i++) { nsIObserver* observer = mCharSetObservers.ElementAt(i); observer->Observe(static_cast<nsIDocument *>(this), "charset",
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_1_A.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_1_B.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("A"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_1_B.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_1_C.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_1_A.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("B"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_1_C.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_1_B.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_1_A.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("C"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_2_A.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_2_B.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("A"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_2_B.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_2_A.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_2_C.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_2_D.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("B"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_2_C.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> +</head> +<body> + <script> + order.push("C"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_2_D.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> +</head> +<body> + <script> + order.push("D"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_3_A.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_3_C.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("A"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_3_B.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_3_C.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("B"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_3_C.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_3_A.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_3_B.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("C"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_4_A.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_4_B.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("A"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_4_B.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_4_C.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("B"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_4_C.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_4_D.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("C"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_4_D.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_4_B.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_4_E.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("D"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_4_E.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_4_C.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("E"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_5_A.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_5_B.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_5_D.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_5_C.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("A"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_5_B.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_5_A.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_5_C.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_5_D.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("B"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_5_C.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_5_A.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_5_B.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_5_D.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("C"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_cycle_5_D.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html lang="en-US"> +<head> + <link rel="import" href="file_cycle_5_A.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_5_B.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_5_C.html" onload="loaded()" onerror="failed()"></link> +</head> +<body> + <script> + order.push("D"); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/file_encoding.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<head> +<meta charset="EUC-KR"> +</head> +<body>Ignore my encoding</body> \ No newline at end of file
--- a/content/html/content/test/imports/mochitest.ini +++ b/content/html/content/test/imports/mochitest.ini @@ -11,10 +11,40 @@ support-files = file_importC5.html file_importC6.html file_importC7.html file_importC8.html file_importC9.html file_importC10.html file_importD.html file_importE.html + file_cycle_1_A.html + file_cycle_1_B.html + file_cycle_1_C.html + file_cycle_2_A.html + file_cycle_2_B.html + file_cycle_2_C.html + file_cycle_2_D.html + file_cycle_3_A.html + file_cycle_3_B.html + file_cycle_3_C.html + file_cycle_4_A.html + file_cycle_4_B.html + file_cycle_4_C.html + file_cycle_4_D.html + file_cycle_4_E.html + file_cycle_5_A.html + file_cycle_5_B.html + file_cycle_5_C.html + file_cycle_5_D.html + file_encoding.html - +[test_cycle_1.html] +skip-if = toolkit == 'gonk' # nested imports fail on b2g emulator +[test_cycle_2.html] +skip-if = toolkit == 'gonk' # nested imports fail on b2g emulator +[test_cycle_3.html] +skip-if = toolkit == 'gonk' # nested imports fail on b2g emulator +[test_cycle_4.html] +skip-if = toolkit == 'gonk' # nested imports fail on b2g emulator +[test_cycle_5.html] +skip-if = toolkit == 'gonk' # nested imports fail on b2g emulator +[test_encoding.html]
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/test_cycle_1.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1061469 +--> +<head> + <title>Test for Bug 1061469</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> +</head> +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1061469">Mozilla Bug 1061469</a> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + var counter = 0; + var fcounter = 0; + var order = []; + function loaded() { + counter++; + } + function failed() { + fcounter++; + } + </script> + <link rel="import" href="file_cycle_1_A.html" onload="loaded()" onerror="failed()"></link> + <script type="text/javascript"> + is(counter, 6, "Imports are loaded"); + is(fcounter, 0, "No error in imports"); + var expected = ["C","B","A"]; + for (i in expected) + is(order[i], expected[i], "import " + i + " should be " + expected[i]); + SimpleTest.finish(); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/test_cycle_2.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1061469 +--> +<head> + <title>Test for Bug 1061469</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> +</head> +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1061469">Mozilla Bug 1061469</a> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + var counter = 0; + var fcounter = 0; + var order = []; + function loaded() { + counter++; + } + function failed() { + fcounter++; + } + </script> + <link rel="import" href="file_cycle_2_A.html" onload="loaded()" onerror="failed()"></link> + <script type="text/javascript"> + is(counter, 5, "Imports are loaded"); + is(fcounter, 0, "No error in imports"); + var expected = ["C","D","B","A"]; + for (i in expected) + is(order[i], expected[i], "import " + i + " should be " + expected[i]); + SimpleTest.finish(); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/test_cycle_3.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1061469 +--> +<head> + <title>Test for Bug 1061469</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> +</head> +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1061469">Mozilla Bug 1061469</a> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + var counter = 0; + var fcounter = 0; + var order = []; + function loaded() { + counter++; + } + function failed() { + fcounter++; + } + </script> + <link rel="import" href="file_cycle_3_A.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_3_B.html" onload="loaded()" onerror="failed()"></link> + <script type="text/javascript"> + is(counter, 6, "Imports are loaded"); + is(fcounter, 0, "No error in imports"); + var expected = ["B","C","A"]; + for (i in expected) + is(order[i], expected[i], "import " + i + " should be " + expected[i]); + SimpleTest.finish(); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/test_cycle_4.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1061469 +--> +<head> + <title>Test for Bug 1061469</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> +</head> +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1061469">Mozilla Bug 1061469</a> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + var counter = 0; + var fcounter = 0; + var order = []; + function loaded() { + counter++; + } + function failed() { + fcounter++; + } + </script> + <link rel="import" href="file_cycle_4_A.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_4_D.html" onload="loaded()" onerror="failed()"></link> + <script type="text/javascript"> + is(counter, 8, "Imports are loaded"); + is(fcounter, 0, "No error in imports"); + var expected = ["E","D","C","B","A"]; + for (i in expected) + is(order[i], expected[i], "import " + i + " should be " + expected[i]); + SimpleTest.finish(); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/test_cycle_5.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1061469 +--> +<head> + <title>Test for Bug 1061469</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> +</head> +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1061469">Mozilla Bug 1061469</a> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + var counter = 0; + var fcounter = 0; + var order = []; + function loaded() { + counter++; + } + function failed() { + fcounter++; + } + </script> + <link rel="import" href="file_cycle_5_A.html" onload="loaded()" onerror="failed()"></link> + <link rel="import" href="file_cycle_5_C.html" onload="loaded()" onerror="failed()"></link> + <script type="text/javascript"> + is(counter, 14, "Imports are loaded"); + is(fcounter, 0, "No error in imports"); + var expected = ["D","C","B","A"]; + for (i in expected) + is(order[i], expected[i], "import " + i + " should be " + expected[i]); + SimpleTest.finish(); + </script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/content/html/content/test/imports/test_encoding.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1061469 +--> +<head> + <title>Test for Bug 1061469</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> +</head> +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1061469">Mozilla Bug 1061469</a> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + var success = false; + function loaded() { + success = true; + } + function failed() { + ok(false, "Import loading failed"); + } + </script> + <link rel="import" href="file_encoding.html" id="import" onload="loaded()" onerror="failed()"></link> + <script type="text/javascript"> + is(document.getElementById("import").import.characterSet, "UTF-8", "characterSet should be UTF-8 for imports"); + SimpleTest.finish(); + </script> +</body> +</html> \ No newline at end of file
--- a/content/html/content/test/test_video_wakelock.html +++ b/content/html/content/test/test_video_wakelock.html @@ -16,64 +16,70 @@ https://bugzilla.mozilla.org/show_bug.cg <div id="content"> </div> <pre id="test"> <script type="application/javascript"> /** Test for Bug 868943 **/ function testVideoPlayPause() { + info("#1 testVideoPlayPause"); + var lockState_cpu = true; var lockState_screen = true; var count_cpu = 0; var count_screen = 0; var content = document.getElementById('content'); var video = document.createElement('video'); video.src = "wakelock.ogv"; content.appendChild(video); var startDate; - video.addEventListener('playing', function() { - startDate = new Date(); + function testVideoPlayPauseListener(topic, state) { + info("#1 topic=" + topic + ", state=" + state); - // The next step is to unlock the resource. - lockState_cpu = false; - lockState_screen = false; - video.pause(); - }); - - function testVideoPlayPauseListener(topic, state) { var locked = state == "locked-foreground" || state == "locked-background"; if (topic == "cpu") { - is(locked, lockState_cpu, "Video element locked the cpu - paused"); + is(locked, lockState_cpu, "#1 Video element locked the cpu"); count_cpu++; } else if (topic == "screen") { - is(locked, lockState_screen, "Video element locked the screen - paused"); + is(locked, lockState_screen, "#1 Video element locked the screen"); count_screen++; } + if (count_cpu == 1 && count_screen == 1) { + info("#1 Both cpu and screen are locked"); + // The next step is to unlock the resource. + lockState_cpu = false; + lockState_screen = false; + video.pause(); + startDate = new Date(); + } + if (count_cpu == 2 && count_screen == 2) { var diffDate = (new Date() - startDate); - ok(diffDate > 200, "There was at least 200 milliseconds between the stop and the wakelock release"); + ok(diffDate > 200, "#1 There was at least 200 milliseconds between the stop and the wakelock release"); content.removeChild(video); navigator.mozPower.removeWakeLockListener(testVideoPlayPauseListener); runTests(); } } navigator.mozPower.addWakeLockListener(testVideoPlayPauseListener); video.play(); } function testVideoPlay() { + info("#2 testVideoPlay"); + var lockState_cpu = true; var lockState_screen = true; var count_cpu = 0; var count_screen = 0; var content = document.getElementById('content'); var video = document.createElement('video'); @@ -81,34 +87,37 @@ function testVideoPlay() { content.appendChild(video); var startDate; video.addEventListener('progress', function() { startDate = new Date(); }); function testVideoPlayListener(topic, state) { + info("#2 topic=" + topic + ", state=" + state); + var locked = state == "locked-foreground" || state == "locked-background"; if (topic == "cpu") { - is(locked, lockState_cpu, "Video element locked the cpu - paused"); + is(locked, lockState_cpu, "#2 Video element locked the cpu"); count_cpu++; } else if (topic == "screen") { - is(locked, lockState_screen, "Video element locked the screen - paused"); + is(locked, lockState_screen, "#2 Video element locked the screen"); count_screen++; } if (count_cpu == 1 && count_screen == 1) { + info("#2 Both cpu and screen are locked"); // The next step is to unlock the resource. lockState_cpu = false; lockState_screen = false; } else if (count_cpu == 2 && count_screen == 2) { var diffDate = (new Date() - startDate); - ok(diffDate > 200, "There was at least milliseconds between the stop and the wakelock release"); + ok(diffDate > 200, "#2 There was at least milliseconds between the stop and the wakelock release"); content.removeChild(video); navigator.mozPower.removeWakeLockListener(testVideoPlayListener); runTests(); } } navigator.mozPower.addWakeLockListener(testVideoPlayListener);
--- a/dom/browser-element/BrowserElementParent.jsm +++ b/dom/browser-element/BrowserElementParent.jsm @@ -139,16 +139,18 @@ function BrowserElementParent(frameLoade defineDOMRequestMethod('getCanGoBack', 'get-can-go-back'); defineDOMRequestMethod('getCanGoForward', 'get-can-go-forward'); defineDOMRequestMethod('getContentDimensions', 'get-contentdimensions'); } defineMethod('addNextPaintListener', this._addNextPaintListener); defineMethod('removeNextPaintListener', this._removeNextPaintListener); + defineNoReturnMethod('setActive', this._setActive); + defineMethod('getActive', 'this._getActive'); let principal = this._frameElement.ownerDocument.nodePrincipal; let perm = Services.perms .testExactPermissionFromPrincipal(principal, "input-manage"); if (perm === Ci.nsIPermissionManager.ALLOW_ACTION) { defineMethod('setInputMethodActive', this._setInputMethodActive); } @@ -582,16 +584,24 @@ BrowserElementParent.prototype = { } }, _setVisible: function(visible) { this._sendAsyncMsg('set-visible', {visible: visible}); this._frameLoader.visible = visible; }, + _setActive: function(active) { + this._frameLoader.visible = active; + }, + + _getActive: function() { + return this._frameLoader.visible; + }, + _sendMouseEvent: function(type, x, y, button, clickCount, modifiers) { this._sendAsyncMsg("send-mouse-event", { "type": type, "x": x, "y": y, "button": button, "clickCount": clickCount, "modifiers": modifiers
--- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -3087,21 +3087,28 @@ CanvasRenderingContext2D::GetHitRegionRe */ struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor { typedef CanvasRenderingContext2D::ContextState ContextState; virtual void SetText(const char16_t* text, int32_t length, nsBidiDirection direction) { mFontgrp->UpdateUserFonts(); // ensure user font generation is current + // adjust flags for current direction run + uint32_t flags = mTextRunFlags; + if (direction & 1) { + flags |= gfxTextRunFactory::TEXT_IS_RTL; + } else { + flags &= ~gfxTextRunFactory::TEXT_IS_RTL; + } mTextRun = mFontgrp->MakeTextRun(text, length, mThebes, mAppUnitsPerDevPixel, - direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0); + flags); } virtual nscoord GetWidth() { gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0, mTextRun->GetLength(), mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS : @@ -3117,34 +3124,38 @@ struct MOZ_STACK_CLASS CanvasBidiProcess } return NSToCoordRound(textRunMetrics.mAdvanceWidth); } virtual void DrawText(nscoord xOffset, nscoord width) { gfxPoint point = mPt; - point.x += xOffset; + bool rtl = mTextRun->IsRightToLeft(); + bool verticalRun = mTextRun->IsVertical(); + + gfxFloat& inlineCoord = verticalRun ? point.y : point.x; + inlineCoord += xOffset; // offset is given in terms of left side of string - if (mTextRun->IsRightToLeft()) { + if (rtl) { // Bug 581092 - don't use rounded pixel width to advance to // right-hand end of run, because this will cause different // glyph positioning for LTR vs RTL drawing of the same // glyph string on OS X and DWrite where textrun widths may // involve fractional pixels. gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0, mTextRun->GetLength(), mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS : gfxFont::LOOSE_INK_EXTENTS, mThebes, nullptr); - point.x += textRunMetrics.mAdvanceWidth; + inlineCoord += textRunMetrics.mAdvanceWidth; // old code was: // point.x += width * mAppUnitsPerDevPixel; // TODO: restore this if/when we move to fractional coords // throughout the text layout process } uint32_t numRuns; const gfxTextRun::GlyphRun *runs = mTextRun->GetGlyphRuns(&numRuns); @@ -3153,16 +3164,25 @@ struct MOZ_STACK_CLASS CanvasBidiProcess Point baselineOrigin = Point(point.x * devUnitsPerAppUnit, point.y * devUnitsPerAppUnit); float advanceSum = 0; mCtx->EnsureTarget(); for (uint32_t c = 0; c < numRuns; c++) { gfxFont *font = runs[c].mFont; + + bool verticalFont = + runs[c].mOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT; + + const float& baselineOriginInline = + verticalFont ? baselineOrigin.y : baselineOrigin.x; + const float& baselineOriginBlock = + verticalFont ? baselineOrigin.x : baselineOrigin.y; + uint32_t endRun = 0; if (c + 1 < numRuns) { endRun = runs[c + 1].mCharacterOffset; } else { endRun = mTextRun->GetLength(); } const gfxTextRun::CompressedGlyph *glyphs = mTextRun->GetCharacterGlyphs(); @@ -3170,70 +3190,100 @@ struct MOZ_STACK_CLASS CanvasBidiProcess RefPtr<ScaledFont> scaledFont = gfxPlatform::GetPlatform()->GetScaledFontForFont(mCtx->mTarget, font); if (!scaledFont) { // This can occur when something switched DirectWrite off. return; } + AutoRestoreTransform sidewaysRestore; + if (runs[c].mOrientation == + gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT) { + sidewaysRestore.Init(mCtx->mTarget); + // TODO: The baseline adjustment here is kinda ad-hoc; eventually + // perhaps we should check for horizontal and vertical baseline data + // in the font, and adjust accordingly. + // (The same will be true for HTML text layout.) + const gfxFont::Metrics& metrics = mTextRun->GetFontGroup()-> + GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal); + mCtx->mTarget->SetTransform(mCtx->mTarget->GetTransform().Copy(). + PreTranslate(baselineOrigin). // translate origin for rotation + PreRotate(gfx::Float(M_PI / 2.0)). // turn 90deg clockwise + PreTranslate(-baselineOrigin). // undo the translation + PreTranslate(Point(0, metrics.emAscent - metrics.emDescent) / 2)); + // and offset the (alphabetic) baseline of the + // horizontally-shaped text from the (centered) + // default baseline used for vertical + } + RefPtr<GlyphRenderingOptions> renderingOptions = font->GetGlyphRenderingOptions(); GlyphBuffer buffer; std::vector<Glyph> glyphBuf; + // TODO: + // This more-or-less duplicates the code found in gfxTextRun::Draw + // and the gfxFont methods that uses (Draw, DrawGlyphs, DrawOneGlyph); + // it would be nice to refactor and share that code. for (uint32_t i = runs[c].mCharacterOffset; i < endRun; i++) { Glyph newGlyph; + + float& inlinePos = + verticalFont ? newGlyph.mPosition.y : newGlyph.mPosition.x; + float& blockPos = + verticalFont ? newGlyph.mPosition.x : newGlyph.mPosition.y; + if (glyphs[i].IsSimpleGlyph()) { newGlyph.mIndex = glyphs[i].GetSimpleGlyph(); - if (mTextRun->IsRightToLeft()) { - newGlyph.mPosition.x = baselineOrigin.x - advanceSum - + if (rtl) { + inlinePos = baselineOriginInline - advanceSum - glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit; } else { - newGlyph.mPosition.x = baselineOrigin.x + advanceSum; + inlinePos = baselineOriginInline + advanceSum; } - newGlyph.mPosition.y = baselineOrigin.y; + blockPos = baselineOriginBlock; advanceSum += glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit; glyphBuf.push_back(newGlyph); continue; } if (!glyphs[i].GetGlyphCount()) { continue; } - gfxTextRun::DetailedGlyph *detailedGlyphs = - mTextRun->GetDetailedGlyphs(i); + const gfxTextRun::DetailedGlyph *d = mTextRun->GetDetailedGlyphs(i); if (glyphs[i].IsMissing()) { newGlyph.mIndex = 0; - if (mTextRun->IsRightToLeft()) { - newGlyph.mPosition.x = baselineOrigin.x - advanceSum - - detailedGlyphs[0].mAdvance * devUnitsPerAppUnit; + if (rtl) { + inlinePos = baselineOriginInline - advanceSum - + d->mAdvance * devUnitsPerAppUnit; } else { - newGlyph.mPosition.x = baselineOrigin.x + advanceSum; + inlinePos = baselineOriginInline + advanceSum; } - newGlyph.mPosition.y = baselineOrigin.y; - advanceSum += detailedGlyphs[0].mAdvance * devUnitsPerAppUnit; + blockPos = baselineOriginBlock; + advanceSum += d->mAdvance * devUnitsPerAppUnit; glyphBuf.push_back(newGlyph); continue; } - for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++) { - newGlyph.mIndex = detailedGlyphs[c].mGlyphID; - if (mTextRun->IsRightToLeft()) { - newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit - - advanceSum - detailedGlyphs[c].mAdvance * devUnitsPerAppUnit; + for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++, d++) { + newGlyph.mIndex = d->mGlyphID; + if (rtl) { + inlinePos = baselineOriginInline - advanceSum - + d->mAdvance * devUnitsPerAppUnit; } else { - newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit + advanceSum; + inlinePos = baselineOriginInline + advanceSum; } - newGlyph.mPosition.y = baselineOrigin.y + detailedGlyphs[c].mYOffset * devUnitsPerAppUnit; + inlinePos += d->mXOffset * devUnitsPerAppUnit; + blockPos = baselineOriginBlock + d->mYOffset * devUnitsPerAppUnit; glyphBuf.push_back(newGlyph); - advanceSum += detailedGlyphs[c].mAdvance * devUnitsPerAppUnit; + advanceSum += d->mAdvance * devUnitsPerAppUnit; } } if (!glyphBuf.size()) { // This may happen for glyph runs for a 0 size font. continue; } @@ -3298,16 +3348,19 @@ struct MOZ_STACK_CLASS CanvasBidiProcess CanvasRenderingContext2D::TextDrawOperation mOp; // context state ContextState *mState; // union of bounding boxes of all runs, needed for shadows gfxRect mBoundingBox; + // flags to use when creating textrun, based on CSS style + uint32_t mTextRunFlags; + // true iff the bounding box should be measured bool mDoMeasureBoundingBox; }; nsresult CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText, float aX, float aY, @@ -3337,19 +3390,20 @@ CanvasRenderingContext2D::DrawOrMeasureT // replace all the whitespace characters with U+0020 SPACE nsAutoString textToDraw(aRawText); TextReplaceWhitespaceCharacters(textToDraw); // for now, default to ltr if not in doc bool isRTL = false; + nsRefPtr<nsStyleContext> canvasStyle; if (mCanvasElement && mCanvasElement->IsInDoc()) { // try to find the closest context - nsRefPtr<nsStyleContext> canvasStyle = + canvasStyle = nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement, nullptr, presShell); if (!canvasStyle) { return NS_ERROR_FAILURE; } isRTL = canvasStyle->StyleVisibility()->mDirection == @@ -3374,16 +3428,24 @@ CanvasRenderingContext2D::DrawOrMeasureT const ContextState &state = CurrentState(); // This is only needed to know if we can know the drawing bounding box easily. bool doCalculateBounds = NeedToCalculateBounds(); CanvasBidiProcessor processor; + // If we don't have a style context, we can't set up vertical-text flags + // (for now, at least; perhaps we need new Canvas API to control this). + processor.mTextRunFlags = canvasStyle ? + nsLayoutUtils::GetTextRunFlagsForStyle(canvasStyle, + canvasStyle->StyleFont(), + canvasStyle->StyleText(), + 0) : 0; + GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr); processor.mPt = gfxPoint(aX, aY); processor.mThebes = new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()); // If we don't have a target then we don't have a transform. A target won't // be needed in the case where we're measuring the text size. This allows // to avoid creating a target if it's only being used to measure text sizes. @@ -3440,17 +3502,19 @@ CanvasRenderingContext2D::DrawOrMeasureT anchorX = 1; } processor.mPt.x -= anchorX * totalWidth; // offset pt.y based on text baseline processor.mFontgrp->UpdateUserFonts(); // ensure user font generation is current const gfxFont::Metrics& fontMetrics = - processor.mFontgrp->GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal); // XXX vertical? + processor.mFontgrp->GetFirstValidFont()->GetMetrics( + processor.mTextRun->IsVertical() ? gfxFont::eVertical : + gfxFont::eHorizontal); gfxFloat anchorY; switch (state.textBaseline) { case TextBaseline::HANGING: // fall through; best we can do with the information available case TextBaseline::TOP:
--- a/dom/tests/mochitest/general/mochitest.ini +++ b/dom/tests/mochitest/general/mochitest.ini @@ -1,10 +1,9 @@ [DEFAULT] -skip-if = e10s support-files = 497633.html file_MozEnteredDomFullscreen.html file_bug628069.html file_clonewrapper.html file_domWindowUtils_scrollbarSize.html file_frameElementWrapping.html file_interfaces.xml @@ -30,62 +29,62 @@ support-files = res6.resource^headers^ res7.resource res7.resource^headers^ res8.resource res8.resource^headers^ resource_timing.js [test_497898.html] -skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || toolkit == 'android' #Bug 931116, b2g desktop specific, initial triage +skip-if = ((buildapp == 'mulet' || buildapp == 'b2g') && toolkit != 'gonk') || toolkit == 'android' #Bug 931116, b2g desktop specific, initial triage [test_bug504220.html] [test_bug628069_1.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage [test_bug628069_2.html] [test_bug631440.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage [test_bug653364.html] [test_bug861217.html] [test_clientRects.html] [test_clipboard_events.html] -skip-if = buildapp == 'b2g' # b2g(clipboard undefined) b2g-debug(clipboard undefined) b2g-desktop(clipboard undefined) +skip-if = e10s || buildapp == 'b2g' # b2g(clipboard undefined) b2g-debug(clipboard undefined) b2g-desktop(clipboard undefined) [test_consoleAPI.html] [test_DOMMatrix.html] [test_domWindowUtils.html] [test_domWindowUtils_scrollXY.html] [test_domWindowUtils_scrollbarSize.html] [test_donottrack.html] skip-if = buildapp == 'mulet' [test_focus_legend_noparent.html] [test_focusrings.xul] -skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT +skip-if = e10s || buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT [test_for_of.html] [test_frameElementWrapping.html] [test_framedhistoryframes.html] [test_idleapi_permissions.html] -skip-if = buildapp == 'b2g' || buildapp == 'mulet' +skip-if = e10s || buildapp == 'b2g' || buildapp == 'mulet' [test_interfaces.html] skip-if = ((buildapp == 'mulet' || buildapp == 'b2g') && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage # [test_network_events.html] # Disable this test until bug 795711 is fixed. [test_offsets.html] [test_offsets.js] [test_outerHTML.html] [test_outerHTML.xhtml] skip-if = buildapp == 'mulet' [test_paste_selection.html] skip-if = buildapp == 'mulet' [test_picture_pref.html] [test_resource_timing.html] -skip-if = buildapp == 'b2g' || buildapp == 'mulet' # b2g(No clipboard) b2g-debug(No clipboard) b2g-desktop(No clipboard) +skip-if = buildapp == 'b2g' || buildapp == 'mulet' [test_resource_timing_cross_origin.html] skip-if = buildapp == 'b2g' || buildapp == 'mulet' [test_performance_now.html] [test_srcset_pref.html] [test_showModalDialog.html] -skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' #Don't run modal tests on Android # b2g(showmodaldialog) b2g-debug(showmodaldialog) b2g-desktop(showmodaldialog) +skip-if = e10s || buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' #Don't run modal tests on Android # b2g(showmodaldialog) b2g-debug(showmodaldialog) b2g-desktop(showmodaldialog) [test_stylesheetPI.html] [test_vibrator.html] skip-if = buildapp == 'mulet' || toolkit == 'android' #CRASH_SUTAGENT [test_windowProperties.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage [test_windowedhistoryframes.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
--- a/dom/tests/mochitest/general/test_resource_timing.html +++ b/dom/tests/mochitest/general/test_resource_timing.html @@ -15,21 +15,24 @@ https://bugzilla.mozilla.org/show_bug.cg <body> <pre id="test"> <script type="application/javascript"> SimpleTest.waitForExplicitFinish(); // Resource timing is prefed off by default, so we had to use this workaround -SpecialPowers.setBoolPref("dom.enable_resource_timing", true); -var subwindow = window.open("resource_timing_main_test.html"); +SpecialPowers.pushPrefEnv({"set": [["dom.enable_resource_timing", true]]}, start); +var subwindow = null; + +function start() { + subwindow = window.open("resource_timing_main_test.html"); +} function finishTests() { subwindow.close(); - SpecialPowers.setBoolPref("dom.enable_resource_timing", false); SimpleTest.finish(); } </script> </pre> </body> </html>
--- a/gfx/2d/Helpers.h +++ b/gfx/2d/Helpers.h @@ -9,16 +9,20 @@ #include "2D.h" namespace mozilla { namespace gfx { class AutoRestoreTransform { public: + AutoRestoreTransform() + { + } + explicit AutoRestoreTransform(DrawTarget *aTarget) : mDrawTarget(aTarget), mOldTransform(aTarget->GetTransform()) { } void Init(DrawTarget *aTarget) {
--- a/gfx/2d/Logging.h +++ b/gfx/2d/Logging.h @@ -5,16 +5,20 @@ #ifndef MOZILLA_GFX_LOGGING_H_ #define MOZILLA_GFX_LOGGING_H_ #include <string> #include <sstream> #include <stdio.h> +#ifdef MOZ_LOGGING +#include <prlog.h> +#endif + #if defined(MOZ_WIDGET_GONK) || defined(MOZ_WIDGET_ANDROID) #include "nsDebug.h" #endif #include "Point.h" #include "BaseRect.h" #include "Matrix.h" #include "mozilla/TypedEnum.h" @@ -24,30 +28,28 @@ // thing we need from windows.h, we just declare it here directly. // Note: the function's documented signature is // WINBASEAPI void WINAPI OutputDebugStringA(LPCSTR lpOutputString) // but if we don't include windows.h, the macros WINBASEAPI, WINAPI, and // LPCSTR are not defined, so we need to replace them with their expansions. extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA(const char* lpOutputString); #endif -#if defined(DEBUG) || defined(PR_LOGGING) -#include <prlog.h> - +#if defined(PR_LOGGING) extern GFX2D_API PRLogModuleInfo *GetGFX2DLog(); #endif namespace mozilla { namespace gfx { const int LOG_DEBUG = 1; const int LOG_WARNING = 2; const int LOG_CRITICAL = 3; -#if defined(DEBUG) || defined(PR_LOGGING) +#if defined(PR_LOGGING) inline PRLogModuleLevel PRLogLevelForLevel(int aLevel) { switch (aLevel) { case LOG_DEBUG: return PR_LOG_DEBUG; case LOG_WARNING: return PR_LOG_WARNING; }
--- a/gfx/angle/src/common/debug.h +++ b/gfx/angle/src/common/debug.h @@ -119,17 +119,17 @@ namespace gl // A macro that determines whether an object has a given runtime type. #if !defined(NDEBUG) && (!defined(_MSC_VER) || defined(_CPPRTTI)) && (!defined(__GNUC__) || __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3) || defined(__GXX_RTTI)) #define HAS_DYNAMIC_TYPE(type, obj) (dynamic_cast<type >(obj) != NULL) #else #define HAS_DYNAMIC_TYPE(type, obj) true #endif // A macro functioning as a compile-time assert to validate constant conditions -#if defined(_MSC_VER) && _MSC_VER >= 1600 +#if (defined(_MSC_VER) && _MSC_VER >= 1600) || (defined(__GNUC__) && (__GNUC__ > 4 || __GNUC_MINOR__ >= 3)) #define META_ASSERT_MSG(condition, msg) static_assert(condition, msg) #else #define META_ASSERT_CONCAT(a, b) a ## b #define META_ASSERT_CONCAT2(a, b) META_ASSERT_CONCAT(a, b) #define META_ASSERT_MSG(condition, msg) typedef int META_ASSERT_CONCAT2(COMPILE_TIME_ASSERT_, __LINE__)[static_cast<bool>(condition)?1:-1] #endif #define META_ASSERT(condition) META_ASSERT_MSG(condition, "compile time assertion failed.")
--- a/gfx/angle/src/libEGL/Display.cpp +++ b/gfx/angle/src/libEGL/Display.cpp @@ -9,16 +9,17 @@ // [EGL 1.4] section 2.1.2 page 3. #include "libEGL/Display.h" #include <algorithm> #include <map> #include <vector> #include <sstream> +#include <iterator> #include "common/debug.h" #include "common/mathutil.h" #include "libGLESv2/main.h" #include "libGLESv2/Context.h" #include "libGLESv2/renderer/SwapChain.h" #include "libEGL/main.h"
--- a/gfx/angle/src/libEGL/moz.build +++ b/gfx/angle/src/libEGL/moz.build @@ -49,14 +49,13 @@ DEFINES['GL_GLEXT_PROTOTYPES'] = "" DEFINES['EGLAPI'] = "" # ANGLE uses the STL, so we can't use our derpy STL wrappers. DISABLE_STL_WRAPPING = True LOCAL_INCLUDES += [ '../../include', '../../src' ] USE_LIBS += [ 'libGLESv2' ] -EXTRA_DSO_LDOPTS += [ '../libGLESv2/libGLESv2.lib' ] SharedLibrary('libEGL') RCFILE = SRCDIR + '/libEGL.rc' DEFFILE = SRCDIR + '/libEGL.def'
--- a/gfx/angle/src/libGLESv2.gypi +++ b/gfx/angle/src/libGLESv2.gypi @@ -41,16 +41,17 @@ 'common/utilities.cpp', 'common/utilities.h', 'common/version.h', 'libGLESv2/BinaryStream.h', 'libGLESv2/Buffer.cpp', 'libGLESv2/Buffer.h', 'libGLESv2/Caps.cpp', 'libGLESv2/Caps.h', + 'libGLESv2/Constants.h', 'libGLESv2/Context.cpp', 'libGLESv2/Context.h', 'libGLESv2/Error.cpp', 'libGLESv2/Error.h', 'libGLESv2/Fence.cpp', 'libGLESv2/Fence.h', 'libGLESv2/Float16ToFloat32.cpp', 'libGLESv2/Framebuffer.cpp', @@ -82,17 +83,16 @@ 'libGLESv2/Uniform.cpp', 'libGLESv2/Uniform.h', 'libGLESv2/VertexArray.cpp', 'libGLESv2/VertexArray.h', 'libGLESv2/VertexAttribute.cpp', 'libGLESv2/VertexAttribute.h', 'libGLESv2/angletypes.cpp', 'libGLESv2/angletypes.h', - 'libGLESv2/constants.h', 'libGLESv2/formatutils.cpp', 'libGLESv2/formatutils.h', 'libGLESv2/libGLESv2.cpp', 'libGLESv2/libGLESv2.def', 'libGLESv2/libGLESv2.rc', 'libGLESv2/main.cpp', 'libGLESv2/main.h', 'libGLESv2/queryconversions.cpp',
--- a/gfx/angle/src/libGLESv2/Context.cpp +++ b/gfx/angle/src/libGLESv2/Context.cpp @@ -28,16 +28,17 @@ #include "libGLESv2/VertexArray.h" #include "libGLESv2/Sampler.h" #include "libGLESv2/validationES.h" #include "libGLESv2/TransformFeedback.h" #include "libEGL/Surface.h" #include <sstream> +#include <iterator> namespace gl { Context::Context(int clientVersion, const gl::Context *shareContext, rx::Renderer *renderer, bool notifyResets, bool robustAccess) : mRenderer(renderer) { ASSERT(robustAccess == false); // Unimplemented @@ -336,17 +337,17 @@ void Context::deleteRenderbuffer(GLuint } void Context::deleteFenceSync(GLsync fenceSync) { // The spec specifies the underlying Fence object is not deleted until all current // wait commands finish. However, since the name becomes invalid, we cannot query the fence, // and since our API is currently designed for being called from a single thread, we can delete // the fence immediately. - mResourceManager->deleteFenceSync(reinterpret_cast<GLuint>(fenceSync)); + mResourceManager->deleteFenceSync(reinterpret_cast<uintptr_t>(fenceSync)); } void Context::deleteVertexArray(GLuint vertexArray) { auto vertexArrayObject = mVertexArrayMap.find(vertexArray); if (vertexArrayObject != mVertexArrayMap.end()) { @@ -442,17 +443,17 @@ Texture *Context::getTexture(GLuint hand Renderbuffer *Context::getRenderbuffer(GLuint handle) { return mResourceManager->getRenderbuffer(handle); } FenceSync *Context::getFenceSync(GLsync handle) const { - return mResourceManager->getFenceSync(reinterpret_cast<GLuint>(handle)); + return mResourceManager->getFenceSync(reinterpret_cast<uintptr_t>(handle)); } VertexArray *Context::getVertexArray(GLuint handle) const { auto vertexArray = mVertexArrayMap.find(handle); if (vertexArray == mVertexArrayMap.end()) {
--- a/gfx/angle/src/libGLESv2/Framebuffer.h +++ b/gfx/angle/src/libGLESv2/Framebuffer.h @@ -7,17 +7,17 @@ // Framebuffer.h: Defines the gl::Framebuffer class. Implements GL framebuffer // objects and related functionality. [OpenGL ES 2.0.24] section 4.4 page 105. #ifndef LIBGLESV2_FRAMEBUFFER_H_ #define LIBGLESV2_FRAMEBUFFER_H_ #include "common/angleutils.h" #include "common/RefCountObject.h" -#include "constants.h" +#include "Constants.h" namespace rx { class Renderer; } namespace gl {
--- a/gfx/angle/src/libGLESv2/Texture.h +++ b/gfx/angle/src/libGLESv2/Texture.h @@ -9,17 +9,17 @@ // related functionality. [OpenGL ES 2.0.24] section 3.7 page 63. #ifndef LIBGLESV2_TEXTURE_H_ #define LIBGLESV2_TEXTURE_H_ #include "common/debug.h" #include "common/RefCountObject.h" #include "libGLESv2/angletypes.h" -#include "libGLESv2/constants.h" +#include "libGLESv2/Constants.h" #include "libGLESv2/renderer/TextureImpl.h" #include "libGLESv2/Caps.h" #include "angle_gl.h" #include <vector> namespace egl
--- a/gfx/angle/src/libGLESv2/VertexArray.h +++ b/gfx/angle/src/libGLESv2/VertexArray.h @@ -9,17 +9,17 @@ // together to form a vertex array object. All state related to the definition of data used // by the vertex processor is encapsulated in a vertex array object. // #ifndef LIBGLESV2_VERTEXARRAY_H_ #define LIBGLESV2_VERTEXARRAY_H_ #include "common/RefCountObject.h" -#include "libGLESv2/constants.h" +#include "libGLESv2/Constants.h" #include "libGLESv2/VertexAttribute.h" #include <vector> namespace rx { class Renderer; class VertexArrayImpl;
--- a/gfx/angle/src/libGLESv2/angletypes.h +++ b/gfx/angle/src/libGLESv2/angletypes.h @@ -4,17 +4,17 @@ // found in the LICENSE file. // // angletypes.h : Defines a variety of structures and enum types that are used throughout libGLESv2 #ifndef LIBGLESV2_ANGLETYPES_H_ #define LIBGLESV2_ANGLETYPES_H_ -#include "libGLESv2/constants.h" +#include "libGLESv2/Constants.h" #include "common/RefCountObject.h" namespace gl { class Buffer; class ProgramBinary; struct VertexAttribute; struct VertexAttribCurrentValueData;
--- a/gfx/angle/src/libGLESv2/moz.build +++ b/gfx/angle/src/libGLESv2/moz.build @@ -112,17 +112,16 @@ UNIFIED_SOURCES += [ 'renderer/d3d/d3d9/renderer9_utils.cpp', 'renderer/d3d/d3d9/RenderTarget9.cpp', 'renderer/d3d/d3d9/ShaderExecutable9.cpp', 'renderer/d3d/d3d9/SwapChain9.cpp', 'renderer/d3d/d3d9/TextureStorage9.cpp', 'renderer/d3d/d3d9/VertexBuffer9.cpp', 'renderer/d3d/d3d9/VertexDeclarationCache.cpp', 'renderer/d3d/DynamicHLSL.cpp', - 'renderer/d3d/HLSLCompiler.cpp', 'renderer/d3d/ImageD3D.cpp', 'renderer/d3d/IndexBuffer.cpp', 'renderer/d3d/IndexDataManager.cpp', 'renderer/d3d/MemoryBuffer.cpp', 'renderer/d3d/ShaderD3D.cpp', 'renderer/d3d/TextureD3D.cpp', 'renderer/d3d/TextureStorage.cpp', 'renderer/d3d/TransformFeedbackD3D.cpp', @@ -143,16 +142,17 @@ UNIFIED_SOURCES += [ 'validationES2.cpp', 'validationES3.cpp', 'VertexArray.cpp', 'VertexAttribute.cpp', ] SOURCES += [ '../compiler/translator/glslang_lex.cpp', '../compiler/translator/glslang_tab.cpp', + 'renderer/d3d/HLSLCompiler.cpp', 'renderer/loadimageSSE2.cpp', ] SOURCES['renderer/loadimageSSE2.cpp'].flags += CONFIG['SSE2_FLAGS'] if CONFIG['MOZ_HAS_WINSDK_WITH_D3D']: UNIFIED_SOURCES += [ 'renderer/d3d/d3d11/Blit11.cpp', 'renderer/d3d/d3d11/Buffer11.cpp', 'renderer/d3d/d3d11/Clear11.cpp', @@ -208,22 +208,22 @@ DEFINES['EGLAPI'] = "" # ANGLE uses the STL, so we can't use our derpy STL wrappers. DISABLE_STL_WRAPPING = True LOCAL_INCLUDES += [ '../../include', '../../src' ] if CONFIG['MOZ_HAS_WINSDK_WITH_D3D']: - EXTRA_DSO_LDOPTS += [ 'd3d9.lib', 'dxguid.lib' ] + OS_LIBS += [ 'd3d9', 'dxguid' ] else: EXTRA_DSO_LDOPTS += [ '\'%s/lib/%s/d3d9.lib\'' % (CONFIG['MOZ_DIRECTX_SDK_PATH'], CONFIG['MOZ_D3D_CPU_SUFFIX']), '\'%s/lib/%s/dxguid.lib\'' % (CONFIG['MOZ_DIRECTX_SDK_PATH'], CONFIG['MOZ_D3D_CPU_SUFFIX']), ] SharedLibrary('libGLESv2') RCFILE = SRCDIR + '/libGLESv2.rc' DEFFILE = SRCDIR + '/libGLESv2.def' -DEFINES['ANGLE_PRELOADED_D3DCOMPILER_MODULE_NAMES'] = '{ TEXT("d3dcompiler_47.dll"), TEXT("d3dcompiler_46.dll"), TEXT("d3dcompiler_43.dll") }' +SOURCES['renderer/d3d/HLSLCompiler.cpp'].flags += ['-DANGLE_PRELOADED_D3DCOMPILER_MODULE_NAMES=\'{ TEXT("d3dcompiler_47.dll"), TEXT("d3dcompiler_46.dll"), TEXT("d3dcompiler_43.dll") }\'']
--- a/gfx/angle/src/libGLESv2/renderer/copyimage.cpp +++ b/gfx/angle/src/libGLESv2/renderer/copyimage.cpp @@ -1,17 +1,17 @@ // // Copyright (c) 2013 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // copyimage.cpp: Defines image copying functions -#include "libGLESv2/renderer/copyImage.h" +#include "libGLESv2/renderer/copyimage.h" namespace rx { void CopyBGRA8ToRGBA8(const uint8_t *source, uint8_t *dest) { uint32_t argb = *reinterpret_cast<const uint32_t*>(source); *reinterpret_cast<uint32_t*>(dest) = (argb & 0xFF00FF00) | // Keep alpha and green
--- a/gfx/angle/src/libGLESv2/renderer/copyimage.inl +++ b/gfx/angle/src/libGLESv2/renderer/copyimage.inl @@ -19,14 +19,14 @@ template <typename destType, typename co inline void WriteColor(const uint8_t *source, uint8_t *dest) { destType::writeColor(reinterpret_cast<destType*>(dest), reinterpret_cast<const gl::Color<colorDataType>*>(source)); } template <typename sourceType, typename destType, typename colorDataType> inline void CopyPixel(const uint8_t *source, uint8_t *dest) { - colorType temp; + colorDataType temp; ReadColor<sourceType, colorDataType>(source, &temp); WriteColor<destType, colorDataType>(&temp, dest); } }
--- a/gfx/angle/src/libGLESv2/renderer/d3d/DynamicHLSL.h +++ b/gfx/angle/src/libGLESv2/renderer/d3d/DynamicHLSL.h @@ -5,17 +5,17 @@ // // DynamicHLSL.h: Interface for link and run-time HLSL generation // #ifndef LIBGLESV2_RENDERER_DYNAMIC_HLSL_H_ #define LIBGLESV2_RENDERER_DYNAMIC_HLSL_H_ #include "common/angleutils.h" -#include "libGLESv2/constants.h" +#include "libGLESv2/Constants.h" #include "angle_gl.h" #include <vector> #include <map> namespace rx {
--- a/gfx/angle/src/libGLESv2/renderer/d3d/TextureD3D.cpp +++ b/gfx/angle/src/libGLESv2/renderer/d3d/TextureD3D.cpp @@ -124,17 +124,17 @@ bool TextureD3D::subImage(GLint xoffset, GLenum format, GLenum type, const gl::PixelUnpackState &unpack, const void *pixels, Image *image) { const void *pixelData = pixels; // CPU readback & copy where direct GPU copy is not supported if (unpack.pixelBuffer.id() != 0) { gl::Buffer *pixelBuffer = unpack.pixelBuffer.get(); - unsigned int offset = reinterpret_cast<unsigned int>(pixels); + uintptr_t offset = reinterpret_cast<uintptr_t>(pixels); // TODO: setImage/subImage is the only place outside of renderer that asks for a buffers raw data. // This functionality should be moved into renderer and the getData method of BufferImpl removed. const void *bufferData = pixelBuffer->getImplementation()->getData(); pixelData = static_cast<const unsigned char *>(bufferData) + offset; } if (pixelData != NULL) { @@ -178,17 +178,17 @@ bool TextureD3D::fastUnpackPixels(const { return true; } // In order to perform the fast copy through the shader, we must have the right format, and be able // to create a render target. ASSERT(mRenderer->supportsFastCopyBufferToTexture(sizedInternalFormat)); - unsigned int offset = reinterpret_cast<unsigned int>(pixels); + uintptr_t offset = reinterpret_cast<uintptr_t>(pixels); return mRenderer->fastCopyBufferToTexture(unpack, offset, destRenderTarget, sizedInternalFormat, type, destArea); } GLint TextureD3D::creationLevels(GLsizei width, GLsizei height, GLsizei depth) const { if ((gl::isPow2(width) && gl::isPow2(height) && gl::isPow2(depth)) || mRenderer->getRendererExtensions().textureNPOT) {
--- a/gfx/angle/src/libGLESv2/renderer/d3d/TextureD3D.h +++ b/gfx/angle/src/libGLESv2/renderer/d3d/TextureD3D.h @@ -6,17 +6,17 @@ // TextureD3D.h: Implementations of the Texture interfaces shared betweeen the D3D backends. #ifndef LIBGLESV2_RENDERER_TEXTURED3D_H_ #define LIBGLESV2_RENDERER_TEXTURED3D_H_ #include "libGLESv2/renderer/TextureImpl.h" #include "libGLESv2/angletypes.h" -#include "libGLESv2/constants.h" +#include "libGLESv2/Constants.h" namespace gl { class Framebuffer; } namespace rx {
--- a/gfx/angle/src/libGLESv2/validationES.cpp +++ b/gfx/angle/src/libGLESv2/validationES.cpp @@ -1641,17 +1641,17 @@ bool ValidateDrawElements(Context *conte return false; } // Use max index to validate if our vertex buffers are large enough for the pull. // TODO: offer fast path, with disabled index validation. // TODO: also disable index checking on back-ends that are robust to out-of-range accesses. if (elementArrayBuffer) { - unsigned int offset = reinterpret_cast<unsigned int>(indices); + uintptr_t offset = reinterpret_cast<uintptr_t>(indices); if (!elementArrayBuffer->getIndexRangeCache()->findRange(type, offset, count, indexRangeOut, NULL)) { const void *dataPointer = elementArrayBuffer->getImplementation()->getData(); const uint8_t *offsetPointer = static_cast<const uint8_t *>(dataPointer) + offset; *indexRangeOut = rx::IndexRangeCache::ComputeRange(type, offsetPointer, count); } } else
--- a/gfx/angle/src/third_party/trace_event/trace_event.h +++ b/gfx/angle/src/third_party/trace_event/trace_event.h @@ -582,17 +582,17 @@ const int zeroNumArgs = 0; const unsigned long long noEventId = 0; // TraceID encapsulates an ID that can either be an integer or pointer. Pointers // are mangled with the Process ID so that they are unlikely to collide when the // same pointer is used on different processes. class TraceID { public: explicit TraceID(const void* id, unsigned char* flags) : - m_data(static_cast<unsigned long long>(reinterpret_cast<unsigned long>(id))) + m_data(static_cast<unsigned long long>(reinterpret_cast<uintptr_t>(id))) { *flags |= TRACE_EVENT_FLAG_MANGLE_ID; } explicit TraceID(unsigned long long id, unsigned char* flags) : m_data(id) { (void)flags; } explicit TraceID(unsigned long id, unsigned char* flags) : m_data(id) { (void)flags; } explicit TraceID(unsigned int id, unsigned char* flags) : m_data(id) { (void)flags; } explicit TraceID(unsigned short id, unsigned char* flags) : m_data(id) { (void)flags; } explicit TraceID(unsigned char id, unsigned char* flags) : m_data(id) { (void)flags; } @@ -783,44 +783,13 @@ private: struct Data { const unsigned char* categoryEnabled; const char* name; }; Data* m_pdata; Data m_data; }; -// TraceEventSamplingStateScope records the current sampling state -// and sets a new sampling state. When the scope exists, it restores -// the sampling state having recorded. -template<size_t BucketNumber> -class SamplingStateScope { -public: - SamplingStateScope(const char* categoryAndName) - { - m_previousState = SamplingStateScope<BucketNumber>::current(); - SamplingStateScope<BucketNumber>::set(categoryAndName); - } - - ~SamplingStateScope() - { - SamplingStateScope<BucketNumber>::set(m_previousState); - } - - // FIXME: Make load/store to traceSamplingState[] thread-safe and atomic. - static inline const char* current() - { - return reinterpret_cast<const char*>(*gl::traceSamplingState[BucketNumber]); - } - static inline void set(const char* categoryAndName) - { - *gl::traceSamplingState[BucketNumber] = reinterpret_cast<long>(const_cast<char*>(categoryAndName)); - } - -private: - const char* m_previousState; -}; - } // namespace TraceEvent } // namespace gl #endif
--- a/gfx/layers/RotatedBuffer.cpp +++ b/gfx/layers/RotatedBuffer.cpp @@ -723,16 +723,22 @@ RotatedContentBuffer::BorrowDrawTargetFo } if (result->GetBackendType() == BackendType::DIRECT2D || result->GetBackendType() == BackendType::DIRECT2D1_1) { drawPtr->SimplifyOutwardByArea(100 * 100); } if (aPaintState.mMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { MOZ_ASSERT(mDTBuffer && mDTBufferOnWhite); + if (!mDTBuffer || !mDTBufferOnWhite) { + // This can happen in release builds if allocating one of the two buffers + // failed. This is pretty bad and the reason for the failure is already + // reported through gfxCriticalError. + return nullptr; + } nsIntRegionRectIterator iter(*drawPtr); const nsIntRect *iterRect; while ((iterRect = iter.Next())) { mDTBuffer->FillRect(Rect(iterRect->x, iterRect->y, iterRect->width, iterRect->height), ColorPattern(Color(0.0, 0.0, 0.0, 1.0))); mDTBufferOnWhite->FillRect(Rect(iterRect->x, iterRect->y, iterRect->width, iterRect->height), ColorPattern(Color(1.0, 1.0, 1.0, 1.0))); }
--- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -620,17 +620,17 @@ nsEventStatus APZCTreeManager::ProcessTouchInput(MultiTouchInput& aInput, ScrollableLayerGuid* aOutTargetGuid) { if (aInput.mType == MultiTouchInput::MULTITOUCH_START) { // If we are in an overscrolled state and a second finger goes down, // ignore that second touch point completely. The touch-start for it is // dropped completely; subsequent touch events until the touch-end for it // will have this touch point filtered out. - if (mApzcForInputBlock && mApzcForInputBlock->IsOverscrolled()) { + if (mApzcForInputBlock && BuildOverscrollHandoffChain(mApzcForInputBlock)->HasOverscrolledApzc()) { if (mRetainedTouchIdentifier == -1) { mRetainedTouchIdentifier = mApzcForInputBlock->GetLastTouchIdentifier(); } return nsEventStatus_eConsumeNoDefault; } // NS_TOUCH_START event contains all active touches of the current // session thus resetting mTouchCount.
--- a/gfx/layers/apz/src/OverscrollHandoffState.cpp +++ b/gfx/layers/apz/src/OverscrollHandoffState.cpp @@ -64,16 +64,28 @@ void OverscrollHandoffChain::ForEachApzc(APZCMethod aMethod) const { MOZ_ASSERT(Length() > 0); for (uint32_t i = 0; i < Length(); ++i) { (mChain[i]->*aMethod)(); } } +bool +OverscrollHandoffChain::AnyApzc(APZCPredicate aPredicate) const +{ + MOZ_ASSERT(Length() > 0); + for (uint32_t i = 0; i < Length(); ++i) { + if ((mChain[i]->*aPredicate)()) { + return true; + } + } + return false; +} + void OverscrollHandoffChain::FlushRepaints() const { ForEachApzc(&AsyncPanZoomController::FlushRepaintForOverscrollHandoff); } void OverscrollHandoffChain::CancelAnimations() const @@ -124,10 +136,17 @@ OverscrollHandoffChain::CanBePanned(cons if (mChain[j]->IsPannable()) { return true; } } return false; } +bool +OverscrollHandoffChain::HasOverscrolledApzc() const +{ + return AnyApzc(&AsyncPanZoomController::IsOverscrolled); +} + + } // namespace layers } // namespace mozilla
--- a/gfx/layers/apz/src/OverscrollHandoffState.h +++ b/gfx/layers/apz/src/OverscrollHandoffState.h @@ -100,21 +100,26 @@ public: // Snap back the APZC that is overscrolled on the subset of the chain from // |aStart| onwards, if any. void SnapBackOverscrolledApzc(const AsyncPanZoomController* aStart) const; // Determine whether the given APZC, or any APZC further in the chain, // has room to be panned. bool CanBePanned(const AsyncPanZoomController* aApzc) const; + + // Determine whether any APZC along this handoff chain is overscrolled. + bool HasOverscrolledApzc() const; private: std::vector<nsRefPtr<AsyncPanZoomController>> mChain; typedef void (AsyncPanZoomController::*APZCMethod)(); + typedef bool (AsyncPanZoomController::*APZCPredicate)() const; void ForEachApzc(APZCMethod aMethod) const; + bool AnyApzc(APZCPredicate aPredicate) const; }; /** * This class groups the state maintained during overscroll handoff. */ struct OverscrollHandoffState { OverscrollHandoffState(const OverscrollHandoffChain& aChain, const ScreenPoint& aPanDistance)
--- a/gfx/layers/d3d11/CompositorD3D11.cpp +++ b/gfx/layers/d3d11/CompositorD3D11.cpp @@ -548,17 +548,20 @@ CompositorD3D11::ClearRect(const gfx::Re mContext->VSSetShader(mAttachments->mVSQuadShader[MaskType::MaskNone], nullptr, 0); mContext->PSSetShader(mAttachments->mSolidColorShader[MaskType::MaskNone], nullptr, 0); mPSConstants.layerColor[0] = 0; mPSConstants.layerColor[1] = 0; mPSConstants.layerColor[2] = 0; mPSConstants.layerColor[3] = 0; - UpdateConstantBuffers(); + if (!UpdateConstantBuffers()) { + NS_WARNING("Failed to update shader constant buffers"); + return; + } mContext->Draw(4, 0); mContext->OMSetBlendState(mAttachments->mPremulBlendState, sBlendFactor, 0xFFFFFFFF); } void CompositorD3D11::DrawQuad(const gfx::Rect& aRect, @@ -734,17 +737,20 @@ CompositorD3D11::DrawQuad(const gfx::Rec mContext->OMSetBlendState(mAttachments->mComponentBlendState, sBlendFactor, 0xFFFFFFFF); restoreBlendMode = true; } break; default: NS_WARNING("Unknown shader type"); return; } - UpdateConstantBuffers(); + if (!UpdateConstantBuffers()) { + NS_WARNING("Failed to update shader constant buffers"); + return; + } mContext->Draw(4, 0); if (restoreBlendMode) { mContext->OMSetBlendState(mAttachments->mPremulBlendState, sBlendFactor, 0xFFFFFFFF); } } void @@ -961,33 +967,72 @@ CompositorD3D11::CreateShaders() byRef(mAttachments->mRGBAShader[MaskType::Mask3d])); if (FAILED(hr)) { return false; } return true; } -void +static +bool ShouldRecoverFromMapFailure(HRESULT hr, ID3D11Device* device) +{ + // XXX - it would be nice to use gfxCriticalError, but it needs to + // be made to work off the main thread first. + if (SUCCEEDED(hr)) { + return true; + } + if (hr == DXGI_ERROR_DEVICE_REMOVED) { + switch (device->GetDeviceRemovedReason()) { + case DXGI_ERROR_DEVICE_HUNG: + case DXGI_ERROR_DEVICE_REMOVED: + case DXGI_ERROR_DEVICE_RESET: + case DXGI_ERROR_DRIVER_INTERNAL_ERROR: + return true; + case DXGI_ERROR_INVALID_CALL: + default: + return false; + } + } + return false; +} + +bool CompositorD3D11::UpdateConstantBuffers() { + HRESULT hr; D3D11_MAPPED_SUBRESOURCE resource; - mContext->Map(mAttachments->mVSConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource); + + hr = mContext->Map(mAttachments->mVSConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource); + if (FAILED(hr)) { + if (ShouldRecoverFromMapFailure(hr, GetDevice())) { + return false; + } + MOZ_CRASH(); + } *(VertexShaderConstants*)resource.pData = mVSConstants; mContext->Unmap(mAttachments->mVSConstantBuffer, 0); - mContext->Map(mAttachments->mPSConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource); + + hr = mContext->Map(mAttachments->mPSConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource); + if (FAILED(hr)) { + if (ShouldRecoverFromMapFailure(hr, GetDevice())) { + return false; + } + MOZ_CRASH(); + } *(PixelShaderConstants*)resource.pData = mPSConstants; mContext->Unmap(mAttachments->mPSConstantBuffer, 0); ID3D11Buffer *buffer = mAttachments->mVSConstantBuffer; mContext->VSSetConstantBuffers(0, 1, &buffer); buffer = mAttachments->mPSConstantBuffer; mContext->PSSetConstantBuffers(0, 1, &buffer); + return true; } void CompositorD3D11::SetSamplerForFilter(Filter aFilter) { ID3D11SamplerState *sampler; switch (aFilter) { default:
--- a/gfx/layers/d3d11/CompositorD3D11.h +++ b/gfx/layers/d3d11/CompositorD3D11.h @@ -143,17 +143,17 @@ public: ID3D11DeviceContext* GetDC() { return mContext; } private: // ensure mSize is up to date with respect to mWidget void EnsureSize(); void VerifyBufferSize(); void UpdateRenderTarget(); bool CreateShaders(); - void UpdateConstantBuffers(); + bool UpdateConstantBuffers(); void SetSamplerForFilter(gfx::Filter aFilter); void SetPSForEffect(Effect *aEffect, MaskType aMaskType, gfx::SurfaceFormat aFormat); void PaintToTarget(); virtual gfx::IntSize GetWidgetSize() const MOZ_OVERRIDE { return gfx::ToIntSize(mSize); } RefPtr<ID3D11DeviceContext> mContext; RefPtr<ID3D11Device> mDevice;
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp +++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp @@ -1607,16 +1607,65 @@ protected: SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID); SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1); SetScrollableFrameMetrics(layers[5], FrameMetrics::START_SCROLL_ID + 1); SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2); SetScrollableFrameMetrics(layers[6], FrameMetrics::START_SCROLL_ID + 3); } }; +// A version of ApzcPan() that routes the pan through the tree manager, +// so that the tree manager has the appropriate state for testing. +static void +ApzctmPan(APZCTreeManager* aTreeManager, + int& aTime, + int aTouchStartY, + int aTouchEndY, + bool aKeepFingerDown = false) +{ + // TODO: Reuse some code between this and ApzcPan(). + + // Reduce the touch start tolerance to a tiny value. + // We can't do what ApzcPan() does to overcome the tolerance (send the + // touch-start at (aTouchStartY + some_large_value)) because the tree manager + // does hit testing based on the touch-start coordinates, and a different + // APZC than the one we intend might be hit. + SCOPED_GFX_PREF(APZTouchStartTolerance, float, 1.0f / 1000.0f); + const int OVERCOME_TOUCH_TOLERANCE = 1; + + const int TIME_BETWEEN_TOUCH_EVENT = 100; + + // Make sure the move is large enough to not be handled as a tap + MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime, TimeStamp(), 0); + mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchStartY), ScreenSize(0, 0), 0, 0)); + aTreeManager->ReceiveInputEvent(mti, nullptr); + + aTime += TIME_BETWEEN_TOUCH_EVENT; + + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime, TimeStamp(), 0); + mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchStartY + OVERCOME_TOUCH_TOLERANCE), ScreenSize(0, 0), 0, 0)); + aTreeManager->ReceiveInputEvent(mti, nullptr); + + aTime += TIME_BETWEEN_TOUCH_EVENT; + + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime, TimeStamp(), 0); + mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchEndY), ScreenSize(0, 0), 0, 0)); + aTreeManager->ReceiveInputEvent(mti, nullptr); + + aTime += TIME_BETWEEN_TOUCH_EVENT; + + if (!aKeepFingerDown) { + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime, TimeStamp(), 0); + mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchEndY), ScreenSize(0, 0), 0, 0)); + aTreeManager->ReceiveInputEvent(mti, nullptr); + } + + aTime += TIME_BETWEEN_TOUCH_EVENT; +} + class APZHitTestingTester : public APZCTreeManagerTester { protected: Matrix4x4 transformToApzc; Matrix4x4 transformToGecko; already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint) { nsRefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(aPoint, nullptr); if (hit) { @@ -2184,16 +2233,53 @@ TEST_F(APZOverscrollHandoffTester, Layer // Make sure things have scrolled according to the handoff chain in // place at the time the touch-start of the second pan was queued. EXPECT_EQ(0, childApzc->GetFrameMetrics().GetScrollOffset().y); EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y); EXPECT_EQ(-10, middleApzc->GetFrameMetrics().GetScrollOffset().y); } +// Test that putting a second finger down on an APZC while a down-chain APZC +// is overscrolled doesn't result in being stuck in overscroll. +TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1073250) { + // Enable overscrolling. + SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); + + CreateOverscrollHandoffLayerTree1(); + + TestAsyncPanZoomController* child = ApzcOf(layers[1]); + + // Pan, causing the parent APZC to overscroll. + int time = 0; + ApzctmPan(manager, time, 10, 40, true /* keep finger down */); + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + // Put a second finger down. + MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); + // Use the same touch identifier for the first touch (0) as ApzctmPan(). (A bit hacky.) + secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0)); + secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0)); + manager->ReceiveInputEvent(secondFingerDown, nullptr); + + // Release the fingers. + MultiTouchInput fingersUp = secondFingerDown; + fingersUp.mType = MultiTouchInput::MULTITOUCH_END; + manager->ReceiveInputEvent(fingersUp, nullptr); + + // Allow any animations to run their course. + child->AdvanceAnimationsUntilEnd(testStartTime); + rootApzc->AdvanceAnimationsUntilEnd(testStartTime); + + // Make sure nothing is overscrolled. + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); +} + // Here we test that if two flings are happening simultaneously, overscroll // is handed off correctly for each. TEST_F(APZOverscrollHandoffTester, SimultaneousFlings) { // Set up an initial APZC tree. CreateOverscrollHandoffLayerTree3(); TestAsyncPanZoomController* parent1 = ApzcOf(layers[1]); TestAsyncPanZoomController* child1 = ApzcOf(layers[2]);
--- a/image/decoders/nsPNGDecoder.cpp +++ b/image/decoders/nsPNGDecoder.cpp @@ -1,31 +1,26 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "ImageLogging.h" -#include "nsPNGDecoder.h" - +#include "ImageLogging.h" // Must appear first +#include "gfxColor.h" +#include "gfxPlatform.h" +#include "nsColor.h" +#include "nsIInputStream.h" #include "nsMemory.h" +#include "nsPNGDecoder.h" #include "nsRect.h" - -#include "nsIInputStream.h" - +#include "nspr.h" +#include "png.h" #include "RasterImage.h" -#include "gfxColor.h" -#include "nsColor.h" - -#include "nspr.h" -#include "png.h" - -#include "gfxPlatform.h" #include <algorithm> namespace mozilla { namespace image { #ifdef PR_LOGGING static PRLogModuleInfo * GetPNGLog() @@ -41,17 +36,17 @@ GetPNGDecoderAccountingLog() { static PRLogModuleInfo *sPNGDecoderAccountingLog; if (!sPNGDecoderAccountingLog) sPNGDecoderAccountingLog = PR_NewLogModule("PNGDecoderAccounting"); return sPNGDecoderAccountingLog; } #endif -/* limit image dimensions (bug #251381, #591822, and #967656) */ +// Limit image dimensions (bug #251381, #591822, and #967656) #ifndef MOZ_PNG_MAX_DIMENSION # define MOZ_PNG_MAX_DIMENSION 32767 #endif // For size decodes #define WIDTH_OFFSET 16 #define HEIGHT_OFFSET (WIDTH_OFFSET + 4) #define BYTES_NEEDED_FOR_DIMENSIONS (HEIGHT_OFFSET + 4) @@ -64,33 +59,34 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameIn #ifdef PNG_APNG_SUPPORTED nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo) : mDispose(FrameBlender::kDisposeKeep) , mBlend(FrameBlender::kBlendOver) , mTimeout(0) { png_uint_16 delay_num, delay_den; - /* delay, in seconds is delay_num/delay_den */ + // delay, in seconds is delay_num/delay_den png_byte dispose_op; png_byte blend_op; delay_num = png_get_next_frame_delay_num(aPNG, aInfo); delay_den = png_get_next_frame_delay_den(aPNG, aInfo); dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo); blend_op = png_get_next_frame_blend_op(aPNG, aInfo); if (delay_num == 0) { mTimeout = 0; // SetFrameTimeout() will set to a minimum } else { if (delay_den == 0) delay_den = 100; // so says the APNG spec // Need to cast delay_num to float to have a proper division and // the result to int to avoid compiler warning - mTimeout = static_cast<int32_t>(static_cast<double>(delay_num) * 1000 / delay_den); + mTimeout = static_cast<int32_t>(static_cast<double>(delay_num) * + 1000 / delay_den); } if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) { mDispose = FrameBlender::kDisposeRestorePrevious; } else if (dispose_op == PNG_DISPOSE_OP_BACKGROUND) { mDispose = FrameBlender::kDisposeClear; } else { mDispose = FrameBlender::kDisposeKeep; @@ -126,17 +122,17 @@ nsPNGDecoder::~nsPNGDecoder() png_destroy_read_struct(&mPNG, mInfo ? &mInfo : nullptr, nullptr); if (mCMSLine) nsMemory::Free(mCMSLine); if (interlacebuf) nsMemory::Free(interlacebuf); if (mInProfile) { qcms_profile_release(mInProfile); - /* mTransform belongs to us only if mInProfile is non-null */ + // mTransform belongs to us only if mInProfile is non-null if (mTransform) qcms_transform_release(mTransform); } } // CreateFrame() is used for both simple and animated images void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset, int32_t width, int32_t height, @@ -190,55 +186,57 @@ void nsPNGDecoder::EndImageFrame() uint32_t numFrames = GetFrameCount(); // We can't use mPNG->num_frames_read as it may be one ahead. if (numFrames > 1) { PostInvalidation(mFrameRect); } #endif - PostFrameStop(alpha, mAnimInfo.mDispose, mAnimInfo.mTimeout, mAnimInfo.mBlend); + PostFrameStop(alpha, mAnimInfo.mDispose, mAnimInfo.mTimeout, + mAnimInfo.mBlend); } void nsPNGDecoder::InitInternal() { // For size decodes, we don't need to initialize the png decoder if (IsSizeDecode()) { return; } mCMSMode = gfxPlatform::GetCMSMode(); if ((mDecodeFlags & DECODER_NO_COLORSPACE_CONVERSION) != 0) mCMSMode = eCMSMode_Off; - mDisablePremultipliedAlpha = (mDecodeFlags & DECODER_NO_PREMULTIPLY_ALPHA) != 0; + mDisablePremultipliedAlpha = (mDecodeFlags & DECODER_NO_PREMULTIPLY_ALPHA) + != 0; #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED static png_byte color_chunks[]= - { 99, 72, 82, 77, '\0', /* cHRM */ - 105, 67, 67, 80, '\0'}; /* iCCP */ + { 99, 72, 82, 77, '\0', // cHRM + 105, 67, 67, 80, '\0'}; // iCCP static png_byte unused_chunks[]= - { 98, 75, 71, 68, '\0', /* bKGD */ - 104, 73, 83, 84, '\0', /* hIST */ - 105, 84, 88, 116, '\0', /* iTXt */ - 111, 70, 70, 115, '\0', /* oFFs */ - 112, 67, 65, 76, '\0', /* pCAL */ - 115, 67, 65, 76, '\0', /* sCAL */ - 112, 72, 89, 115, '\0', /* pHYs */ - 115, 66, 73, 84, '\0', /* sBIT */ - 115, 80, 76, 84, '\0', /* sPLT */ - 116, 69, 88, 116, '\0', /* tEXt */ - 116, 73, 77, 69, '\0', /* tIME */ - 122, 84, 88, 116, '\0'}; /* zTXt */ + { 98, 75, 71, 68, '\0', // bKGD + 104, 73, 83, 84, '\0', // hIST + 105, 84, 88, 116, '\0', // iTXt + 111, 70, 70, 115, '\0', // oFFs + 112, 67, 65, 76, '\0', // pCAL + 115, 67, 65, 76, '\0', // sCAL + 112, 72, 89, 115, '\0', // pHYs + 115, 66, 73, 84, '\0', // sBIT + 115, 80, 76, 84, '\0', // sPLT + 116, 69, 88, 116, '\0', // tEXt + 116, 73, 77, 69, '\0', // tIME + 122, 84, 88, 116, '\0'}; // zTXt #endif - /* For full decodes, do png init stuff */ + // For full decodes, do png init stuff - /* Initialize the container's source image header. */ - /* Always decode to 24 bit pixdepth */ + // Initialize the container's source image header + // Always decode to 24 bit pixdepth mPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nsPNGDecoder::error_callback, nsPNGDecoder::warning_callback); if (!mPNG) { PostDecoderError(NS_ERROR_OUT_OF_MEMORY); return; } @@ -246,57 +244,57 @@ nsPNGDecoder::InitInternal() mInfo = png_create_info_struct(mPNG); if (!mInfo) { PostDecoderError(NS_ERROR_OUT_OF_MEMORY); png_destroy_read_struct(&mPNG, nullptr, nullptr); return; } #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED - /* Ignore unused chunks */ + // Ignore unused chunks if (mCMSMode == eCMSMode_Off) png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2); png_set_keep_unknown_chunks(mPNG, 1, unused_chunks, (int)sizeof(unused_chunks)/5); #endif #ifdef PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED if (mCMSMode != eCMSMode_Off) png_set_chunk_malloc_max(mPNG, 4000000L); #endif #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED #ifndef PR_LOGGING - /* Disallow palette-index checking, for speed; we would ignore the warning - * anyhow unless we have defined PR_LOGGING. This feature was added at - * libpng version 1.5.10 and is disabled in the embedded libpng but enabled - * by default in the system libpng. This call also disables it in the - * system libpng, for decoding speed. Bug #745202. - */ + // Disallow palette-index checking, for speed; we would ignore the warning + // anyhow unless we have defined PR_LOGGING. This feature was added at + // libpng version 1.5.10 and is disabled in the embedded libpng but enabled + // by default in the system libpng. This call also disables it in the + // system libpng, for decoding speed. Bug #745202. png_set_check_for_invalid_index(mPNG, 0); #endif #endif #if defined(PNG_SET_OPTION_SUPPORTED) && defined(PNG_sRGB_PROFILE_CHECKS) && \ PNG_sRGB_PROFILE_CHECKS >= 0 - /* Skip checking of sRGB ICC profiles */ + // Skip checking of sRGB ICC profiles png_set_option(mPNG, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif - /* use this as libpng "progressive pointer" (retrieve in callbacks) */ + // use this as libpng "progressive pointer" (retrieve in callbacks) png_set_progressive_read_fn(mPNG, static_cast<png_voidp>(this), nsPNGDecoder::info_callback, nsPNGDecoder::row_callback, nsPNGDecoder::end_callback); } void -nsPNGDecoder::WriteInternal(const char *aBuffer, uint32_t aCount, DecodeStrategy) +nsPNGDecoder::WriteInternal(const char *aBuffer, uint32_t aCount, + DecodeStrategy) { NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!"); // If we only want width/height, we don't need to go through libpng if (IsSizeDecode()) { // Are we done? if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) @@ -388,34 +386,24 @@ PNGGetColorProfile(png_structp png_ptr, int color_type, qcms_data_type *inType, uint32_t *intent) { qcms_profile *profile = nullptr; *intent = QCMS_INTENT_PERCEPTUAL; // Our default // First try to see if iCCP chunk is present if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) { png_uint_32 profileLen; -#if (PNG_LIBPNG_VER < 10500) - char *profileData, *profileName; -#else png_bytep profileData; png_charp profileName; -#endif int compression; png_get_iCCP(png_ptr, info_ptr, &profileName, &compression, &profileData, &profileLen); - profile = qcms_profile_from_memory( -#if (PNG_LIBPNG_VER < 10500) - profileData, -#else - (char *)profileData, -#endif - profileLen); + profile = qcms_profile_from_memory((char *)profileData, profileLen); if (profile) { uint32_t profileSpace = qcms_profile_get_color_space(profile); bool mismatch = false; if (color_type & PNG_COLOR_MASK_COLOR) { if (profileSpace != icSigRgbData) mismatch = true; } else { @@ -493,81 +481,81 @@ PNGGetColorProfile(png_structp png_ptr, } return profile; } void nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) { -/* int number_passes; NOT USED */ +// int number_passes; NOT USED png_uint_32 width, height; int bit_depth, color_type, interlace_type, compression_type, filter_type; unsigned int channels; png_bytep trans = nullptr; int num_trans = 0; nsPNGDecoder *decoder = static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr)); - /* always decode to 24-bit RGB or 32-bit RGBA */ + // Always decode to 24-bit RGB or 32-bit RGBA png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, &compression_type, &filter_type); - /* Are we too big? */ + // Are we too big? if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION) - longjmp(png_jmpbuf(decoder->mPNG), 1); + png_longjmp(decoder->mPNG, 1); // Post our size to the superclass decoder->PostSize(width, height); if (decoder->HasError()) { // Setting the size led to an error. - longjmp(png_jmpbuf(decoder->mPNG), 1); + png_longjmp(decoder->mPNG, 1); } if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_expand(png_ptr); if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand(png_ptr); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { int sample_max = (1 << bit_depth); png_color_16p trans_values; png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); - /* libpng doesn't reject a tRNS chunk with out-of-range samples - so we check it here to avoid setting up a useless opacity - channel or producing unexpected transparent pixels when using - libpng-1.2.19 through 1.2.26 (bug #428045) */ + // libpng doesn't reject a tRNS chunk with out-of-range samples + // so we check it here to avoid setting up a useless opacity + // channel or producing unexpected transparent pixels when using + // libpng-1.2.19 through 1.2.26 (bug #428045) if ((color_type == PNG_COLOR_TYPE_GRAY && (int)trans_values->gray > sample_max) || (color_type == PNG_COLOR_TYPE_RGB && ((int)trans_values->red > sample_max || (int)trans_values->green > sample_max || (int)trans_values->blue > sample_max))) { - /* clear the tRNS valid flag and release tRNS memory */ + // clear the tRNS valid flag and release tRNS memory png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0); } else png_set_expand(png_ptr); } if (bit_depth == 16) png_set_scale_16(png_ptr); qcms_data_type inType = QCMS_DATA_RGBA_8; uint32_t intent = -1; uint32_t pIntent; if (decoder->mCMSMode != eCMSMode_Off) { intent = gfxPlatform::GetRenderingIntent(); decoder->mInProfile = PNGGetColorProfile(png_ptr, info_ptr, color_type, &inType, &pIntent); - /* If we're not mandating an intent, use the one from the image. */ + // If we're not mandating an intent, use the one from the image. if (intent == uint32_t(-1)) intent = pIntent; } if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) { qcms_data_type outType; if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) outType = QCMS_DATA_RGBA_8; @@ -589,52 +577,30 @@ nsPNGDecoder::info_callback(png_structp if (decoder->mCMSMode == eCMSMode_All) { if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) decoder->mTransform = gfxPlatform::GetCMSRGBATransform(); else decoder->mTransform = gfxPlatform::GetCMSRGBTransform(); } } - /* let libpng expand interlaced images */ + // let libpng expand interlaced images if (interlace_type == PNG_INTERLACE_ADAM7) { - /* number_passes = */ + // number_passes = png_set_interlace_handling(png_ptr); } - /* now all of those things we set above are used to update various struct - * members and whatnot, after which we can get channels, rowbytes, etc. */ + // now all of those things we set above are used to update various struct + // members and whatnot, after which we can get channels, rowbytes, etc. png_read_update_info(png_ptr, info_ptr); decoder->mChannels = channels = png_get_channels(png_ptr, info_ptr); - /*---------------------------------------------------------------*/ - /* copy PNG info into imagelib structs (formerly png_set_dims()) */ - /*---------------------------------------------------------------*/ - - // This code is currently unused, but it will be needed for bug 517713. -#if 0 - int32_t alpha_bits = 1; - - if (channels == 2 || channels == 4) { - /* check if alpha is coming from a tRNS chunk and is binary */ - if (num_trans) { - /* if it's not an indexed color image, tRNS means binary */ - if (color_type == PNG_COLOR_TYPE_PALETTE) { - for (int i=0; i<num_trans; i++) { - if ((trans[i] != 0) && (trans[i] != 255)) { - alpha_bits = 8; - break; - } - } - } - } else { - alpha_bits = 8; - } - } -#endif + //---------------------------------------------------------------// + // copy PNG info into imagelib structs (formerly png_set_dims()) // + //---------------------------------------------------------------// if (channels == 1 || channels == 3) decoder->format = gfx::SurfaceFormat::B8G8R8X8; else if (channels == 2 || channels == 4) decoder->format = gfx::SurfaceFormat::B8G8R8A8; #ifdef PNG_APNG_SUPPORTED if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) @@ -651,32 +617,31 @@ nsPNGDecoder::info_callback(png_structp #endif if (decoder->mTransform && (channels <= 2 || interlace_type == PNG_INTERLACE_ADAM7)) { uint32_t bpp[] = { 0, 3, 4, 3, 4 }; decoder->mCMSLine = (uint8_t *)moz_malloc(bpp[channels] * width); if (!decoder->mCMSLine) { - longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY + png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY } } if (interlace_type == PNG_INTERLACE_ADAM7) { if (height < INT32_MAX / (width * channels)) decoder->interlacebuf = (uint8_t *)moz_malloc(channels * width * height); if (!decoder->interlacebuf) { - longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY + png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY } } if (decoder->NeedsNewFrame()) { - /* We know that we need a new frame, so pause input so the decoder - * infrastructure can give it to us. - */ + // We know that we need a new frame, so pause input so the decoder + // infrastructure can give it to us. png_process_data_pause(png_ptr, /* save = */ 1); } } void nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass) { @@ -730,17 +695,17 @@ nsPNGDecoder::row_callback(png_structp p uint32_t bpr = width * sizeof(uint32_t); uint32_t *cptr32 = (uint32_t*)(decoder->mImageData + (row_num*bpr)); bool rowHasNoAlpha = true; if (decoder->mTransform) { if (decoder->mCMSLine) { qcms_transform_data(decoder->mTransform, line, decoder->mCMSLine, iwidth); - /* copy alpha over */ + // copy alpha over uint32_t channels = decoder->mChannels; if (channels == 2 || channels == 4) { for (uint32_t i = 0; i < iwidth; i++) decoder->mCMSLine[4 * i + 3] = line[channels * i + channels - 1]; } line = decoder->mCMSLine; } else { qcms_transform_data(decoder->mTransform, line, line, iwidth); @@ -781,26 +746,27 @@ nsPNGDecoder::row_callback(png_structp p for (uint32_t x=width; x>0; --x) { *cptr32++ = gfxPackedPixel(line[3], line[0], line[1], line[2]); if (line[3] != 0xff) rowHasNoAlpha = false; line += 4; } } else { for (uint32_t x=width; x>0; --x) { - *cptr32++ = gfxPackedPixelNoPreMultiply(line[3], line[0], line[1], line[2]); + *cptr32++ = gfxPackedPixelNoPreMultiply(line[3], line[0], line[1], + line[2]); if (line[3] != 0xff) rowHasNoAlpha = false; line += 4; } } } break; default: - longjmp(png_jmpbuf(decoder->mPNG), 1); + png_longjmp(decoder->mPNG, 1); } if (!rowHasNoAlpha) decoder->mFrameHasNoAlpha = false; if (decoder->mNumFrames <= 1) { // Only do incremental image display for the first frame // XXXbholley - this check should be handled in the superclass @@ -830,19 +796,18 @@ nsPNGDecoder::frame_info_callback(png_st x_offset = png_get_next_frame_x_offset(png_ptr, decoder->mInfo); y_offset = png_get_next_frame_y_offset(png_ptr, decoder->mInfo); width = png_get_next_frame_width(png_ptr, decoder->mInfo); height = png_get_next_frame_height(png_ptr, decoder->mInfo); decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format); if (decoder->NeedsNewFrame()) { - /* We know that we need a new frame, so pause input so the decoder - * infrastructure can give it to us. - */ + // We know that we need a new frame, so pause input so the decoder + // infrastructure can give it to us. png_process_data_pause(png_ptr, /* save = */ 1); } } #endif void nsPNGDecoder::end_callback(png_structp png_ptr, png_infop info_ptr) { @@ -877,17 +842,17 @@ nsPNGDecoder::end_callback(png_structp p decoder->PostDecodeDone(loop_count); } void nsPNGDecoder::error_callback(png_structp png_ptr, png_const_charp error_msg) { PR_LOG(GetPNGLog(), PR_LOG_ERROR, ("libpng error: %s\n", error_msg)); - longjmp(png_jmpbuf(png_ptr), 1); + png_longjmp(png_ptr, 1); } void nsPNGDecoder::warning_callback(png_structp png_ptr, png_const_charp warning_msg) { PR_LOG(GetPNGLog(), PR_LOG_WARNING, ("libpng warning: %s\n", warning_msg)); }
--- a/image/decoders/nsPNGDecoder.h +++ b/image/decoders/nsPNGDecoder.h @@ -1,16 +1,16 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef nsPNGDecoder_h__ -#define nsPNGDecoder_h__ +#ifndef nsPNGDecoder_h +#define nsPNGDecoder_h #include "Decoder.h" #include "gfxTypes.h" #include "nsCOMPtr.h" #include "png.h" @@ -128,9 +128,9 @@ public: // This is defined in the PNG spec as an invariant. We use it to // do manual validation without libpng. static const uint8_t pngSignatureBytes[]; }; } // namespace image } // namespace mozilla -#endif // nsPNGDecoder_h__ +#endif // nsPNGDecoder_h
--- a/image/encoders/png/nsPNGEncoder.cpp +++ b/image/encoders/png/nsPNGEncoder.cpp @@ -1,31 +1,45 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "ImageLogging.h" #include "nsCRT.h" #include "nsPNGEncoder.h" -#include "prprf.h" +#include "nsStreamUtils.h" #include "nsString.h" -#include "nsStreamUtils.h" +#include "prprf.h" using namespace mozilla; -NS_IMPL_ISUPPORTS(nsPNGEncoder, imgIEncoder, nsIInputStream, nsIAsyncInputStream) +#ifdef PR_LOGGING +static PRLogModuleInfo * +GetPNGEncoderLog() +{ + static PRLogModuleInfo *sPNGEncoderLog; + if (!sPNGEncoderLog) + sPNGEncoderLog = PR_NewLogModule("PNGEncoder"); + return sPNGEncoderLog; +} +#endif + +NS_IMPL_ISUPPORTS(nsPNGEncoder, imgIEncoder, nsIInputStream, + nsIAsyncInputStream) nsPNGEncoder::nsPNGEncoder() : mPNG(nullptr), mPNGinfo(nullptr), mIsAnimation(false), mFinished(false), mImageBuffer(nullptr), mImageBufferSize(0), mImageBufferUsed(0), mImageBufferReadPoint(0), mCallback(nullptr), mCallbackTarget(nullptr), mNotifyThreshold(0), - mReentrantMonitor("nsPNGEncoder.mReentrantMonitor") + mReentrantMonitor( + "nsPNGEncoder.mReentrantMonitor") { } nsPNGEncoder::~nsPNGEncoder() { if (mImageBuffer) { moz_free(mImageBuffer); mImageBuffer = nullptr; @@ -67,17 +81,17 @@ NS_IMETHODIMP nsPNGEncoder::InitFromData rv = EndImageEncode(); return rv; } // nsPNGEncoder::StartImageEncode // -// +// // See ::InitFromData for other info. NS_IMETHODIMP nsPNGEncoder::StartImageEncode(uint32_t aWidth, uint32_t aHeight, uint32_t aInputFormat, const nsAString& aOutputOptions) { bool useTransparency = true, skipFirstFrame = false; uint32_t numFrames = 1; @@ -236,17 +250,17 @@ NS_IMETHODIMP nsPNGEncoder::AddImageFram if (mIsAnimation) { // XXX the row pointers arg (#3) is unused, can it be removed? png_write_frame_head(mPNG, mPNGinfo, nullptr, aWidth, aHeight, x_offset, y_offset, delay_ms, 1000, dispose_op, blend_op); } #endif - // Stride is the padded width of each row, so it better be longer + // Stride is the padded width of each row, so it better be longer // (I'm afraid people will not understand what stride means, so // check it well) if ((aInputFormat == INPUT_FORMAT_RGB && aStride < aWidth * 3) || ((aInputFormat == INPUT_FORMAT_RGBA || aInputFormat == INPUT_FORMAT_HOSTARGB) && aStride < aWidth * 4)) { NS_WARNING("Invalid stride for InitFromData/AddImageFrame"); @@ -562,17 +576,18 @@ NS_IMETHODIMP nsPNGEncoder::AsyncWait(ns // 0 means "any number of bytes except 0" mNotifyThreshold = aRequestedCount; if (!aRequestedCount) mNotifyThreshold = 1024; // We don't want to notify incessantly // We set the callback absolutely last, because NotifyListener uses it to // determine if someone needs to be notified. If we don't set it last, // NotifyListener might try to fire off a notification to a null target - // which will generally cause non-threadsafe objects to be used off the main thread + // which will generally cause non-threadsafe objects to be used off the main + // thread mCallback = aCallback; // What we are being asked for may be present already NotifyListener(); return NS_OK; } NS_IMETHODIMP nsPNGEncoder::CloseWithStatus(nsresult aStatus) @@ -626,47 +641,35 @@ nsPNGEncoder::StripAlpha(const uint8_t* pixelOut[1] = pixelIn[1]; pixelOut[2] = pixelIn[2]; } } // nsPNGEncoder::WarningCallback -void // static +void nsPNGEncoder::WarningCallback(png_structp png_ptr, png_const_charp warning_msg) { -#ifdef DEBUG - // XXX: these messages are probably useful callers... - // use nsIConsoleService? - PR_fprintf(PR_STDERR, "PNG Encoder: %s\n", warning_msg);; -#endif + PR_LOG(GetPNGEncoderLog(), PR_LOG_WARNING, + ("libpng warning: %s\n", warning_msg)); } // nsPNGEncoder::ErrorCallback -void // static +void nsPNGEncoder::ErrorCallback(png_structp png_ptr, png_const_charp error_msg) { -#ifdef DEBUG - // XXX: these messages are probably useful callers... - // use nsIConsoleService? - PR_fprintf(PR_STDERR, "PNG Encoder: %s\n", error_msg);; -#endif -#if PNG_LIBPNG_VER < 10500 - longjmp(png_ptr->jmpbuf, 1); -#else - png_longjmp(png_ptr, 1); -#endif + PR_LOG(GetPNGEncoderLog(), PR_LOG_ERROR, ("libpng error: %s\n", error_msg)); + png_longjmp(png_ptr, 1); } - // nsPNGEncoder::WriteCallback void // static nsPNGEncoder::WriteCallback(png_structp png, png_bytep data, png_size_t size) { nsPNGEncoder* that = static_cast<nsPNGEncoder*>(png_get_io_ptr(png)); if (! that->mImageBuffer)
--- a/image/encoders/png/nsPNGEncoder.h +++ b/image/encoders/png/nsPNGEncoder.h @@ -1,21 +1,22 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "mozilla/Attributes.h" -#include "mozilla/ReentrantMonitor.h" +#ifndef nsPNGEncoder_h + +#include <png.h> #include "imgIEncoder.h" - #include "nsCOMPtr.h" -#include <png.h> +#include "mozilla/Attributes.h" +#include "mozilla/ReentrantMonitor.h" #define NS_PNGENCODER_CID \ { /* 38d1592e-b81e-432b-86f8-471878bbfe07 */ \ 0x38d1592e, \ 0xb81e, \ 0x432b, \ {0x86, 0xf8, 0x47, 0x18, 0x78, 0xbb, 0xfe, 0x07} \ } @@ -67,16 +68,16 @@ protected: uint32_t mImageBufferUsed; uint32_t mImageBufferReadPoint; nsCOMPtr<nsIInputStreamCallback> mCallback; nsCOMPtr<nsIEventTarget> mCallbackTarget; uint32_t mNotifyThreshold; - /* - nsPNGEncoder is designed to allow one thread to pump data into it while another - reads from it. We lock to ensure that the buffer remains append-only while - we read from it (that it is not realloced) and to ensure that only one thread - dispatches a callback for each call to AsyncWait. - */ + // nsPNGEncoder is designed to allow one thread to pump data into it while + // another reads from it. We lock to ensure that the buffer remains + // append-only while we read from it (that it is not realloced) and to + // ensure that only one thread dispatches a callback for each call to + // AsyncWait. ReentrantMonitor mReentrantMonitor; }; +#endif // nsPNGEncoder_h
--- a/js/src/builtin/SymbolObject.cpp +++ b/js/src/builtin/SymbolObject.cpp @@ -5,16 +5,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "builtin/SymbolObject.h" #include "vm/StringBuffer.h" #include "jsobjinlines.h" +#include "vm/ObjectImpl-inl.h" #include "vm/Symbol-inl.h" using JS::Symbol; using namespace js; const Class SymbolObject::class_ = { "Symbol", JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Symbol),
--- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -31,38 +31,33 @@ namespace gc { typedef Vector<JS::Zone *, 4, SystemAllocPolicy> ZoneVector; class MarkingValidator; struct AutoPrepareForTracing; class AutoTraceSession; class ChunkPool { - Chunk *emptyChunkListHead; - size_t emptyCount; + Chunk *head_; + size_t count_; public: - ChunkPool() - : emptyChunkListHead(nullptr), - emptyCount(0) - {} + ChunkPool() : head_(nullptr), count_(0) {} - size_t getEmptyCount() const { - return emptyCount; - } + size_t count() const { return count_; } /* Must be called with the GC lock taken. */ inline Chunk *get(JSRuntime *rt); /* Must be called either during the GC or with the GC lock taken. */ inline void put(Chunk *chunk); class Enum { public: - explicit Enum(ChunkPool &pool) : pool(pool), chunkp(&pool.emptyChunkListHead) {} + explicit Enum(ChunkPool &pool) : pool(pool), chunkp(&pool.head_) {} bool empty() { return !*chunkp; } Chunk *front(); inline void popFront(); inline void removeAndPopFront(); private: ChunkPool &pool; Chunk **chunkp; }; @@ -590,17 +585,17 @@ class GCRuntime * Doubly-linked lists of chunks from user and system compartments. The GC * allocates its arenas from the corresponding list and when all arenas * in the list head are taken, then the chunk is removed from the list. * During the GC when all arenas in a chunk become free, that chunk is * removed from the list and scheduled for release. */ js::gc::Chunk *systemAvailableChunkListHead; js::gc::Chunk *userAvailableChunkListHead; - js::gc::ChunkPool chunkPool; + js::gc::ChunkPool emptyChunks; js::RootedValueMap rootsHash; size_t maxMallocBytes; /* * Number of the committed arenas in all GC chunks including empty chunks. */ @@ -693,16 +688,22 @@ class GCRuntime JS::Zone *zoneGroups; JS::Zone *currentZoneGroup; int finalizePhase; JS::Zone *sweepZone; int sweepKindIndex; bool abortSweepAfterCurrentGroup; /* + * Concurrent sweep infrastructure. + */ + void startTask(GCParallelTask &task, gcstats::Phase phase); + void joinTask(GCParallelTask &task, gcstats::Phase phase); + + /* * List head of arenas allocated during the sweep phase. */ js::gc::ArenaHeader *arenasAllocatedDuringSweep; #ifdef JS_GC_MARKING_VALIDATION js::gc::MarkingValidator *markingValidator; #endif
--- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -467,16 +467,25 @@ IsMarked(T **thingp) #endif return (*thingp)->asTenured().isMarked(); } template <typename T> static bool IsAboutToBeFinalized(T **thingp) { + MOZ_ASSERT_IF(!ThingIsPermanentAtom(*thingp), + CurrentThreadCanAccessRuntime((*thingp)->runtimeFromMainThread())); + return IsAboutToBeFinalizedFromAnyThread(thingp); +} + +template <typename T> +static bool +IsAboutToBeFinalizedFromAnyThread(T **thingp) +{ MOZ_ASSERT(thingp); MOZ_ASSERT(*thingp); T *thing = *thingp; JSRuntime *rt = thing->runtimeFromAnyThread(); /* Permanent atoms are never finalized by non-owning runtimes. */ if (ThingIsPermanentAtom(thing) && !TlsPerThreadData.get()->associatedWith(rt)) @@ -498,17 +507,17 @@ IsAboutToBeFinalized(T **thingp) if (rt->isHeapMinorCollecting()) { if (IsInsideNursery(thing)) return !nursery.getForwardedPointer(thingp); return false; } } #endif // JSGC_GENERATIONAL - Zone *zone = thing->asTenured().zone(); + Zone *zone = thing->asTenured().zoneFromAnyThread(); if (zone->isGCSweeping()) { /* * We should return false for things that have been allocated during * incremental sweeping, but this possibility doesn't occur at the moment * because this function is only called at the very start of the sweeping a * compartment group and during minor gc. Rather than do the extra check, * we just assert that it's not necessary. */ @@ -611,16 +620,22 @@ Is##base##Marked(BarrieredBase<type*> *t \ bool \ Is##base##AboutToBeFinalized(type **thingp) \ { \ return IsAboutToBeFinalized<type>(thingp); \ } \ \ bool \ +Is##base##AboutToBeFinalizedFromAnyThread(type **thingp) \ +{ \ + return IsAboutToBeFinalizedFromAnyThread<type>(thingp); \ +} \ + \ +bool \ Is##base##AboutToBeFinalized(BarrieredBase<type*> *thingp) \ { \ return IsAboutToBeFinalized<type>(thingp->unsafeGet()); \ } \ \ type * \ Update##base##IfRelocated(JSRuntime *rt, BarrieredBase<type*> *thingp) \ { \ @@ -905,16 +920,38 @@ gc::IsValueAboutToBeFinalized(Value *v) MOZ_ASSERT(v->isSymbol()); JS::Symbol *sym = v->toSymbol(); rv = IsAboutToBeFinalized<JS::Symbol>(&sym); v->setSymbol(sym); } return rv; } +bool +gc::IsValueAboutToBeFinalizedFromAnyThread(Value *v) +{ + MOZ_ASSERT(v->isMarkable()); + bool rv; + if (v->isString()) { + JSString *str = (JSString *)v->toGCThing(); + rv = IsAboutToBeFinalizedFromAnyThread<JSString>(&str); + v->setString(str); + } else if (v->isObject()) { + JSObject *obj = (JSObject *)v->toGCThing(); + rv = IsAboutToBeFinalizedFromAnyThread<JSObject>(&obj); + v->setObject(*obj); + } else { + MOZ_ASSERT(v->isSymbol()); + JS::Symbol *sym = v->toSymbol(); + rv = IsAboutToBeFinalizedFromAnyThread<JS::Symbol>(&sym); + v->setSymbol(sym); + } + return rv; +} + /*** Slot Marking ***/ bool gc::IsSlotMarked(HeapSlot *s) { return IsMarked(s); } @@ -1026,16 +1063,22 @@ gc::IsCellMarked(Cell **thingp) } bool gc::IsCellAboutToBeFinalized(Cell **thingp) { return IsAboutToBeFinalized<Cell>(thingp); } +bool +gc::IsCellAboutToBeFinalizedFromAnyThread(Cell **thingp) +{ + return IsAboutToBeFinalizedFromAnyThread<Cell>(thingp); +} + /*** Push Mark Stack ***/ #define JS_COMPARTMENT_ASSERT(rt, thing) \ MOZ_ASSERT((thing)->zone()->isGCMarking()) #define JS_COMPARTMENT_ASSERT_STR(rt, thing) \ MOZ_ASSERT((thing)->zone()->isGCMarking() || \ (rt)->isAtomsZone((thing)->zone()));
--- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -93,16 +93,17 @@ namespace gc { void Mark##base(JSTracer *trc, BarrieredBase<type*> *thing, const char *name); \ void Mark##base##Root(JSTracer *trc, type **thingp, const char *name); \ void Mark##base##Unbarriered(JSTracer *trc, type **thingp, const char *name); \ void Mark##base##Range(JSTracer *trc, size_t len, HeapPtr<type*> *thing, const char *name); \ void Mark##base##RootRange(JSTracer *trc, size_t len, type **thing, const char *name); \ bool Is##base##Marked(type **thingp); \ bool Is##base##Marked(BarrieredBase<type*> *thingp); \ bool Is##base##AboutToBeFinalized(type **thingp); \ +bool Is##base##AboutToBeFinalizedFromAnyThread(type **thingp); \ bool Is##base##AboutToBeFinalized(BarrieredBase<type*> *thingp); \ type *Update##base##IfRelocated(JSRuntime *rt, BarrieredBase<type*> *thingp); \ type *Update##base##IfRelocated(JSRuntime *rt, type **thingp); DeclMarker(BaseShape, BaseShape) DeclMarker(BaseShape, UnownedBaseShape) DeclMarker(JitCode, jit::JitCode) DeclMarker(Object, NativeObject) @@ -214,16 +215,19 @@ void MarkTypeRoot(JSTracer *trc, types::Type *v, const char *name); bool IsValueMarked(Value *v); bool IsValueAboutToBeFinalized(Value *v); +bool +IsValueAboutToBeFinalizedFromAnyThread(Value *v); + /*** Slot Marking ***/ bool IsSlotMarked(HeapSlot *s); void MarkSlot(JSTracer *trc, HeapSlot *s, const char *name); @@ -322,16 +326,19 @@ Mark(JSTracer *trc, ScopeObject **obj, c } bool IsCellMarked(Cell **thingp); bool IsCellAboutToBeFinalized(Cell **thing); +bool +IsCellAboutToBeFinalizedFromAnyThread(Cell **thing); + inline bool IsMarked(BarrieredBase<Value> *v) { if (!v->isMarkable()) return true; return IsValueMarked(v->unsafeGet()); }
--- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -14,16 +14,17 @@ #include <stdio.h> #include "jscrashreport.h" #include "jsprf.h" #include "jsutil.h" #include "prmjtime.h" #include "gc/Memory.h" +#include "vm/HelperThreads.h" #include "vm/Runtime.h" using namespace js; using namespace js::gc; using namespace js::gcstats; using mozilla::PodArrayZero; @@ -832,16 +833,26 @@ Statistics::endPhase(Phase phase) phaseNestingDepth--; int64_t t = PRMJ_Now() - phaseStartTimes[phase]; slices.back().phaseTimes[phase] += t; phaseTimes[phase] += t; phaseStartTimes[phase] = 0; } +void +Statistics::endParallelPhase(Phase phase, const GCParallelTask *task) +{ + phaseNestingDepth--; + + slices.back().phaseTimes[phase] += task->duration(); + phaseTimes[phase] += task->duration(); + phaseStartTimes[phase] = 0; +} + int64_t Statistics::beginSCC() { return PRMJ_Now(); } void Statistics::endSCC(unsigned scc, int64_t start)
--- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -15,16 +15,19 @@ #include "jspubtd.h" #include "js/GCAPI.h" #include "js/Vector.h" struct JSCompartment; namespace js { + +class GCParallelTask; + namespace gcstats { enum Phase { PHASE_GC_BEGIN, PHASE_WAIT_BACKGROUND_THREAD, PHASE_MARK_DISCARD_CODE, PHASE_PURGE, PHASE_MARK, @@ -104,16 +107,17 @@ struct ZoneGCStats struct Statistics { explicit Statistics(JSRuntime *rt); ~Statistics(); void beginPhase(Phase phase); void endPhase(Phase phase); + void endParallelPhase(Phase phase, const GCParallelTask *task); void beginSlice(const ZoneGCStats &zoneStats, JS::gcreason::Reason reason); void endSlice(); void reset(const char *reason) { slices.back().resetReason = reason; } void nonincremental(const char *reason) { nonincrementalReason = reason; } void count(Stat s) { @@ -229,35 +233,51 @@ struct AutoGCSlice Statistics &stats; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; struct AutoPhase { AutoPhase(Statistics &stats, Phase phase MOZ_GUARD_OBJECT_NOTIFIER_PARAM) - : stats(stats), phase(phase), enabled(true) + : stats(stats), task(nullptr), phase(phase), enabled(true) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; stats.beginPhase(phase); } + AutoPhase(Statistics &stats, bool condition, Phase phase MOZ_GUARD_OBJECT_NOTIFIER_PARAM) - : stats(stats), phase(phase), enabled(condition) + : stats(stats), task(nullptr), phase(phase), enabled(condition) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; if (enabled) stats.beginPhase(phase); } - ~AutoPhase() { + + AutoPhase(Statistics &stats, const GCParallelTask &task, Phase phase + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : stats(stats), task(&task), phase(phase), enabled(true) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; if (enabled) - stats.endPhase(phase); + stats.beginPhase(phase); + } + + ~AutoPhase() { + if (enabled) { + if (task) + stats.endParallelPhase(phase, task); + else + stats.endPhase(phase); + } } Statistics &stats; + const GCParallelTask *task; Phase phase; bool enabled; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; struct AutoSCC { AutoSCC(Statistics &stats, unsigned scc
--- a/js/src/jit/arm/Assembler-arm.cpp +++ b/js/src/jit/arm/Assembler-arm.cpp @@ -1211,16 +1211,20 @@ Instruction * BOffImm::getDest(Instruction *src) { // TODO: It is probably worthwhile to verify that src is actually a branch. // NOTE: This does not explicitly shift the offset of the destination left by 2, // since it is indexing into an array of instruction sized objects. return &src[(((int32_t)data << 8) >> 8) + 2]; } +const js::jit::DoubleEncoder::DoubleEntry js::jit::DoubleEncoder::table[256] = { +#include "jit/arm/DoubleEntryTable.tbl" +}; + // VFPRegister implementation VFPRegister VFPRegister::doubleOverlay(unsigned int which) const { MOZ_ASSERT(!_isInvalid); MOZ_ASSERT(which == 0); if (kind != Double) return VFPRegister(code_ >> 1, Double);
--- a/js/src/jit/arm/Assembler-arm.h +++ b/js/src/jit/arm/Assembler-arm.h @@ -445,36 +445,33 @@ struct Imm8VFPOffData MOZ_ASSERT((imm & ~(0xff)) == 0); } }; // ARM can magically encode 256 very special immediates to be moved into a // register. struct Imm8VFPImmData { - private: + // This structure's members are public and it has no constructor to + // initialize them, for a very special reason. Were this structure to + // have a constructor, the initialization for DoubleEncoder's internal + // table (see below) would require a rather large static constructor on + // some of our supported compilers. The known solution to this is to mark + // the constructor MOZ_CONSTEXPR, but, again, some of our supported + // compilers don't support MOZ_CONSTEXPR! So we are reduced to public + // members and eschewing a constructor in hopes that the initialization + // of DoubleEncoder's table is correct. uint32_t imm4L : 4; - uint32_t pad : 12; uint32_t imm4H : 4; - int32_t isInvalid : 12; - - public: - Imm8VFPImmData() - : imm4L(-1U & 0xf), imm4H(-1U & 0xf), isInvalid(-1) - { } - - Imm8VFPImmData(uint32_t imm) - : imm4L(imm&0xf), imm4H(imm >> 4), isInvalid(0) - { - MOZ_ASSERT(imm <= 0xff); - } + int32_t isInvalid : 24; uint32_t encode() { - if (isInvalid != 0) - return -1; + // This assert is an attempting at ensuring that we don't create random + // instances of this structure and then asking to encode() it. + MOZ_ASSERT(isInvalid == 0); return imm4L | (imm4H << 16); }; }; struct Imm12Data { uint32_t data : 12; uint32_t encode() { @@ -2185,60 +2182,25 @@ GetDoubleArgStackDisp(uint32_t usedIntAr return (intSlots + doubleSlots + *padding) * sizeof(intptr_t); } #endif class DoubleEncoder { - uint32_t rep(bool b, uint32_t count) { - uint32_t ret = 0; - for (uint32_t i = 0; i < count; i++) - ret = (ret << 1) | b; - return ret; - } - - uint32_t encode(uint8_t value) { - // ARM ARM "VFP modified immediate constants" - // aBbbbbbb bbcdefgh 000... - // We want to return the top 32 bits of the double the rest are 0. - bool a = value >> 7; - bool b = value >> 6 & 1; - bool B = !b; - uint32_t cdefgh = value & 0x3f; - return a << 31 | - B << 30 | - rep(b, 8) << 22 | - cdefgh << 16; - } - struct DoubleEntry { uint32_t dblTop; datastore::Imm8VFPImmData data; - - DoubleEntry() - : dblTop(-1) - { } - DoubleEntry(uint32_t dblTop_, datastore::Imm8VFPImmData data_) - : dblTop(dblTop_), data(data_) - { } }; - mozilla::Array<DoubleEntry, 256> table; + static const DoubleEntry table[256]; public: - DoubleEncoder() - { - for (int i = 0; i < 256; i++) { - table[i] = DoubleEntry(encode(i), datastore::Imm8VFPImmData(i)); - } - } - bool lookup(uint32_t top, datastore::Imm8VFPImmData *ret) { for (int i = 0; i < 256; i++) { if (table[i].dblTop == top) { *ret = table[i].data; return true; } } return false;
new file mode 100644 --- /dev/null +++ b/js/src/jit/arm/DoubleEntryTable.tbl @@ -0,0 +1,257 @@ +/* THIS FILE IS AUTOMATICALLY GENERATED BY gen-double-encode-table.py. */ + { 0x40000000, { 0, 0, 0 } }, + { 0x40010000, { 1, 0, 0 } }, + { 0x40020000, { 2, 0, 0 } }, + { 0x40030000, { 3, 0, 0 } }, + { 0x40040000, { 4, 0, 0 } }, + { 0x40050000, { 5, 0, 0 } }, + { 0x40060000, { 6, 0, 0 } }, + { 0x40070000, { 7, 0, 0 } }, + { 0x40080000, { 8, 0, 0 } }, + { 0x40090000, { 9, 0, 0 } }, + { 0x400a0000, { 10, 0, 0 } }, + { 0x400b0000, { 11, 0, 0 } }, + { 0x400c0000, { 12, 0, 0 } }, + { 0x400d0000, { 13, 0, 0 } }, + { 0x400e0000, { 14, 0, 0 } }, + { 0x400f0000, { 15, 0, 0 } }, + { 0x40100000, { 0, 1, 0 } }, + { 0x40110000, { 1, 1, 0 } }, + { 0x40120000, { 2, 1, 0 } }, + { 0x40130000, { 3, 1, 0 } }, + { 0x40140000, { 4, 1, 0 } }, + { 0x40150000, { 5, 1, 0 } }, + { 0x40160000, { 6, 1, 0 } }, + { 0x40170000, { 7, 1, 0 } }, + { 0x40180000, { 8, 1, 0 } }, + { 0x40190000, { 9, 1, 0 } }, + { 0x401a0000, { 10, 1, 0 } }, + { 0x401b0000, { 11, 1, 0 } }, + { 0x401c0000, { 12, 1, 0 } }, + { 0x401d0000, { 13, 1, 0 } }, + { 0x401e0000, { 14, 1, 0 } }, + { 0x401f0000, { 15, 1, 0 } }, + { 0x40200000, { 0, 2, 0 } }, + { 0x40210000, { 1, 2, 0 } }, + { 0x40220000, { 2, 2, 0 } }, + { 0x40230000, { 3, 2, 0 } }, + { 0x40240000, { 4, 2, 0 } }, + { 0x40250000, { 5, 2, 0 } }, + { 0x40260000, { 6, 2, 0 } }, + { 0x40270000, { 7, 2, 0 } }, + { 0x40280000, { 8, 2, 0 } }, + { 0x40290000, { 9, 2, 0 } }, + { 0x402a0000, { 10, 2, 0 } }, + { 0x402b0000, { 11, 2, 0 } }, + { 0x402c0000, { 12, 2, 0 } }, + { 0x402d0000, { 13, 2, 0 } }, + { 0x402e0000, { 14, 2, 0 } }, + { 0x402f0000, { 15, 2, 0 } }, + { 0x40300000, { 0, 3, 0 } }, + { 0x40310000, { 1, 3, 0 } }, + { 0x40320000, { 2, 3, 0 } }, + { 0x40330000, { 3, 3, 0 } }, + { 0x40340000, { 4, 3, 0 } }, + { 0x40350000, { 5, 3, 0 } }, + { 0x40360000, { 6, 3, 0 } }, + { 0x40370000, { 7, 3, 0 } }, + { 0x40380000, { 8, 3, 0 } }, + { 0x40390000, { 9, 3, 0 } }, + { 0x403a0000, { 10, 3, 0 } }, + { 0x403b0000, { 11, 3, 0 } }, + { 0x403c0000, { 12, 3, 0 } }, + { 0x403d0000, { 13, 3, 0 } }, + { 0x403e0000, { 14, 3, 0 } }, + { 0x403f0000, { 15, 3, 0 } }, + { 0x3fc00000, { 0, 4, 0 } }, + { 0x3fc10000, { 1, 4, 0 } }, + { 0x3fc20000, { 2, 4, 0 } }, + { 0x3fc30000, { 3, 4, 0 } }, + { 0x3fc40000, { 4, 4, 0 } }, + { 0x3fc50000, { 5, 4, 0 } }, + { 0x3fc60000, { 6, 4, 0 } }, + { 0x3fc70000, { 7, 4, 0 } }, + { 0x3fc80000, { 8, 4, 0 } }, + { 0x3fc90000, { 9, 4, 0 } }, + { 0x3fca0000, { 10, 4, 0 } }, + { 0x3fcb0000, { 11, 4, 0 } }, + { 0x3fcc0000, { 12, 4, 0 } }, + { 0x3fcd0000, { 13, 4, 0 } }, + { 0x3fce0000, { 14, 4, 0 } }, + { 0x3fcf0000, { 15, 4, 0 } }, + { 0x3fd00000, { 0, 5, 0 } }, + { 0x3fd10000, { 1, 5, 0 } }, + { 0x3fd20000, { 2, 5, 0 } }, + { 0x3fd30000, { 3, 5, 0 } }, + { 0x3fd40000, { 4, 5, 0 } }, + { 0x3fd50000, { 5, 5, 0 } }, + { 0x3fd60000, { 6, 5, 0 } }, + { 0x3fd70000, { 7, 5, 0 } }, + { 0x3fd80000, { 8, 5, 0 } }, + { 0x3fd90000, { 9, 5, 0 } }, + { 0x3fda0000, { 10, 5, 0 } }, + { 0x3fdb0000, { 11, 5, 0 } }, + { 0x3fdc0000, { 12, 5, 0 } }, + { 0x3fdd0000, { 13, 5, 0 } }, + { 0x3fde0000, { 14, 5, 0 } }, + { 0x3fdf0000, { 15, 5, 0 } }, + { 0x3fe00000, { 0, 6, 0 } }, + { 0x3fe10000, { 1, 6, 0 } }, + { 0x3fe20000, { 2, 6, 0 } }, + { 0x3fe30000, { 3, 6, 0 } }, + { 0x3fe40000, { 4, 6, 0 } }, + { 0x3fe50000, { 5, 6, 0 } }, + { 0x3fe60000, { 6, 6, 0 } }, + { 0x3fe70000, { 7, 6, 0 } }, + { 0x3fe80000, { 8, 6, 0 } }, + { 0x3fe90000, { 9, 6, 0 } }, + { 0x3fea0000, { 10, 6, 0 } }, + { 0x3feb0000, { 11, 6, 0 } }, + { 0x3fec0000, { 12, 6, 0 } }, + { 0x3fed0000, { 13, 6, 0 } }, + { 0x3fee0000, { 14, 6, 0 } }, + { 0x3fef0000, { 15, 6, 0 } }, + { 0x3ff00000, { 0, 7, 0 } }, + { 0x3ff10000, { 1, 7, 0 } }, + { 0x3ff20000, { 2, 7, 0 } }, + { 0x3ff30000, { 3, 7, 0 } }, + { 0x3ff40000, { 4, 7, 0 } }, + { 0x3ff50000, { 5, 7, 0 } }, + { 0x3ff60000, { 6, 7, 0 } }, + { 0x3ff70000, { 7, 7, 0 } }, + { 0x3ff80000, { 8, 7, 0 } }, + { 0x3ff90000, { 9, 7, 0 } }, + { 0x3ffa0000, { 10, 7, 0 } }, + { 0x3ffb0000, { 11, 7, 0 } }, + { 0x3ffc0000, { 12, 7, 0 } }, + { 0x3ffd0000, { 13, 7, 0 } }, + { 0x3ffe0000, { 14, 7, 0 } }, + { 0x3fff0000, { 15, 7, 0 } }, + { 0xc0000000, { 0, 8, 0 } }, + { 0xc0010000, { 1, 8, 0 } }, + { 0xc0020000, { 2, 8, 0 } }, + { 0xc0030000, { 3, 8, 0 } }, + { 0xc0040000, { 4, 8, 0 } }, + { 0xc0050000, { 5, 8, 0 } }, + { 0xc0060000, { 6, 8, 0 } }, + { 0xc0070000, { 7, 8, 0 } }, + { 0xc0080000, { 8, 8, 0 } }, + { 0xc0090000, { 9, 8, 0 } }, + { 0xc00a0000, { 10, 8, 0 } }, + { 0xc00b0000, { 11, 8, 0 } }, + { 0xc00c0000, { 12, 8, 0 } }, + { 0xc00d0000, { 13, 8, 0 } }, + { 0xc00e0000, { 14, 8, 0 } }, + { 0xc00f0000, { 15, 8, 0 } }, + { 0xc0100000, { 0, 9, 0 } }, + { 0xc0110000, { 1, 9, 0 } }, + { 0xc0120000, { 2, 9, 0 } }, + { 0xc0130000, { 3, 9, 0 } }, + { 0xc0140000, { 4, 9, 0 } }, + { 0xc0150000, { 5, 9, 0 } }, + { 0xc0160000, { 6, 9, 0 } }, + { 0xc0170000, { 7, 9, 0 } }, + { 0xc0180000, { 8, 9, 0 } }, + { 0xc0190000, { 9, 9, 0 } }, + { 0xc01a0000, { 10, 9, 0 } }, + { 0xc01b0000, { 11, 9, 0 } }, + { 0xc01c0000, { 12, 9, 0 } }, + { 0xc01d0000, { 13, 9, 0 } }, + { 0xc01e0000, { 14, 9, 0 } }, + { 0xc01f0000, { 15, 9, 0 } }, + { 0xc0200000, { 0, 10, 0 } }, + { 0xc0210000, { 1, 10, 0 } }, + { 0xc0220000, { 2, 10, 0 } }, + { 0xc0230000, { 3, 10, 0 } }, + { 0xc0240000, { 4, 10, 0 } }, + { 0xc0250000, { 5, 10, 0 } }, + { 0xc0260000, { 6, 10, 0 } }, + { 0xc0270000, { 7, 10, 0 } }, + { 0xc0280000, { 8, 10, 0 } }, + { 0xc0290000, { 9, 10, 0 } }, + { 0xc02a0000, { 10, 10, 0 } }, + { 0xc02b0000, { 11, 10, 0 } }, + { 0xc02c0000, { 12, 10, 0 } }, + { 0xc02d0000, { 13, 10, 0 } }, + { 0xc02e0000, { 14, 10, 0 } }, + { 0xc02f0000, { 15, 10, 0 } }, + { 0xc0300000, { 0, 11, 0 } }, + { 0xc0310000, { 1, 11, 0 } }, + { 0xc0320000, { 2, 11, 0 } }, + { 0xc0330000, { 3, 11, 0 } }, + { 0xc0340000, { 4, 11, 0 } }, + { 0xc0350000, { 5, 11, 0 } }, + { 0xc0360000, { 6, 11, 0 } }, + { 0xc0370000, { 7, 11, 0 } }, + { 0xc0380000, { 8, 11, 0 } }, + { 0xc0390000, { 9, 11, 0 } }, + { 0xc03a0000, { 10, 11, 0 } }, + { 0xc03b0000, { 11, 11, 0 } }, + { 0xc03c0000, { 12, 11, 0 } }, + { 0xc03d0000, { 13, 11, 0 } }, + { 0xc03e0000, { 14, 11, 0 } }, + { 0xc03f0000, { 15, 11, 0 } }, + { 0xbfc00000, { 0, 12, 0 } }, + { 0xbfc10000, { 1, 12, 0 } }, + { 0xbfc20000, { 2, 12, 0 } }, + { 0xbfc30000, { 3, 12, 0 } }, + { 0xbfc40000, { 4, 12, 0 } }, + { 0xbfc50000, { 5, 12, 0 } }, + { 0xbfc60000, { 6, 12, 0 } }, + { 0xbfc70000, { 7, 12, 0 } }, + { 0xbfc80000, { 8, 12, 0 } }, + { 0xbfc90000, { 9, 12, 0 } }, + { 0xbfca0000, { 10, 12, 0 } }, + { 0xbfcb0000, { 11, 12, 0 } }, + { 0xbfcc0000, { 12, 12, 0 } }, + { 0xbfcd0000, { 13, 12, 0 } }, + { 0xbfce0000, { 14, 12, 0 } }, + { 0xbfcf0000, { 15, 12, 0 } }, + { 0xbfd00000, { 0, 13, 0 } }, + { 0xbfd10000, { 1, 13, 0 } }, + { 0xbfd20000, { 2, 13, 0 } }, + { 0xbfd30000, { 3, 13, 0 } }, + { 0xbfd40000, { 4, 13, 0 } }, + { 0xbfd50000, { 5, 13, 0 } }, + { 0xbfd60000, { 6, 13, 0 } }, + { 0xbfd70000, { 7, 13, 0 } }, + { 0xbfd80000, { 8, 13, 0 } }, + { 0xbfd90000, { 9, 13, 0 } }, + { 0xbfda0000, { 10, 13, 0 } }, + { 0xbfdb0000, { 11, 13, 0 } }, + { 0xbfdc0000, { 12, 13, 0 } }, + { 0xbfdd0000, { 13, 13, 0 } }, + { 0xbfde0000, { 14, 13, 0 } }, + { 0xbfdf0000, { 15, 13, 0 } }, + { 0xbfe00000, { 0, 14, 0 } }, + { 0xbfe10000, { 1, 14, 0 } }, + { 0xbfe20000, { 2, 14, 0 } }, + { 0xbfe30000, { 3, 14, 0 } }, + { 0xbfe40000, { 4, 14, 0 } }, + { 0xbfe50000, { 5, 14, 0 } }, + { 0xbfe60000, { 6, 14, 0 } }, + { 0xbfe70000, { 7, 14, 0 } }, + { 0xbfe80000, { 8, 14, 0 } }, + { 0xbfe90000, { 9, 14, 0 } }, + { 0xbfea0000, { 10, 14, 0 } }, + { 0xbfeb0000, { 11, 14, 0 } }, + { 0xbfec0000, { 12, 14, 0 } }, + { 0xbfed0000, { 13, 14, 0 } }, + { 0xbfee0000, { 14, 14, 0 } }, + { 0xbfef0000, { 15, 14, 0 } }, + { 0xbff00000, { 0, 15, 0 } }, + { 0xbff10000, { 1, 15, 0 } }, + { 0xbff20000, { 2, 15, 0 } }, + { 0xbff30000, { 3, 15, 0 } }, + { 0xbff40000, { 4, 15, 0 } }, + { 0xbff50000, { 5, 15, 0 } }, + { 0xbff60000, { 6, 15, 0 } }, + { 0xbff70000, { 7, 15, 0 } }, + { 0xbff80000, { 8, 15, 0 } }, + { 0xbff90000, { 9, 15, 0 } }, + { 0xbffa0000, { 10, 15, 0 } }, + { 0xbffb0000, { 11, 15, 0 } }, + { 0xbffc0000, { 12, 15, 0 } }, + { 0xbffd0000, { 13, 15, 0 } }, + { 0xbffe0000, { 14, 15, 0 } }, + { 0xbfff0000, { 15, 15, 0 } },
new file mode 100644 --- /dev/null +++ b/js/src/jit/arm/gen-double-encoder-table.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +"""Generate tables of immediately-encodable VFP doubles. + +DOES NOT get automatically run during the build process. If you need to +modify this file (which is unlikely), you must re-run this script: + +python gen-double-encode-table.py > $(topsrcdir)/path/to/DoubleEntryTable.tbl +""" + +import operator + +def rep(bit, count): + return reduce(operator.ior, [bit << c for c in range(count)]) + +def encodeDouble(value): + """Generate an ARM ARM 'VFP modified immediate constant' with format: + aBbbbbbb bbcdefgh 000... + + We will return the top 32 bits of the double; the rest are 0.""" + assert (0 <= value) and (value <= 255) + a = value >> 7 + b = (value >> 6) & 1 + B = int(b == 0) + cdefgh = value & 0x3f + return (a << 31) | (B << 30) | (rep(b, 8) << 22) | cdefgh << 16 + +print '/* THIS FILE IS AUTOMATICALLY GENERATED BY gen-double-encode-table.py. */' +for i in range(256): + print ' { 0x%08x, { %d, %d, 0 } },' % (encodeDouble(i), i & 0xf, i >> 4)
--- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -249,17 +249,17 @@ void JSRuntime::sweepAtoms() { if (!atoms_) return; for (AtomSet::Enum e(*atoms_); !e.empty(); e.popFront()) { AtomStateEntry entry = e.front(); JSAtom *atom = entry.asPtr(); - bool isDying = IsStringAboutToBeFinalized(&atom); + bool isDying = IsStringAboutToBeFinalizedFromAnyThread(&atom); /* Pinned or interned key cannot be finalized. */ MOZ_ASSERT_IF(hasContexts() && entry.isTagged(), !isDying); if (isDying) e.removeFront(); } }
--- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -96,18 +96,19 @@ js::TraceCycleDetectionSet(JSTracer *trc } void JSCompartment::sweepCallsiteClones() { if (callsiteClones.initialized()) { for (CallsiteCloneTable::Enum e(callsiteClones); !e.empty(); e.popFront()) { CallsiteCloneKey key = e.front().key(); - if (IsObjectAboutToBeFinalized(&key.original) || IsScriptAboutToBeFinalized(&key.script) || - IsObjectAboutToBeFinalized(e.front().value().unsafeGet())) + if (IsObjectAboutToBeFinalizedFromAnyThread(&key.original) || + IsScriptAboutToBeFinalizedFromAnyThread(&key.script) || + IsObjectAboutToBeFinalizedFromAnyThread(e.front().value().unsafeGet())) { e.removeFront(); } else if (key != e.front().key()) { e.rekeyFront(key); } } } }
--- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -530,54 +530,54 @@ JSCompartment::markRoots(JSTracer *trc) if (jitCompartment_) jitCompartment_->mark(trc, this); /* * If a compartment is on-stack, we mark its global so that * JSContext::global() remains valid. */ - if (enterCompartmentDepth && global_) + if (enterCompartmentDepth && global_.unbarrieredGet()) MarkObjectRoot(trc, global_.unsafeGet(), "on-stack compartment global"); } void JSCompartment::sweepInnerViews() { - innerViews.sweep(runtimeFromMainThread()); + innerViews.sweep(runtimeFromAnyThread()); } void JSCompartment::sweepTypeObjectTables() { sweepNewTypeObjectTable(newTypeObjects); sweepNewTypeObjectTable(lazyTypeObjects); } void JSCompartment::sweepSavedStacks() { - savedStacks_.sweep(runtimeFromMainThread()); + savedStacks_.sweep(runtimeFromAnyThread()); } void JSCompartment::sweepGlobalObject(FreeOp *fop) { - if (global_ && IsObjectAboutToBeFinalized(global_.unsafeGet())) { + if (global_.unbarrieredGet() && IsObjectAboutToBeFinalizedFromAnyThread(global_.unsafeGet())) { if (debugMode()) Debugger::detachAllDebuggersFromGlobal(fop, global_); global_.set(nullptr); } } void JSCompartment::sweepSelfHostingScriptSource() { - if (selfHostingScriptSource && - IsObjectAboutToBeFinalized((JSObject **) selfHostingScriptSource.unsafeGet())) + if (selfHostingScriptSource.unbarrieredGet() && + IsObjectAboutToBeFinalizedFromAnyThread((JSObject **) selfHostingScriptSource.unsafeGet())) { selfHostingScriptSource.set(nullptr); } } void JSCompartment::sweepJitCompartment(FreeOp *fop) { @@ -588,23 +588,23 @@ JSCompartment::sweepJitCompartment(FreeO void JSCompartment::sweepRegExps() { /* * JIT code increments activeWarmUpCounter for any RegExpShared used by jit * code for the lifetime of the JIT script. Thus, we must perform * sweeping after clearing jit code. */ - regExps.sweep(runtimeFromMainThread()); + regExps.sweep(runtimeFromAnyThread()); } void JSCompartment::sweepDebugScopes() { - JSRuntime *rt = runtimeFromMainThread(); + JSRuntime *rt = runtimeFromAnyThread(); if (debugScopes) debugScopes->sweep(rt); } void JSCompartment::sweepWeakMaps() { /* Finalize unreachable (key,value) pairs in all weak maps. */ @@ -614,36 +614,36 @@ JSCompartment::sweepWeakMaps() void JSCompartment::sweepNativeIterators() { /* Sweep list of native iterators. */ NativeIterator *ni = enumerators->next(); while (ni != enumerators) { JSObject *iterObj = ni->iterObj(); NativeIterator *next = ni->next(); - if (gc::IsObjectAboutToBeFinalized(&iterObj)) + if (gc::IsObjectAboutToBeFinalizedFromAnyThread(&iterObj)) ni->unlink(); ni = next; } } /* * Remove dead wrappers from the table. We must sweep all compartments, since * string entries in the crossCompartmentWrappers table are not marked during * markCrossCompartmentWrappers. */ void JSCompartment::sweepCrossCompartmentWrappers() { /* Remove dead wrappers from the table. */ for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { CrossCompartmentKey key = e.front().key(); - bool keyDying = IsCellAboutToBeFinalized(&key.wrapped); - bool valDying = IsValueAboutToBeFinalized(e.front().value().unsafeGet()); - bool dbgDying = key.debugger && IsObjectAboutToBeFinalized(&key.debugger); + bool keyDying = IsCellAboutToBeFinalizedFromAnyThread(&key.wrapped); + bool valDying = IsValueAboutToBeFinalizedFromAnyThread(e.front().value().unsafeGet()); + bool dbgDying = key.debugger && IsObjectAboutToBeFinalizedFromAnyThread(&key.debugger); if (keyDying || valDying || dbgDying) { MOZ_ASSERT(key.kind != CrossCompartmentKey::StringWrapper); e.removeFront(); } else if (key.wrapped != e.front().key().wrapped || key.debugger != e.front().key().debugger) { e.rekeyFront(key); }
--- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -249,16 +249,17 @@ struct JSCompartment /* Set of initial shapes in the compartment. */ js::InitialShapeSet initialShapes; void sweepInitialShapeTable(); /* Set of default 'new' or lazy types in the compartment. */ js::types::TypeObjectWithNewScriptSet newTypeObjects; js::types::TypeObjectWithNewScriptSet lazyTypeObjects; void sweepNewTypeObjectTable(js::types::TypeObjectWithNewScriptSet &table); + #ifdef JSGC_HASH_TABLE_CHECKS void checkTypeObjectTablesAfterMovingGC(); void checkTypeObjectTableAfterMovingGC(js::types::TypeObjectWithNewScriptSet &table); void checkInitialShapesTableAfterMovingGC(); void checkWrapperMapAfterMovingGC(); #endif /*
--- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -2145,39 +2145,16 @@ js::ReportIncompatible(JSContext *cx, Ca JSAutoByteString funNameBytes; if (const char *funName = GetFunctionNameBytes(cx, fun, &funNameBytes)) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_METHOD, funName, "method", InformalValueTypeName(call.thisv())); } } } -bool -JSObject::hasIdempotentProtoChain() const -{ - // Return false if obj (or an object on its proto chain) is non-native or - // has a resolve or lookup hook. - JSObject *obj = const_cast<JSObject *>(this); - while (true) { - if (!obj->isNative()) - return false; - - JSResolveOp resolve = obj->getClass()->resolve; - if (resolve != JS_ResolveStub && resolve != (JSResolveOp) js::fun_resolve) - return false; - - if (obj->getOps()->lookupProperty || obj->getOps()->lookupGeneric || obj->getOps()->lookupElement) - return false; - - obj = obj->getProto(); - if (!obj) - return true; - } -} - namespace JS { namespace detail { JS_PUBLIC_API(void) CheckIsValidConstructible(Value calleev) { JSObject *callee = &calleev.toObject(); if (callee->is<JSFunction>())
--- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -658,74 +658,74 @@ FreeChunk(JSRuntime *rt, Chunk *p) { UnmapPages(static_cast<void *>(p), ChunkSize); } /* Must be called with the GC lock taken. */ inline Chunk * ChunkPool::get(JSRuntime *rt) { - Chunk *chunk = emptyChunkListHead; + Chunk *chunk = head_; if (!chunk) { - MOZ_ASSERT(!emptyCount); + MOZ_ASSERT(!count_); return nullptr; } - MOZ_ASSERT(emptyCount); - emptyChunkListHead = chunk->info.next; - --emptyCount; + MOZ_ASSERT(count_); + head_ = chunk->info.next; + --count_; return chunk; } /* Must be called either during the GC or with the GC lock taken. */ inline void ChunkPool::put(Chunk *chunk) { chunk->info.age = 0; - chunk->info.next = emptyChunkListHead; - emptyChunkListHead = chunk; - emptyCount++; + chunk->info.next = head_; + head_ = chunk; + count_++; } inline Chunk * ChunkPool::Enum::front() { Chunk *chunk = *chunkp; - MOZ_ASSERT_IF(chunk, pool.getEmptyCount() != 0); + MOZ_ASSERT_IF(chunk, pool.count() != 0); return chunk; } inline void ChunkPool::Enum::popFront() { MOZ_ASSERT(!empty()); chunkp = &front()->info.next; } inline void ChunkPool::Enum::removeAndPopFront() { MOZ_ASSERT(!empty()); *chunkp = front()->info.next; - --pool.emptyCount; + --pool.count_; } /* Must be called either during the GC or with the GC lock taken. */ Chunk * GCRuntime::expireChunkPool(bool shrinkBuffers, bool releaseAll) { /* * Return old empty chunks to the system while preserving the order of * other chunks in the list. This way, if the GC runs several times * without emptying the list, the older chunks will stay at the tail * and are more likely to reach the max age. */ Chunk *freeList = nullptr; unsigned freeChunkCount = 0; - for (ChunkPool::Enum e(chunkPool); !e.empty(); ) { + for (ChunkPool::Enum e(emptyChunks); !e.empty(); ) { Chunk *chunk = e.front(); MOZ_ASSERT(chunk->unused()); MOZ_ASSERT(!chunkSet.has(chunk)); if (releaseAll || freeChunkCount >= tunables.maxEmptyChunkCount() || (freeChunkCount >= tunables.minEmptyChunkCount() && (shrinkBuffers || chunk->info.age == MAX_EMPTY_CHUNK_AGE))) { e.removeAndPopFront(); @@ -734,19 +734,19 @@ GCRuntime::expireChunkPool(bool shrinkBu freeList = chunk; } else { /* Keep the chunk but increase its age. */ ++freeChunkCount; ++chunk->info.age; e.popFront(); } } - MOZ_ASSERT(chunkPool.getEmptyCount() <= tunables.maxEmptyChunkCount()); - MOZ_ASSERT_IF(shrinkBuffers, chunkPool.getEmptyCount() <= tunables.minEmptyChunkCount()); - MOZ_ASSERT_IF(releaseAll, chunkPool.getEmptyCount() == 0); + MOZ_ASSERT(emptyChunks.count() <= tunables.maxEmptyChunkCount()); + MOZ_ASSERT_IF(shrinkBuffers, emptyChunks.count() <= tunables.minEmptyChunkCount()); + MOZ_ASSERT_IF(releaseAll, emptyChunks.count() == 0); return freeList; } void GCRuntime::freeChunkList(Chunk *chunkListHead) { while (Chunk *chunk = chunkListHead) { MOZ_ASSERT(!chunk->info.numArenasFreeCommitted); @@ -1032,29 +1032,29 @@ Chunk::releaseArena(ArenaHeader *aheader } void GCRuntime::moveChunkToFreePool(Chunk *chunk) { MOZ_ASSERT(chunk->unused()); MOZ_ASSERT(chunkSet.has(chunk)); chunkSet.remove(chunk); - chunkPool.put(chunk); + emptyChunks.put(chunk); } inline bool GCRuntime::wantBackgroundAllocation() const { /* * To minimize memory waste we do not want to run the background chunk * allocation if we have empty chunks or when the runtime needs just few * of them. */ return helperState.canBackgroundAllocate() && - chunkPool.getEmptyCount() < tunables.minEmptyChunkCount() && + emptyChunks.count() < tunables.minEmptyChunkCount() && chunkSet.count() >= 4; } class js::gc::AutoMaybeStartBackgroundAllocation { private: JSRuntime *runtime; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER @@ -1083,17 +1083,17 @@ class js::gc::AutoMaybeStartBackgroundAl Chunk * GCRuntime::pickChunk(Zone *zone, AutoMaybeStartBackgroundAllocation &maybeStartBackgroundAllocation) { Chunk **listHeadp = getAvailableChunkList(zone); Chunk *chunk = *listHeadp; if (chunk) return chunk; - chunk = chunkPool.get(rt); + chunk = emptyChunks.get(rt); if (!chunk) { chunk = Chunk::allocate(rt); if (!chunk) return nullptr; MOZ_ASSERT(chunk->info.numArenasFreeCommitted == 0); } MOZ_ASSERT(chunk->unused()); @@ -1507,19 +1507,19 @@ GCRuntime::getParameter(JSGCParamKey key return uint32_t(tunables.gcMaxBytes()); case JSGC_MAX_MALLOC_BYTES: return maxMallocBytes; case JSGC_BYTES: return uint32_t(usage.gcBytes()); case JSGC_MODE: return uint32_t(mode); case JSGC_UNUSED_CHUNKS: - return uint32_t(chunkPool.getEmptyCount()); + return uint32_t(emptyChunks.count()); case JSGC_TOTAL_CHUNKS: - return uint32_t(chunkSet.count() + chunkPool.getEmptyCount()); + return uint32_t(chunkSet.count() + emptyChunks.count()); case JSGC_SLICE_TIME_BUDGET: return uint32_t(sliceBudget > 0 ? sliceBudget / PRMJ_USEC_PER_MSEC : 0); case JSGC_MARK_STACK_LIMIT: return marker.maxCapacity(); case JSGC_HIGH_FREQUENCY_TIME_LIMIT: return tunables.highFrequencyThresholdUsec(); case JSGC_HIGH_FREQUENCY_LOW_LIMIT: return tunables.highFrequencyLowLimitBytes() / 1024 / 1024; @@ -2193,25 +2193,26 @@ RelocateCell(Zone *zone, TenuredCell *sr // Copy source cell contents to destination. memcpy(dst, src, thingSize); if (thingKind <= FINALIZE_OBJECT_LAST) { JSObject *srcObj = static_cast<JSObject *>(static_cast<Cell *>(src)); JSObject *dstObj = static_cast<JSObject *>(static_cast<Cell *>(dst)); // Fixup the pointer to inline object elements if necessary. - if (srcObj->hasFixedElements()) - dstObj->setFixedElements(); + if (srcObj->isNative() && srcObj->as<NativeObject>().hasFixedElements()) + dstObj->as<NativeObject>().setFixedElements(); // Call object moved hook if present. if (JSObjectMovedOp op = srcObj->getClass()->ext.objectMovedOp) op(dstObj, srcObj); MOZ_ASSERT_IF(dstObj->isNative(), - !PtrIsInRange((const Value*)dstObj->getDenseElements(), src, thingSize)); + !PtrIsInRange((const Value*)dstObj->as<NativeObject>().getDenseElements(), + src, thingSize)); } // Copy the mark bits. dst->copyMarkBitsFrom(src); // Mark source cell as forwarded and leave a pointer to the destination. RelocationOverlay* overlay = RelocationOverlay::fromCell(src); overlay->forwardTo(dst); @@ -3249,17 +3250,19 @@ GCHelperState::startBackgroundThread(Sta HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER); } void GCHelperState::waitForBackgroundThread() { MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt)); +#ifdef DEBUG rt->gc.lockOwner = nullptr; +#endif PR_WaitCondVar(done, PR_INTERVAL_NO_TIMEOUT); #ifdef DEBUG rt->gc.lockOwner = PR_GetCurrentThread(); #endif } void GCHelperState::work() @@ -3294,17 +3297,17 @@ GCHelperState::work() AutoUnlockGC unlock(rt); chunk = Chunk::allocate(rt); } /* OOM stops the background allocation. */ if (!chunk) break; MOZ_ASSERT(chunk->info.numArenasFreeCommitted == 0); - rt->gc.chunkPool.put(chunk); + rt->gc.emptyChunks.put(chunk); } while (state() == ALLOCATING && rt->gc.wantBackgroundAllocation()); MOZ_ASSERT(state() == ALLOCATING || state() == CANCEL_ALLOCATION); break; } case CANCEL_ALLOCATION: break; @@ -4604,16 +4607,107 @@ GCRuntime::endMarkingZoneGroup() for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) { MOZ_ASSERT(zone->isGCMarkingGray()); zone->setGCState(Zone::Mark); } MOZ_ASSERT(marker.isDrained()); marker.setMarkColorBlack(); } +#define MAKE_GC_PARALLEL_TASK(name) \ + class name : public GCParallelTask {\ + JSRuntime *runtime;\ + virtual void run() MOZ_OVERRIDE;\ + public:\ + name (JSRuntime *rt) : runtime(rt) {}\ + } +MAKE_GC_PARALLEL_TASK(SweepAtomsTask); +MAKE_GC_PARALLEL_TASK(SweepInnerViewsTask); +MAKE_GC_PARALLEL_TASK(SweepCCWrappersTask); +MAKE_GC_PARALLEL_TASK(SweepBaseShapesTask); +MAKE_GC_PARALLEL_TASK(SweepInitialShapesTask); +MAKE_GC_PARALLEL_TASK(SweepTypeObjectsTask); +MAKE_GC_PARALLEL_TASK(SweepRegExpsTask); +MAKE_GC_PARALLEL_TASK(SweepMiscTask); + +/* virtual */ void +SweepAtomsTask::run() +{ + runtime->sweepAtoms(); +} + +/* virtual */ void +SweepInnerViewsTask::run() +{ + for (GCCompartmentGroupIter c(runtime); !c.done(); c.next()) + c->sweepInnerViews(); +} + +/* virtual */ void +SweepCCWrappersTask::run() +{ + for (GCCompartmentGroupIter c(runtime); !c.done(); c.next()) + c->sweepCrossCompartmentWrappers(); +} + +/* virtual */ void +SweepBaseShapesTask::run() +{ + for (GCCompartmentGroupIter c(runtime); !c.done(); c.next()) + c->sweepBaseShapeTable(); +} + +/* virtual */ void +SweepInitialShapesTask::run() +{ + for (GCCompartmentGroupIter c(runtime); !c.done(); c.next()) + c->sweepInitialShapeTable(); +} + +/* virtual */ void +SweepTypeObjectsTask::run() +{ + for (GCCompartmentGroupIter c(runtime); !c.done(); c.next()) + c->sweepTypeObjectTables(); +} + +/* virtual */ void +SweepRegExpsTask::run() +{ + for (GCCompartmentGroupIter c(runtime); !c.done(); c.next()) + c->sweepRegExps(); +} + +/* virtual */ void +SweepMiscTask::run() +{ + for (GCCompartmentGroupIter c(runtime); !c.done(); c.next()) { + c->sweepCallsiteClones(); + c->sweepSavedStacks(); + c->sweepSelfHostingScriptSource(); + c->sweepNativeIterators(); + } +} + +void +GCRuntime::startTask(GCParallelTask &task, gcstats::Phase phase) +{ + if (!task.startWithLockHeld()) { + gcstats::AutoPhase ap(stats, phase); + task.runFromMainThread(rt); + } +} + +void +GCRuntime::joinTask(GCParallelTask &task, gcstats::Phase phase) +{ + gcstats::AutoPhase ap(stats, task, phase); + task.joinWithLockHeld(); +} + void GCRuntime::beginSweepingZoneGroup() { /* * Begin sweeping the group of zones in gcCurrentZoneGroup, * performing actions that must be done before yielding to caller. */ @@ -4633,85 +4727,61 @@ GCRuntime::beginSweepingZoneGroup() rt->sweepZoneCallback(zone); zone->gcLastZoneGroupIndex = zoneGroupIndex; } validateIncrementalMarking(); FreeOp fop(rt); + SweepAtomsTask sweepAtomsTask(rt); + SweepInnerViewsTask sweepInnerViewsTask(rt); + SweepCCWrappersTask sweepCCWrappersTask(rt); + SweepBaseShapesTask sweepBaseShapesTask(rt); + SweepInitialShapesTask sweepInitialShapesTask(rt); + SweepTypeObjectsTask sweepTypeObjectsTask(rt); + SweepRegExpsTask sweepRegExpsTask(rt); + SweepMiscTask sweepMiscTask(rt); { gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_START); callFinalizeCallbacks(&fop, JSFINALIZE_GROUP_START); callWeakPointerCallbacks(); } if (sweepingAtoms) { - gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_ATOMS); - rt->sweepAtoms(); + AutoLockHelperThreadState helperLock; + startTask(sweepAtomsTask, gcstats::PHASE_SWEEP_ATOMS); } { gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_COMPARTMENTS); gcstats::AutoSCC scc(stats, zoneGroupIndex); { - gcstats::AutoPhase apiv(stats, gcstats::PHASE_SWEEP_INNER_VIEWS); - for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { - c->sweepInnerViews(); - } + AutoLockHelperThreadState helperLock; + startTask(sweepInnerViewsTask, gcstats::PHASE_SWEEP_INNER_VIEWS); + startTask(sweepCCWrappersTask, gcstats::PHASE_SWEEP_CC_WRAPPER); + startTask(sweepBaseShapesTask, gcstats::PHASE_SWEEP_BASE_SHAPE); + startTask(sweepInitialShapesTask, gcstats::PHASE_SWEEP_INITIAL_SHAPE); + startTask(sweepTypeObjectsTask, gcstats::PHASE_SWEEP_TYPE_OBJECT); + startTask(sweepRegExpsTask, gcstats::PHASE_SWEEP_REGEXP); + startTask(sweepMiscTask, gcstats::PHASE_SWEEP_MISC); } + // The remainder of the of the tasks run in parallel on the main + // thread until we join, below. { - gcstats::AutoPhase apccw(stats, gcstats::PHASE_SWEEP_CC_WRAPPER); - for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { - c->sweepCrossCompartmentWrappers(); - } - } - - { - gcstats::AutoPhase apbs(stats, gcstats::PHASE_SWEEP_BASE_SHAPE); - for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { - c->sweepBaseShapeTable(); - } - } - - { - gcstats::AutoPhase apis(stats, gcstats::PHASE_SWEEP_INITIAL_SHAPE); + gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_MISC); + for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { - c->sweepInitialShapeTable(); - } - } - - { - gcstats::AutoPhase apto(stats, gcstats::PHASE_SWEEP_TYPE_OBJECT); - for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { - c->sweepTypeObjectTables(); - } - } - - { - gcstats::AutoPhase apre(stats, gcstats::PHASE_SWEEP_REGEXP); - for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { - c->sweepRegExps(); - } - } - - { - gcstats::AutoPhase apmisc(stats, gcstats::PHASE_SWEEP_MISC); - for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) { - c->sweepCallsiteClones(); - c->sweepSavedStacks(); c->sweepGlobalObject(&fop); - c->sweepSelfHostingScriptSource(); c->sweepDebugScopes(); c->sweepJitCompartment(&fop); c->sweepWeakMaps(); - c->sweepNativeIterators(); } // Bug 1071218: the following two methods have not yet been // refactored to work on a single zone-group at once. // Collect watch points associated with unreachable objects. WatchpointMap::sweepAll(rt); @@ -4741,16 +4811,36 @@ GCRuntime::beginSweepingZoneGroup() } } if (sweepingAtoms) { gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_SYMBOL_REGISTRY); rt->symbolRegistry().sweep(); } + // Rejoin our off-main-thread tasks. + if (sweepingAtoms) { + AutoLockHelperThreadState helperLock; + joinTask(sweepAtomsTask, gcstats::PHASE_SWEEP_ATOMS); + } + + { + gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_COMPARTMENTS); + gcstats::AutoSCC scc(stats, zoneGroupIndex); + + AutoLockHelperThreadState helperLock; + joinTask(sweepInnerViewsTask, gcstats::PHASE_SWEEP_INNER_VIEWS); + joinTask(sweepCCWrappersTask, gcstats::PHASE_SWEEP_CC_WRAPPER); + joinTask(sweepBaseShapesTask, gcstats::PHASE_SWEEP_BASE_SHAPE); + joinTask(sweepInitialShapesTask, gcstats::PHASE_SWEEP_INITIAL_SHAPE); + joinTask(sweepTypeObjectsTask, gcstats::PHASE_SWEEP_TYPE_OBJECT); + joinTask(sweepRegExpsTask, gcstats::PHASE_SWEEP_REGEXP); + joinTask(sweepMiscTask, gcstats::PHASE_SWEEP_MISC); + } + /* * Queue all GC things in all zones for sweeping, either in the * foreground or on the background thread. * * Note that order is important here for the background case. * * Objects are finalized immediately but this may change in the future. */
--- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -1065,16 +1065,57 @@ class GCHelperState } bool shouldShrink() const { MOZ_ASSERT(isBackgroundSweeping()); return shrinkFlag; } }; +// A generic task used to dispatch work to the helper thread system. +// Users should derive from GCParallelTask add what data they need and +// override |run|. +class GCParallelTask +{ + // The state of the parallel computation. + enum TaskState { + NotStarted, + Dispatched, + Finished, + } state; + + // Amount of time this task took to execute. + uint64_t duration_; + + protected: + virtual void run() = 0; + + public: + GCParallelTask() : state(NotStarted), duration_(0) {} + + int64_t duration() const { return duration_; } + + // The simple interface to a parallel task works exactly like pthreads. + bool start(); + void join(); + + // If multiple tasks are to be started or joined at once, it is more + // efficient to take the helper thread lock once and use these methods. + bool startWithLockHeld(); + void joinWithLockHeld(); + + // Instead of dispatching to a helper, run the task on the main thread. + void runFromMainThread(JSRuntime *rt); + + // This should be friended to HelperThread, but cannot be because it + // would introduce several circular dependencies. + public: + void runFromHelperThread(); +}; + struct GCChunkHasher { typedef gc::Chunk *Lookup; /* * Strip zeros for better distribution after multiplying by the golden * ratio. */ static HashNumber hash(gc::Chunk *chunk) {
--- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -4779,27 +4779,27 @@ TypeCompartment::sweep(FreeOp *fop) e.rekeyFront(key); } } } void JSCompartment::sweepNewTypeObjectTable(TypeObjectWithNewScriptSet &table) { - MOZ_ASSERT(zone()->isCollecting()); + MOZ_ASSERT(zone()->runtimeFromAnyThread()->isHeapCollecting()); if (table.initialized()) { for (TypeObjectWithNewScriptSet::Enum e(table); !e.empty(); e.popFront()) { TypeObjectWithNewScriptEntry entry = e.front(); - if (IsTypeObjectAboutToBeFinalized(entry.object.unsafeGet()) || - (entry.newFunction && IsObjectAboutToBeFinalized(&entry.newFunction))) + if (IsTypeObjectAboutToBeFinalizedFromAnyThread(entry.object.unsafeGet()) || + (entry.newFunction && IsObjectAboutToBeFinalizedFromAnyThread(&entry.newFunction))) { e.removeFront(); } else { /* Any rekeying necessary is handled by fixupNewTypeObjectTable() below. */ - MOZ_ASSERT(entry.object == e.front().object); + MOZ_ASSERT(entry.object.unbarrieredGet() == e.front().object.unbarrieredGet()); MOZ_ASSERT(entry.newFunction == e.front().newFunction); } } } } #ifdef JSGC_COMPACTING
--- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -30,16 +30,17 @@ #include "vm/GlobalObject.h" #include "vm/NumericConversions.h" #include "vm/StringBuffer.h" #include "jsatominlines.h" #include "vm/NumberObject-inl.h" +#include "vm/ObjectImpl-inl.h" #include "vm/String-inl.h" using namespace js; using namespace js::types; using mozilla::Abs; using mozilla::ArrayLength; using mozilla::MinNumberValue;
--- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -6,17 +6,16 @@ /* * JS object implementation. */ #include "jsobjinlines.h" #include "mozilla/ArrayUtils.h" -#include "mozilla/CheckedInt.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/MemoryReporting.h" #include "mozilla/TemplateLib.h" #include <string.h> #include "jsapi.h" #include "jsarray.h" @@ -68,20 +67,16 @@ #include "vm/StringObject-inl.h" using namespace js; using namespace js::gc; using namespace js::types; using mozilla::DebugOnly; using mozilla::Maybe; -using mozilla::RoundUpPow2; - -JS_STATIC_ASSERT(int32_t((NativeObject::NELEMENTS_LIMIT - 1) * sizeof(Value)) == - int64_t((NativeObject::NELEMENTS_LIMIT - 1) * sizeof(Value))); static JSObject * CreateObjectConstructor(JSContext *cx, JSProtoKey key) { Rooted<GlobalObject*> self(cx, cx->global()); if (!GlobalObject::ensureConstructor(cx, self, JSProto_Function)) return nullptr; @@ -651,59 +646,16 @@ Reject(JSContext *cx, HandleId id, unsig if (throwError) return Throw(cx, id, errorNumber); *rval = false; return true; } static unsigned -ApplyAttributes(unsigned attrs, bool enumerable, bool writable, bool configurable) -{ - /* - * Respect the fact that some callers may want to preserve existing attributes as much as - * possible, or install defaults otherwise. - */ - if (attrs & JSPROP_IGNORE_ENUMERATE) { - attrs &= ~JSPROP_IGNORE_ENUMERATE; - if (enumerable) - attrs |= JSPROP_ENUMERATE; - else - attrs &= ~JSPROP_ENUMERATE; - } - if (attrs & JSPROP_IGNORE_READONLY) { - attrs &= ~JSPROP_IGNORE_READONLY; - // Only update the writability if it's relevant - if (!(attrs & (JSPROP_GETTER | JSPROP_SETTER))) { - if (!writable) - attrs |= JSPROP_READONLY; - else - attrs &= ~JSPROP_READONLY; - } - } - if (attrs & JSPROP_IGNORE_PERMANENT) { - attrs &= ~JSPROP_IGNORE_PERMANENT; - if (!configurable) - attrs |= JSPROP_PERMANENT; - else - attrs &= ~JSPROP_PERMANENT; - } - return attrs; -} - -static unsigned -ApplyOrDefaultAttributes(unsigned attrs, const Shape *shape = nullptr) -{ - bool enumerable = shape ? shape->enumerable() : false; - bool writable = shape ? shape->writable() : false; - bool configurable = shape ? shape->configurable() : false; - return ApplyAttributes(attrs, enumerable, writable, configurable); -} - -static unsigned ApplyOrDefaultAttributes(unsigned attrs, Handle<PropertyDescriptor> desc) { bool present = !!desc.object(); bool enumerable = present ? desc.isEnumerable() : false; bool writable = present ? !desc.isReadonly() : false; bool configurable = present ? !desc.isPermanent() : false; return ApplyAttributes(attrs, enumerable, writable, configurable); } @@ -1860,62 +1812,16 @@ js::CreateThisForFunction(JSContext *cx, TypeScript::SetThis(cx, calleeScript, types::Type::ObjectType(nobj)); return nobj; } return obj; } -/* - * Given pc pointing after a property accessing bytecode, return true if the - * access is "object-detecting" in the sense used by web scripts, e.g., when - * checking whether document.all is defined. - */ -static bool -Detecting(JSContext *cx, JSScript *script, jsbytecode *pc) -{ - MOZ_ASSERT(script->containsPC(pc)); - - /* General case: a branch or equality op follows the access. */ - JSOp op = JSOp(*pc); - if (js_CodeSpec[op].format & JOF_DETECTING) - return true; - - jsbytecode *endpc = script->codeEnd(); - - if (op == JSOP_NULL) { - /* - * Special case #1: handle (document.all == null). Don't sweat - * about JS1.2's revision of the equality operators here. - */ - if (++pc < endpc) { - op = JSOp(*pc); - return op == JSOP_EQ || op == JSOP_NE; - } - return false; - } - - if (op == JSOP_GETGNAME || op == JSOP_NAME) { - /* - * Special case #2: handle (document.all == undefined). Don't worry - * about a local variable named |undefined| shadowing the immutable - * global binding...because, really? - */ - JSAtom *atom = script->getAtom(GET_UINT32_INDEX(pc)); - if (atom == cx->names().undefined && - (pc += js_CodeSpec[op].length) < endpc) { - op = JSOp(*pc); - return op == JSOP_EQ || op == JSOP_NE || op == JSOP_STRICTEQ || op == JSOP_STRICTNE; - } - } - - return false; -} - /* static */ bool JSObject::nonNativeSetProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp, bool strict) { if (MOZ_UNLIKELY(obj->watched())) { WatchpointMap *wpmap = cx->compartment()->watchpointMap; if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp)) return false; @@ -2936,673 +2842,16 @@ js_InitClass(JSContext *cx, HandleObject { return nullptr; } return DefineConstructorAndPrototype(cx, obj, key, atom, protoProto, clasp, constructor, nargs, ps, fs, static_ps, static_fs, ctorp, ctorKind); } -/* static */ inline bool -NativeObject::updateSlotsForSpan(ThreadSafeContext *cx, - HandleNativeObject obj, size_t oldSpan, size_t newSpan) -{ - MOZ_ASSERT(cx->isThreadLocal(obj)); - MOZ_ASSERT(oldSpan != newSpan); - - size_t oldCount = dynamicSlotsCount(obj->numFixedSlots(), oldSpan, obj->getClass()); - size_t newCount = dynamicSlotsCount(obj->numFixedSlots(), newSpan, obj->getClass()); - - if (oldSpan < newSpan) { - if (oldCount < newCount && !growSlots(cx, obj, oldCount, newCount)) - return false; - - if (newSpan == oldSpan + 1) - obj->initSlotUnchecked(oldSpan, UndefinedValue()); - else - obj->initializeSlotRange(oldSpan, newSpan - oldSpan); - } else { - /* Trigger write barriers on the old slots before reallocating. */ - obj->prepareSlotRangeForOverwrite(newSpan, oldSpan); - obj->invalidateSlotRange(newSpan, oldSpan - newSpan); - - if (oldCount > newCount) - shrinkSlots(cx, obj, oldCount, newCount); - } - - return true; -} - -/* static */ bool -NativeObject::setLastProperty(ThreadSafeContext *cx, HandleNativeObject obj, HandleShape shape) -{ - MOZ_ASSERT(cx->isThreadLocal(obj)); - MOZ_ASSERT(!obj->inDictionaryMode()); - MOZ_ASSERT(!shape->inDictionary()); - MOZ_ASSERT(shape->compartment() == obj->compartment()); - MOZ_ASSERT(shape->numFixedSlots() == obj->numFixedSlots()); - - size_t oldSpan = obj->lastProperty()->slotSpan(); - size_t newSpan = shape->slotSpan(); - - if (oldSpan == newSpan) { - obj->shape_ = shape; - return true; - } - - if (!updateSlotsForSpan(cx, obj, oldSpan, newSpan)) - return false; - - obj->shape_ = shape; - return true; -} - -void -NativeObject::setLastPropertyShrinkFixedSlots(Shape *shape) -{ - MOZ_ASSERT(!inDictionaryMode()); - MOZ_ASSERT(!shape->inDictionary()); - MOZ_ASSERT(shape->compartment() == compartment()); - MOZ_ASSERT(lastProperty()->slotSpan() == shape->slotSpan()); - - DebugOnly<size_t> oldFixed = numFixedSlots(); - DebugOnly<size_t> newFixed = shape->numFixedSlots(); - MOZ_ASSERT(newFixed < oldFixed); - MOZ_ASSERT(shape->slotSpan() <= oldFixed); - MOZ_ASSERT(shape->slotSpan() <= newFixed); - MOZ_ASSERT(dynamicSlotsCount(oldFixed, shape->slotSpan(), getClass()) == 0); - MOZ_ASSERT(dynamicSlotsCount(newFixed, shape->slotSpan(), getClass()) == 0); - - shape_ = shape; -} - -/* static */ bool -NativeObject::setSlotSpan(ThreadSafeContext *cx, HandleNativeObject obj, uint32_t span) -{ - MOZ_ASSERT(cx->isThreadLocal(obj)); - MOZ_ASSERT(obj->inDictionaryMode()); - - size_t oldSpan = obj->lastProperty()->base()->slotSpan(); - if (oldSpan == span) - return true; - - if (!updateSlotsForSpan(cx, obj, oldSpan, span)) - return false; - - obj->lastProperty()->base()->setSlotSpan(span); - return true; -} - -// This will not run the garbage collector. If a nursery cannot accomodate the slot array -// an attempt will be made to place the array in the tenured area. -static HeapSlot * -AllocateSlots(ThreadSafeContext *cx, JSObject *obj, uint32_t nslots) -{ -#ifdef JSGC_GENERATIONAL - if (cx->isJSContext()) - return cx->asJSContext()->runtime()->gc.nursery.allocateSlots(obj, nslots); -#endif -#ifdef JSGC_FJGENERATIONAL - if (cx->isForkJoinContext()) - return cx->asForkJoinContext()->nursery().allocateSlots(obj, nslots); -#endif - return obj->zone()->pod_malloc<HeapSlot>(nslots); -} - -// This will not run the garbage collector. If a nursery cannot accomodate the slot array -// an attempt will be made to place the array in the tenured area. -// -// If this returns null then the old slots will be left alone. -static HeapSlot * -ReallocateSlots(ThreadSafeContext *cx, JSObject *obj, HeapSlot *oldSlots, - uint32_t oldCount, uint32_t newCount) -{ -#ifdef JSGC_GENERATIONAL - if (cx->isJSContext()) { - return cx->asJSContext()->runtime()->gc.nursery.reallocateSlots(obj, oldSlots, - oldCount, newCount); - } -#endif -#ifdef JSGC_FJGENERATIONAL - if (cx->isForkJoinContext()) { - return cx->asForkJoinContext()->nursery().reallocateSlots(obj, oldSlots, - oldCount, newCount); - } -#endif - return obj->zone()->pod_realloc<HeapSlot>(oldSlots, oldCount, newCount); -} - -/* static */ bool -NativeObject::growSlots(ThreadSafeContext *cx, HandleNativeObject obj, uint32_t oldCount, uint32_t newCount) -{ - MOZ_ASSERT(cx->isThreadLocal(obj)); - MOZ_ASSERT(newCount > oldCount); - MOZ_ASSERT_IF(!obj->is<ArrayObject>(), newCount >= SLOT_CAPACITY_MIN); - - /* - * Slot capacities are determined by the span of allocated objects. Due to - * the limited number of bits to store shape slots, object growth is - * throttled well before the slot capacity can overflow. - */ - MOZ_ASSERT(newCount < NELEMENTS_LIMIT); - - if (!oldCount) { - obj->slots = AllocateSlots(cx, obj, newCount); - if (!obj->slots) - return false; - Debug_SetSlotRangeToCrashOnTouch(obj->slots, newCount); - return true; - } - - HeapSlot *newslots = ReallocateSlots(cx, obj, obj->slots, oldCount, newCount); - if (!newslots) - return false; /* Leave slots at its old size. */ - - obj->slots = newslots; - - Debug_SetSlotRangeToCrashOnTouch(obj->slots + oldCount, newCount - oldCount); - - return true; -} - -static void -FreeSlots(ThreadSafeContext *cx, HeapSlot *slots) -{ -#ifdef JSGC_GENERATIONAL - // Note: threads without a JSContext do not have access to GGC nursery allocated things. - if (cx->isJSContext()) - return cx->asJSContext()->runtime()->gc.nursery.freeSlots(slots); -#endif -#ifdef JSGC_FJGENERATIONAL - if (cx->isForkJoinContext()) - return cx->asForkJoinContext()->nursery().freeSlots(slots); -#endif - js_free(slots); -} - -/* static */ void -NativeObject::shrinkSlots(ThreadSafeContext *cx, HandleNativeObject obj, - uint32_t oldCount, uint32_t newCount) -{ - MOZ_ASSERT(cx->isThreadLocal(obj)); - MOZ_ASSERT(newCount < oldCount); - - if (newCount == 0) { - FreeSlots(cx, obj->slots); - obj->slots = nullptr; - return; - } - - MOZ_ASSERT_IF(!obj->is<ArrayObject>(), newCount >= SLOT_CAPACITY_MIN); - - HeapSlot *newslots = ReallocateSlots(cx, obj, obj->slots, oldCount, newCount); - if (!newslots) - return; /* Leave slots at its old size. */ - - obj->slots = newslots; -} - -/* static */ bool -NativeObject::sparsifyDenseElement(ExclusiveContext *cx, HandleNativeObject obj, uint32_t index) -{ - if (!obj->maybeCopyElementsForWrite(cx)) - return false; - - RootedValue value(cx, obj->getDenseElement(index)); - MOZ_ASSERT(!value.isMagic(JS_ELEMENTS_HOLE)); - - removeDenseElementForSparseIndex(cx, obj, index); - - uint32_t slot = obj->slotSpan(); - if (!obj->addDataProperty(cx, INT_TO_JSID(index), slot, JSPROP_ENUMERATE)) { - obj->setDenseElement(index, value); - return false; - } - - MOZ_ASSERT(slot == obj->slotSpan() - 1); - obj->initSlot(slot, value); - - return true; -} - -/* static */ bool -NativeObject::sparsifyDenseElements(js::ExclusiveContext *cx, HandleNativeObject obj) -{ - if (!obj->maybeCopyElementsForWrite(cx)) - return false; - - uint32_t initialized = obj->getDenseInitializedLength(); - - /* Create new properties with the value of non-hole dense elements. */ - for (uint32_t i = 0; i < initialized; i++) { - if (obj->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) - continue; - - if (!sparsifyDenseElement(cx, obj, i)) - return false; - } - - if (initialized) - obj->setDenseInitializedLength(0); - - /* - * Reduce storage for dense elements which are now holes. Explicitly mark - * the elements capacity as zero, so that any attempts to add dense - * elements will be caught in ensureDenseElements. - */ - if (obj->getDenseCapacity()) { - obj->shrinkElements(cx, 0); - obj->getElementsHeader()->capacity = 0; - } - - return true; -} - -bool -NativeObject::willBeSparseElements(uint32_t requiredCapacity, uint32_t newElementsHint) -{ - MOZ_ASSERT(isNative()); - MOZ_ASSERT(requiredCapacity > MIN_SPARSE_INDEX); - - uint32_t cap = getDenseCapacity(); - MOZ_ASSERT(requiredCapacity >= cap); - - if (requiredCapacity >= NELEMENTS_LIMIT) - return true; - - uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO; - if (newElementsHint >= minimalDenseCount) - return false; - minimalDenseCount -= newElementsHint; - - if (minimalDenseCount > cap) - return true; - - uint32_t len = getDenseInitializedLength(); - const Value *elems = getDenseElements(); - for (uint32_t i = 0; i < len; i++) { - if (!elems[i].isMagic(JS_ELEMENTS_HOLE) && !--minimalDenseCount) - return false; - } - return true; -} - -/* static */ NativeObject::EnsureDenseResult -NativeObject::maybeDensifySparseElements(js::ExclusiveContext *cx, HandleNativeObject obj) -{ - /* - * Wait until after the object goes into dictionary mode, which must happen - * when sparsely packing any array with more than MIN_SPARSE_INDEX elements - * (see PropertyTree::MAX_HEIGHT). - */ - if (!obj->inDictionaryMode()) - return ED_SPARSE; - - /* - * Only measure the number of indexed properties every log(n) times when - * populating the object. - */ - uint32_t slotSpan = obj->slotSpan(); - if (slotSpan != RoundUpPow2(slotSpan)) - return ED_SPARSE; - - /* Watch for conditions under which an object's elements cannot be dense. */ - if (!obj->nonProxyIsExtensible() || obj->watched()) - return ED_SPARSE; - - /* - * The indexes in the object need to be sufficiently dense before they can - * be converted to dense mode. - */ - uint32_t numDenseElements = 0; - uint32_t newInitializedLength = 0; - - RootedShape shape(cx, obj->lastProperty()); - while (!shape->isEmptyShape()) { - uint32_t index; - if (js_IdIsIndex(shape->propid(), &index)) { - if (shape->attributes() == JSPROP_ENUMERATE && - shape->hasDefaultGetter() && - shape->hasDefaultSetter()) - { - numDenseElements++; - newInitializedLength = Max(newInitializedLength, index + 1); - } else { - /* - * For simplicity, only densify the object if all indexed - * properties can be converted to dense elements. - */ - return ED_SPARSE; - } - } - shape = shape->previous(); - } - - if (numDenseElements * SPARSE_DENSITY_RATIO < newInitializedLength) - return ED_SPARSE; - - if (newInitializedLength >= NELEMENTS_LIMIT) - return ED_SPARSE; - - /* - * This object meets all necessary restrictions, convert all indexed - * properties into dense elements. - */ - - if (!obj->maybeCopyElementsForWrite(cx)) - return ED_FAILED; - - if (newInitializedLength > obj->getDenseCapacity()) { - if (!obj->growElements(cx, newInitializedLength)) - return ED_FAILED; - } - - obj->ensureDenseInitializedLength(cx, newInitializedLength, 0); - - RootedValue value(cx); - - shape = obj->lastProperty(); - while (!shape->isEmptyShape()) { - jsid id = shape->propid(); - uint32_t index; - if (js_IdIsIndex(id, &index)) { - value = obj->getSlot(shape->slot()); - - /* - * When removing a property from a dictionary, the specified - * property will be removed from the dictionary list and the - * last property will then be changed due to reshaping the object. - * Compute the next shape in the traverse, watching for such - * removals from the list. - */ - if (shape != obj->lastProperty()) { - shape = shape->previous(); - if (!obj->removeProperty(cx, id)) - return ED_FAILED; - } else { - if (!obj->removeProperty(cx, id)) - return ED_FAILED; - shape = obj->lastProperty(); - } - - obj->setDenseElement(index, value); - } else { - shape = shape->previous(); - } - } - - /* - * All indexed properties on the object are now dense, clear the indexed - * flag so that we will not start using sparse indexes again if we need - * to grow the object. - */ - if (!obj->clearFlag(cx, BaseShape::INDEXED)) - return ED_FAILED; - - return ED_OK; -} - -// This will not run the garbage collector. If a nursery cannot accomodate the element array -// an attempt will be made to place the array in the tenured area. -static ObjectElements * -AllocateElements(ThreadSafeContext *cx, JSObject *obj, uint32_t nelems) -{ -#ifdef JSGC_GENERATIONAL - if (cx->isJSContext()) - return cx->asJSContext()->runtime()->gc.nursery.allocateElements(obj, nelems); -#endif -#ifdef JSGC_FJGENERATIONAL - if (cx->isForkJoinContext()) - return cx->asForkJoinContext()->nursery().allocateElements(obj, nelems); -#endif - - return reinterpret_cast<js::ObjectElements *>(obj->zone()->pod_malloc<HeapSlot>(nelems)); -} - -// This will not run the garbage collector. If a nursery cannot accomodate the element array -// an attempt will be made to place the array in the tenured area. -static ObjectElements * -ReallocateElements(ThreadSafeContext *cx, JSObject *obj, ObjectElements *oldHeader, - uint32_t oldCount, uint32_t newCount) -{ -#ifdef JSGC_GENERATIONAL - if (cx->isJSContext()) { - return cx->asJSContext()->runtime()->gc.nursery.reallocateElements(obj, oldHeader, - oldCount, newCount); - } -#endif -#ifdef JSGC_FJGENERATIONAL - if (cx->isForkJoinContext()) { - return cx->asForkJoinContext()->nursery().reallocateElements(obj, oldHeader, - oldCount, newCount); - } -#endif - - return reinterpret_cast<js::ObjectElements *>( - obj->zone()->pod_realloc<HeapSlot>(reinterpret_cast<HeapSlot *>(oldHeader), - oldCount, newCount)); -} - -// Round up |reqAllocated| to a good size. Up to 1 Mebi (i.e. 1,048,576) the -// slot count is usually a power-of-two: -// -// 8, 16, 32, 64, ..., 256 Ki, 512 Ki, 1 Mi -// -// Beyond that, we use this formula: -// -// count(n+1) = Math.ceil(count(n) * 1.125) -// -// where |count(n)| is the size of the nth bucket measured in MiSlots. -// -// These counts lets us add N elements to an array in amortized O(N) time. -// Having the second class means that for bigger arrays the constant factor is -// higher, but we waste less space. -// -// There is one exception to the above rule: for the power-of-two cases, if the -// chosen capacity would be 2/3 or more of the array's length, the chosen -// capacity is adjusted (up or down) to be equal to the array's length -// (assuming length is at least as large as the required capacity). This avoids -// the allocation of excess elements which are unlikely to be needed, either in -// this resizing or a subsequent one. The 2/3 factor is chosen so that -// exceptional resizings will at most triple the capacity, as opposed to the -// usual doubling. -// -/* static */ uint32_t -NativeObject::goodAllocated(uint32_t reqAllocated, uint32_t length = 0) -{ - static const uint32_t Mebi = 1024 * 1024; - - // This table was generated with this JavaScript code and a small amount - // subsequent reformatting: - // - // for (let n = 1, i = 0; i < 57; i++) { - // print((n * 1024 * 1024) + ', '); - // n = Math.ceil(n * 1.125); - // } - // print('0'); - // - // The final element is a sentinel value. - static const uint32_t BigBuckets[] = { - 1048576, 2097152, 3145728, 4194304, 5242880, 6291456, 7340032, 8388608, - 9437184, 11534336, 13631488, 15728640, 17825792, 20971520, 24117248, - 27262976, 31457280, 35651584, 40894464, 46137344, 52428800, 59768832, - 68157440, 77594624, 88080384, 99614720, 112197632, 126877696, - 143654912, 162529280, 183500800, 206569472, 232783872, 262144000, - 295698432, 333447168, 375390208, 422576128, 476053504, 535822336, - 602931200, 678428672, 763363328, 858783744, 966787072, 1088421888, - 1224736768, 1377828864, 1550843904, 1744830464, 1962934272, 2208301056, - 2485125120, 2796552192, 3146776576, 3541041152, 3984588800, 0 - }; - - // This code relies very much on |goodAllocated| being a uint32_t. - uint32_t goodAllocated = reqAllocated; - if (goodAllocated < Mebi) { - goodAllocated = RoundUpPow2(goodAllocated); - - // Look for the abovementioned exception. - uint32_t goodCapacity = goodAllocated - ObjectElements::VALUES_PER_HEADER; - uint32_t reqCapacity = reqAllocated - ObjectElements::VALUES_PER_HEADER; - if (length >= reqCapacity && goodCapacity > (length / 3) * 2) - goodAllocated = length + ObjectElements::VALUES_PER_HEADER; - - if (goodAllocated < SLOT_CAPACITY_MIN) - goodAllocated = SLOT_CAPACITY_MIN; - - } else { - uint32_t i = 0; - while (true) { - uint32_t b = BigBuckets[i++]; - if (b >= goodAllocated) { - // Found the first bucket greater than or equal to - // |goodAllocated|. - goodAllocated = b; - break; - } else if (b == 0) { - // Hit the end; return the maximum possible goodAllocated. - goodAllocated = 0xffffffff; - break; - } - } - } - - return goodAllocated; -} - -bool -NativeObject::growElements(ThreadSafeContext *cx, uint32_t reqCapacity) -{ - MOZ_ASSERT(nonProxyIsExtensible()); - MOZ_ASSERT(canHaveNonEmptyElements()); - if (denseElementsAreCopyOnWrite()) - MOZ_CRASH(); - - uint32_t oldCapacity = getDenseCapacity(); - MOZ_ASSERT(oldCapacity < reqCapacity); - - using mozilla::CheckedInt; - - CheckedInt<uint32_t> checkedOldAllocated = - CheckedInt<uint32_t>(oldCapacity) + ObjectElements::VALUES_PER_HEADER; - CheckedInt<uint32_t> checkedReqAllocated = - CheckedInt<uint32_t>(reqCapacity) + ObjectElements::VALUES_PER_HEADER; - if (!checkedOldAllocated.isValid() || !checkedReqAllocated.isValid()) - return false; - - uint32_t reqAllocated = checkedReqAllocated.value(); - uint32_t oldAllocated = checkedOldAllocated.value(); - - uint32_t newAllocated; - if (is<ArrayObject>() && !as<ArrayObject>().lengthIsWritable()) { - MOZ_ASSERT(reqCapacity <= as<ArrayObject>().length()); - // Preserve the |capacity <= length| invariant for arrays with - // non-writable length. See also js::ArraySetLength which initially - // enforces this requirement. - newAllocated = reqAllocated; - } else { - newAllocated = goodAllocated(reqAllocated, getElementsHeader()->length); - } - - uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER; - MOZ_ASSERT(newCapacity > oldCapacity && newCapacity >= reqCapacity); - - // Don't let nelements get close to wrapping around uint32_t. - if (newCapacity >= NELEMENTS_LIMIT) - return false; - - uint32_t initlen = getDenseInitializedLength(); - - ObjectElements *newheader; - if (hasDynamicElements()) { - newheader = ReallocateElements(cx, this, getElementsHeader(), oldAllocated, newAllocated); - if (!newheader) - return false; // Leave elements at its old size. - } else { - newheader = AllocateElements(cx, this, newAllocated); - if (!newheader) - return false; // Leave elements at its old size. - js_memcpy(newheader, getElementsHeader(), - (ObjectElements::VALUES_PER_HEADER + initlen) * sizeof(Value)); - } - - newheader->capacity = newCapacity; - elements = newheader->elements(); - - Debug_SetSlotRangeToCrashOnTouch(elements + initlen, newCapacity - initlen); - - return true; -} - -void -NativeObject::shrinkElements(ThreadSafeContext *cx, uint32_t reqCapacity) -{ - MOZ_ASSERT(cx->isThreadLocal(this)); - MOZ_ASSERT(canHaveNonEmptyElements()); - if (denseElementsAreCopyOnWrite()) - MOZ_CRASH(); - - if (!hasDynamicElements()) - return; - - uint32_t oldCapacity = getDenseCapacity(); - MOZ_ASSERT(reqCapacity < oldCapacity); - - uint32_t oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER; - uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER; - uint32_t newAllocated = goodAllocated(reqAllocated); - if (newAllocated == oldAllocated) - return; // Leave elements at its old size. - - MOZ_ASSERT(newAllocated > ObjectElements::VALUES_PER_HEADER); - uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER; - - ObjectElements *newheader = ReallocateElements(cx, this, getElementsHeader(), - oldAllocated, newAllocated); - if (!newheader) { - cx->recoverFromOutOfMemory(); - return; // Leave elements at its old size. - } - - newheader->capacity = newCapacity; - elements = newheader->elements(); -} - -/* static */ bool -NativeObject::CopyElementsForWrite(ThreadSafeContext *cx, NativeObject *obj) -{ - MOZ_ASSERT(obj->denseElementsAreCopyOnWrite()); - - // The original owner of a COW elements array should never be modified. - MOZ_ASSERT(obj->getElementsHeader()->ownerObject() != obj); - - uint32_t initlen = obj->getDenseInitializedLength(); - uint32_t allocated = initlen + ObjectElements::VALUES_PER_HEADER; - uint32_t newAllocated = goodAllocated(allocated); - - uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER; - - if (newCapacity >= NELEMENTS_LIMIT) - return false; - - JSObject::writeBarrierPre(obj->getElementsHeader()->ownerObject()); - - ObjectElements *newheader = AllocateElements(cx, obj, newAllocated); - if (!newheader) - return false; - js_memcpy(newheader, obj->getElementsHeader(), - (ObjectElements::VALUES_PER_HEADER + initlen) * sizeof(Value)); - - newheader->capacity = newCapacity; - newheader->clearCopyOnWrite(); - obj->elements = newheader->elements(); - - Debug_SetSlotRangeToCrashOnTouch(obj->elements + initlen, newCapacity - initlen); - - return true; -} - void JSObject::fixupAfterMovingGC() { /* * If this is a copy-on-write elements we may need to fix up both the * elements' pointer back to the owner object, and the elements pointer * itself if it points to inline elements in another object. */ @@ -3872,159 +3121,16 @@ JSObject::constructHook() const const js::ProxyObject &p = as<js::ProxyObject>(); if (p.handler()->isConstructor(const_cast<JSObject*>(this))) return js::proxy_Construct; } return nullptr; } /* static */ bool -NativeObject::allocSlot(ThreadSafeContext *cx, HandleNativeObject obj, uint32_t *slotp) -{ - MOZ_ASSERT(cx->isThreadLocal(obj)); - - uint32_t slot = obj->slotSpan(); - MOZ_ASSERT(slot >= JSSLOT_FREE(obj->getClass())); - - /* - * If this object is in dictionary mode, try to pull a free slot from the - * shape table's slot-number freelist. - */ - if (obj->inDictionaryMode()) { - ShapeTable &table = obj->lastProperty()->table(); - uint32_t last = table.freelist; - if (last != SHAPE_INVALID_SLOT) { -#ifdef DEBUG - MOZ_ASSERT(last < slot); - uint32_t next = obj->getSlot(last).toPrivateUint32(); - MOZ_ASSERT_IF(next != SHAPE_INVALID_SLOT, next < slot); -#endif - - *slotp = last; - - const Value &vref = obj->getSlot(last); - table.freelist = vref.toPrivateUint32(); - obj->setSlot(last, UndefinedValue()); - return true; - } - } - - if (slot >= SHAPE_MAXIMUM_SLOT) { - js_ReportOutOfMemory(cx); - return false; - } - - *slotp = slot; - - if (obj->inDictionaryMode() && !setSlotSpan(cx, obj, slot + 1)) - return false; - - return true; -} - -void -NativeObject::freeSlot(uint32_t slot) -{ - MOZ_ASSERT(slot < slotSpan()); - - if (inDictionaryMode()) { - uint32_t &last = lastProperty()->table().freelist; - - /* Can't afford to check the whole freelist, but let's check the head. */ - MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan() && last != slot); - - /* - * Place all freed slots other than reserved slots (bug 595230) on the - * dictionary's free list. - */ - if (JSSLOT_FREE(getClass()) <= slot) { - MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan()); - setSlot(slot, PrivateUint32Value(last)); - last = slot; - return; - } - } - setSlot(slot, UndefinedValue()); -} - -static bool -PurgeProtoChain(ExclusiveContext *cx, JSObject *objArg, HandleId id) -{ - /* Root locally so we can re-assign. */ - RootedObject obj(cx, objArg); - - RootedShape shape(cx); - while (obj) { - /* Lookups will not be cached through non-native protos. */ - if (!obj->isNative()) - break; - - shape = obj->as<NativeObject>().lookup(cx, id); - if (shape) - return obj->as<NativeObject>().shadowingShapeChange(cx, *shape); - - obj = obj->getProto(); - } - - return true; -} - -static bool -PurgeScopeChainHelper(ExclusiveContext *cx, HandleObject objArg, HandleId id) -{ - /* Re-root locally so we can re-assign. */ - RootedObject obj(cx, objArg); - - MOZ_ASSERT(obj->isNative()); - MOZ_ASSERT(obj->isDelegate()); - - /* Lookups on integer ids cannot be cached through prototypes. */ - if (JSID_IS_INT(id)) - return true; - - PurgeProtoChain(cx, obj->getProto(), id); - - /* - * We must purge the scope chain only for Call objects as they are the only - * kind of cacheable non-global object that can gain properties after outer - * properties with the same names have been cached or traced. Call objects - * may gain such properties via eval introducing new vars; see bug 490364. - */ - if (obj->is<CallObject>()) { - while ((obj = obj->enclosingScope()) != nullptr) { - if (!PurgeProtoChain(cx, obj, id)) - return false; - } - } - - return true; -} - -/* - * PurgeScopeChain does nothing if obj is not itself a prototype or parent - * scope, else it reshapes the scope and prototype chains it links. It calls - * PurgeScopeChainHelper, which asserts that obj is flagged as a delegate - * (i.e., obj has ever been on a prototype or parent chain). - */ -static inline bool -PurgeScopeChain(ExclusiveContext *cx, JS::HandleObject obj, JS::HandleId id) -{ - if (obj->isDelegate()) - return PurgeScopeChainHelper(cx, obj, id); - return true; -} - -bool -baseops::DefineGeneric(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, HandleValue value, - PropertyOp getter, StrictPropertyOp setter, unsigned attrs) -{ - return DefineNativeProperty(cx, obj, id, value, getter, setter, attrs); -} - -/* static */ bool JSObject::defineGeneric(ExclusiveContext *cx, HandleObject obj, HandleId id, HandleValue value, JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs) { MOZ_ASSERT(!(attrs & JSPROP_NATIVE_ACCESSORS)); js::DefineGenericOp op = obj->getOps()->defineGeneric; if (op) { if (!cx->shouldBeJSContext()) @@ -4038,724 +3144,45 @@ JSObject::defineGeneric(ExclusiveContext JSObject::defineProperty(ExclusiveContext *cx, HandleObject obj, PropertyName *name, HandleValue value, JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs) { RootedId id(cx, NameToId(name)); return defineGeneric(cx, obj, id, value, getter, setter, attrs); } -bool -baseops::DefineElement(ExclusiveContext *cx, HandleNativeObject obj, uint32_t index, HandleValue value, - PropertyOp getter, StrictPropertyOp setter, unsigned attrs) -{ - RootedId id(cx); - if (index <= JSID_INT_MAX) { - id = INT_TO_JSID(index); - return DefineNativeProperty(cx, obj, id, value, getter, setter, attrs); - } - - AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); - - if (!IndexToId(cx, index, &id)) - return false; - - return DefineNativeProperty(cx, obj, id, value, getter, setter, attrs); -} - /* static */ bool JSObject::defineElement(ExclusiveContext *cx, HandleObject obj, uint32_t index, HandleValue value, JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs) { js::DefineElementOp op = obj->getOps()->defineElement; if (op) { if (!cx->shouldBeJSContext()) return false; return op(cx->asJSContext(), obj, index, value, getter, setter, attrs); } return baseops::DefineElement(cx, obj.as<NativeObject>(), index, value, getter, setter, attrs); } -Shape * -NativeObject::addDataProperty(ExclusiveContext *cx, jsid idArg, uint32_t slot, unsigned attrs) -{ - MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); - RootedNativeObject self(cx, this); - RootedId id(cx, idArg); - return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0); -} - -Shape * -NativeObject::addDataProperty(ExclusiveContext *cx, HandlePropertyName name, - uint32_t slot, unsigned attrs) -{ - MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); - RootedNativeObject self(cx, this); - RootedId id(cx, NameToId(name)); - return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0); -} - -/* - * Backward compatibility requires allowing addProperty hooks to mutate the - * nominal initial value of a slotful property, while GC safety wants that - * value to be stored before the call-out through the hook. Optimize to do - * both while saving cycles for classes that stub their addProperty hook. - */ -template <ExecutionMode mode> -static inline bool -CallAddPropertyHook(typename ExecutionModeTraits<mode>::ExclusiveContextType cxArg, - const Class *clasp, HandleNativeObject obj, HandleShape shape, - HandleValue nominal) -{ - if (clasp->addProperty != JS_PropertyStub) { - if (mode == ParallelExecution) - return false; - - ExclusiveContext *cx = cxArg->asExclusiveContext(); - if (!cx->shouldBeJSContext()) - return false; - - /* Make a local copy of value so addProperty can mutate its inout parameter. */ - RootedValue value(cx, nominal); - - Rooted<jsid> id(cx, shape->propid()); - if (!CallJSPropertyOp(cx->asJSContext(), clasp->addProperty, obj, id, &value)) { - obj->removeProperty(cx, shape->propid()); - return false; - } - if (value.get() != nominal) { - if (shape->hasSlot()) - obj->setSlotWithType(cx, shape, value); - } - } - return true; -} - -template <ExecutionMode mode> -static inline bool -CallAddPropertyHookDense(typename ExecutionModeTraits<mode>::ExclusiveContextType cxArg, - const Class *clasp, HandleNativeObject obj, uint32_t index, - HandleValue nominal) -{ - /* Inline addProperty for array objects. */ - if (obj->is<ArrayObject>()) { - ArrayObject *arr = &obj->as<ArrayObject>(); - uint32_t length = arr->length(); - if (index >= length) { - if (mode == ParallelExecution) { - /* We cannot deal with overflows in parallel. */ - if (length > INT32_MAX) - return false; - arr->setLengthInt32(index + 1); - } else { - arr->setLength(cxArg->asExclusiveContext(), index + 1); - } - } - return true; - } - - if (clasp->addProperty != JS_PropertyStub) { - if (mode == ParallelExecution) - return false; - - ExclusiveContext *cx = cxArg->asExclusiveContext(); - if (!cx->shouldBeJSContext()) - return false; - - if (!obj->maybeCopyElementsForWrite(cx)) - return false; - - /* Make a local copy of value so addProperty can mutate its inout parameter. */ - RootedValue value(cx, nominal); - - Rooted<jsid> id(cx, INT_TO_JSID(index)); - if (!CallJSPropertyOp(cx->asJSContext(), clasp->addProperty, obj, id, &value)) { - obj->setDenseElementHole(cx, index); - return false; - } - if (value.get() != nominal) - obj->setDenseElementWithType(cx, index, value); - } - - return true; -} - -template <ExecutionMode mode> -static bool -UpdateShapeTypeAndValue(typename ExecutionModeTraits<mode>::ExclusiveContextType cx, - NativeObject *obj, Shape *shape, const Value &value) -{ - jsid id = shape->propid(); - if (shape->hasSlot()) { - if (mode == ParallelExecution) { - if (!obj->setSlotIfHasType(shape, value, /* overwriting = */ false)) - return false; - } else { - obj->setSlotWithType(cx->asExclusiveContext(), shape, value, /* overwriting = */ false); - } - - // Per the acquired properties analysis, when the shape of a partially - // initialized object is changed to its fully initialized shape, its - // type can be updated as well. - if (types::TypeNewScript *newScript = obj->typeRaw()->newScript()) { - if (newScript->initializedShape() == shape) - obj->setType(newScript->initializedType()); - } - } - if (!shape->hasSlot() || !shape->hasDefaultGetter() || !shape->hasDefaultSetter()) { - if (mode == ParallelExecution) { - if (!IsTypePropertyIdMarkedNonData(obj, id)) - return false; - } else { - MarkTypePropertyNonData(cx->asExclusiveContext(), obj, id); - } - } - if (!shape->writable()) { - if (mode == ParallelExecution) { - if (!IsTypePropertyIdMarkedNonWritable(obj, id)) - return false; - } else { - MarkTypePropertyNonWritable(cx->asExclusiveContext(), obj, id); - } - } - return true; -} - -template <ExecutionMode mode> -static inline bool -DefinePropertyOrElement(typename ExecutionModeTraits<mode>::ExclusiveContextType cx, - HandleNativeObject obj, HandleId id, - PropertyOp getter, StrictPropertyOp setter, - unsigned attrs, HandleValue value, - bool callSetterAfterwards, bool setterIsStrict) -{ - /* Use dense storage for new indexed properties where possible. */ - if (JSID_IS_INT(id) && - getter == JS_PropertyStub && - setter == JS_StrictPropertyStub && - attrs == JSPROP_ENUMERATE && - (!obj->isIndexed() || !obj->containsPure(id)) && - !IsAnyTypedArray(obj)) - { - uint32_t index = JSID_TO_INT(id); - bool definesPast; - if (!WouldDefinePastNonwritableLength(cx, obj, index, setterIsStrict, &definesPast)) - return false; - if (definesPast) - return true; - - NativeObject::EnsureDenseResult result; - if (mode == ParallelExecution) { - if (obj->writeToIndexWouldMarkNotPacked(index)) - return false; - result = obj->ensureDenseElementsPreservePackedFlag(cx, index, 1); - } else { - result = obj->ensureDenseElements(cx->asExclusiveContext(), index, 1); - } - - if (result == NativeObject::ED_FAILED) - return false; - if (result == NativeObject::ED_OK) { - if (mode == ParallelExecution) { - if (!obj->setDenseElementIfHasType(index, value)) - return false; - } else { - obj->setDenseElementWithType(cx->asExclusiveContext(), index, value); - } - return CallAddPropertyHookDense<mode>(cx, obj->getClass(), obj, index, value); - } - } - - if (obj->is<ArrayObject>()) { - Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>()); - if (id == NameToId(cx->names().length)) { - if (mode == SequentialExecution && !cx->shouldBeJSContext()) - return false; - return ArraySetLength<mode>(ExecutionModeTraits<mode>::toContextType(cx), arr, id, - attrs, value, setterIsStrict); - } - - uint32_t index; - if (js_IdIsIndex(id, &index)) { - bool definesPast; - if (!WouldDefinePastNonwritableLength(cx, arr, index, setterIsStrict, &definesPast)) - return false; - if (definesPast) - return true; - } - } - - // Don't define new indexed properties on typed arrays. - if (IsAnyTypedArray(obj)) { - uint64_t index; - if (IsTypedArrayIndex(id, &index)) - return true; - } - - AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); - - RootedShape shape(cx, NativeObject::putProperty<mode>(cx, obj, id, getter, setter, - SHAPE_INVALID_SLOT, attrs, 0)); - if (!shape) - return false; - - if (!UpdateShapeTypeAndValue<mode>(cx, obj, shape, value)) - return false; - - /* - * Clear any existing dense index after adding a sparse indexed property, - * and investigate converting the object to dense indexes. - */ - if (JSID_IS_INT(id)) { - if (mode == ParallelExecution) - return false; - - if (!obj->maybeCopyElementsForWrite(cx)) - return false; - - ExclusiveContext *ncx = cx->asExclusiveContext(); - uint32_t index = JSID_TO_INT(id); - NativeObject::removeDenseElementForSparseIndex(ncx, obj, index); - NativeObject::EnsureDenseResult result = NativeObject::maybeDensifySparseElements(ncx, obj); - if (result == NativeObject::ED_FAILED) - return false; - if (result == NativeObject::ED_OK) { - MOZ_ASSERT(setter == JS_StrictPropertyStub); - return CallAddPropertyHookDense<mode>(cx, obj->getClass(), obj, index, value); - } - } - - if (!CallAddPropertyHook<mode>(cx, obj->getClass(), obj, shape, value)) - return false; - - if (callSetterAfterwards && setter != JS_StrictPropertyStub) { - if (!cx->shouldBeJSContext()) - return false; - RootedValue nvalue(cx, value); - return NativeSet<mode>(ExecutionModeTraits<mode>::toContextType(cx), - obj, obj, shape, setterIsStrict, &nvalue); - } - return true; -} - -static bool -NativeLookupOwnProperty(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, - MutableHandle<Shape*> shapep); - -bool -js::DefineNativeProperty(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, HandleValue value, - PropertyOp getter, StrictPropertyOp setter, unsigned attrs) -{ - MOZ_ASSERT(!(attrs & JSPROP_NATIVE_ACCESSORS)); - - AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); - - RootedShape shape(cx); - RootedValue updateValue(cx, value); - bool shouldDefine = true; - - /* - * If defining a getter or setter, we must check for its counterpart and - * update the attributes and property ops. A getter or setter is really - * only half of a property. - */ - if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) { - if (!NativeLookupOwnProperty(cx, obj, id, &shape)) - return false; - if (shape) { - /* - * If we are defining a getter whose setter was already defined, or - * vice versa, finish the job via obj->changeProperty. - */ - if (IsImplicitDenseOrTypedArrayElement(shape)) { - if (IsAnyTypedArray(obj)) { - /* Ignore getter/setter properties added to typed arrays. */ - return true; - } - if (!NativeObject::sparsifyDenseElement(cx, obj, JSID_TO_INT(id))) - return false; - shape = obj->lookup(cx, id); - } - if (shape->isAccessorDescriptor()) { - attrs = ApplyOrDefaultAttributes(attrs, shape); - shape = NativeObject::changeProperty<SequentialExecution>(cx, obj, shape, attrs, - JSPROP_GETTER | JSPROP_SETTER, - (attrs & JSPROP_GETTER) - ? getter - : shape->getter(), - (attrs & JSPROP_SETTER) - ? setter - : shape->setter()); - if (!shape) - return false; - shouldDefine = false; - } - } - } else if (!(attrs & JSPROP_IGNORE_VALUE)) { - /* - * We might still want to ignore redefining some of our attributes, if the - * request came through a proxy after Object.defineProperty(), but only if we're redefining - * a data property. - * FIXME: All this logic should be removed when Proxies use PropDesc, but we need to - * remove JSPropertyOp getters and setters first. - * FIXME: This is still wrong for various array types, and will set the wrong attributes - * by accident, but we can't use NativeLookupOwnProperty in this case, because of resolve - * loops. - */ - shape = obj->lookup(cx, id); - if (shape && shape->isDataDescriptor()) - attrs = ApplyOrDefaultAttributes(attrs, shape); - } else { - /* - * We have been asked merely to update some attributes by a caller of - * Object.defineProperty, laundered through the proxy system, and returning here. We can - * get away with just using JSObject::changeProperty here. - */ - if (!NativeLookupOwnProperty(cx, obj, id, &shape)) - return false; - - if (shape) { - // Don't forget about arrays. - if (IsImplicitDenseOrTypedArrayElement(shape)) { - if (obj->is<TypedArrayObject>()) { - /* - * Silently ignore attempts to change individial index attributes. - * FIXME: Uses the same broken behavior as for accessors. This should - * probably throw. - */ - return true; - } - if (!NativeObject::sparsifyDenseElement(cx, obj, JSID_TO_INT(id))) - return false; - shape = obj->lookup(cx, id); - } - - attrs = ApplyOrDefaultAttributes(attrs, shape); - - /* Keep everything from the shape that isn't the things we're changing */ - unsigned attrMask = ~(JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); - shape = NativeObject::changeProperty<SequentialExecution>(cx, obj, shape, attrs, attrMask, - shape->getter(), shape->setter()); - if (!shape) - return false; - if (shape->hasSlot()) - updateValue = obj->getSlot(shape->slot()); - shouldDefine = false; - } - } - - /* - * Purge the property cache of any properties named by id that are about - * to be shadowed in obj's scope chain. - */ - if (!PurgeScopeChain(cx, obj, id)) - return false; - - /* Use the object's class getter and setter by default. */ - const Class *clasp = obj->getClass(); - if (!getter && !(attrs & JSPROP_GETTER)) - getter = clasp->getProperty; - if (!setter && !(attrs & JSPROP_SETTER)) - setter = clasp->setProperty; - - if (shouldDefine) { - // Handle the default cases here. Anyone that wanted to set non-default attributes has - // cleared the IGNORE flags by now. Since we can never get here with JSPROP_IGNORE_VALUE - // relevant, just clear it. - attrs = ApplyOrDefaultAttributes(attrs) & ~JSPROP_IGNORE_VALUE; - return DefinePropertyOrElement<SequentialExecution>(cx, obj, id, getter, setter, - attrs, value, false, false); - } - - MOZ_ASSERT(shape); - - JS_ALWAYS_TRUE(UpdateShapeTypeAndValue<SequentialExecution>(cx, obj, shape, updateValue)); - - return CallAddPropertyHook<SequentialExecution>(cx, clasp, obj, shape, updateValue); -} - -/* - * Call obj's resolve hook. - * - * cx, id, and flags are the parameters initially passed to the ongoing lookup; - * objp and propp are its out parameters. obj is an object along the prototype - * chain from where the lookup started. - * - * There are four possible outcomes: - * - * - On failure, report an error or exception and return false. - * - * - If we are already resolving a property of *curobjp, set *recursedp = true, - * and return true. - * - * - If the resolve hook finds or defines the sought property, set *objp and - * *propp appropriately, set *recursedp = false, and return true. - * - * - Otherwise no property was resolved. Set *propp = nullptr and - * *recursedp = false and return true. - */ -static MOZ_ALWAYS_INLINE bool -CallResolveOp(JSContext *cx, HandleNativeObject obj, HandleId id, MutableHandleObject objp, - MutableHandleShape propp, bool *recursedp) -{ - const Class *clasp = obj->getClass(); - JSResolveOp resolve = clasp->resolve; - - /* - * Avoid recursion on (obj, id) already being resolved on cx. - * - * Once we have successfully added an entry for (obj, key) to - * cx->resolvingTable, control must go through cleanup: before - * returning. But note that JS_DHASH_ADD may find an existing - * entry, in which case we bail to suppress runaway recursion. - */ - AutoResolving resolving(cx, obj, id); - if (resolving.alreadyStarted()) { - /* Already resolving id in obj -- suppress recursion. */ - *recursedp = true; - return true; - } - *recursedp = false; - - propp.set(nullptr); - - if (clasp->flags & JSCLASS_NEW_RESOLVE) { - JSNewResolveOp newresolve = reinterpret_cast<JSNewResolveOp>(resolve); - RootedObject obj2(cx, nullptr); - if (!newresolve(cx, obj, id, &obj2)) - return false; - - /* - * We trust the new style resolve hook to set obj2 to nullptr when - * the id cannot be resolved. But, when obj2 is not null, we do - * not assume that id must exist and do full nativeLookup for - * compatibility. - */ - if (!obj2) - return true; - - if (!obj2->isNative()) { - /* Whoops, newresolve handed back a foreign obj2. */ - MOZ_ASSERT(obj2 != obj); - return JSObject::lookupGeneric(cx, obj2, id, objp, propp); - } - - objp.set(obj2); - } else { - if (!resolve(cx, obj, id)) - return false; - - objp.set(obj); - } - - NativeObject *nobjp = &objp->as<NativeObject>(); - - if (JSID_IS_INT(id) && nobjp->containsDenseElement(JSID_TO_INT(id))) { - MarkDenseOrTypedArrayElementFound<CanGC>(propp); - return true; - } - - Shape *shape; - if (!nobjp->empty() && (shape = nobjp->lookup(cx, id))) - propp.set(shape); - else - objp.set(nullptr); - - return true; -} - -template <AllowGC allowGC> -static MOZ_ALWAYS_INLINE bool -LookupOwnPropertyInline(ExclusiveContext *cx, - typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, - typename MaybeRooted<jsid, allowGC>::HandleType id, - typename MaybeRooted<JSObject*, allowGC>::MutableHandleType objp, - typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp, - bool *donep) -{ - // Check for a native dense element. - if (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))) { - objp.set(obj); - MarkDenseOrTypedArrayElementFound<allowGC>(propp); - *donep = true; - return true; - } - - // Check for a typed array element. Integer lookups always finish here - // so that integer properties on the prototype are ignored even for out - // of bounds accesses. - if (IsAnyTypedArray(obj)) { - uint64_t index; - if (IsTypedArrayIndex(id, &index)) { - if (index < AnyTypedArrayLength(obj)) { - objp.set(obj); - MarkDenseOrTypedArrayElementFound<allowGC>(propp); - } else { - objp.set(nullptr); - propp.set(nullptr); - } - *donep = true; - return true; - } - } - - // Check for a native property. - if (Shape *shape = obj->lookup(cx, id)) { - objp.set(obj); - propp.set(shape); - *donep = true; - return true; - } - - // id was not found in obj. Try obj's resolve hook, if any. - if (obj->getClass()->resolve != JS_ResolveStub) { - if (!cx->shouldBeJSContext() || !allowGC) - return false; - - bool recursed; - if (!CallResolveOp(cx->asJSContext(), - MaybeRooted<NativeObject*, allowGC>::toHandle(obj), - MaybeRooted<jsid, allowGC>::toHandle(id), - MaybeRooted<JSObject*, allowGC>::toMutableHandle(objp), - MaybeRooted<Shape*, allowGC>::toMutableHandle(propp), - &recursed)) - { - return false; - } - - if (recursed) { - objp.set(nullptr); - propp.set(nullptr); - *donep = true; - return true; - } - - if (propp) { - *donep = true; - return true; - } - } - - *donep = false; - return true; -} - -static bool -NativeLookupOwnProperty(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, - MutableHandle<Shape*> shapep) -{ - RootedObject pobj(cx); - bool done; - - if (!LookupOwnPropertyInline<CanGC>(cx, obj, id, &pobj, shapep, &done)) - return false; - if (!done || pobj != obj) - shapep.set(nullptr); - return true; -} - -template <AllowGC allowGC> -static MOZ_ALWAYS_INLINE bool -LookupPropertyInline(ExclusiveContext *cx, - typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, - typename MaybeRooted<jsid, allowGC>::HandleType id, - typename MaybeRooted<JSObject*, allowGC>::MutableHandleType objp, - typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp) -{ - /* NB: The logic of this procedure is implicitly reflected in - * BaselineIC.cpp's |EffectlesslyLookupProperty| logic. - * If this changes, please remember to update the logic there as well. - */ - - /* Search scopes starting with obj and following the prototype link. */ - typename MaybeRooted<NativeObject*, allowGC>::RootType current(cx, obj); - - while (true) { - bool done; - if (!LookupOwnPropertyInline<allowGC>(cx, current, id, objp, propp, &done)) - return false; - if (done) - return true; - - typename MaybeRooted<JSObject*, allowGC>::RootType proto(cx, current->getProto()); - - if (!proto) - break; - if (!proto->isNative()) { - if (!cx->shouldBeJSContext() || !allowGC) - return false; - return JSObject::lookupGeneric(cx->asJSContext(), - MaybeRooted<JSObject*, allowGC>::toHandle(proto), - MaybeRooted<jsid, allowGC>::toHandle(id), - MaybeRooted<JSObject*, allowGC>::toMutableHandle(objp), - MaybeRooted<Shape*, allowGC>::toMutableHandle(propp)); - } - - current = &proto->template as<NativeObject>(); - } - - objp.set(nullptr); - propp.set(nullptr); - return true; -} - -template <AllowGC allowGC> -bool -baseops::LookupProperty(ExclusiveContext *cx, - typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, - typename MaybeRooted<jsid, allowGC>::HandleType id, - typename MaybeRooted<JSObject*, allowGC>::MutableHandleType objp, - typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp) -{ - return LookupPropertyInline<allowGC>(cx, obj, id, objp, propp); -} - -template bool -baseops::LookupProperty<CanGC>(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, - MutableHandleObject objp, MutableHandleShape propp); - -template bool -baseops::LookupProperty<NoGC>(ExclusiveContext *cx, NativeObject *obj, jsid id, - FakeMutableHandle<JSObject*> objp, - FakeMutableHandle<Shape*> propp); - /* static */ bool JSObject::lookupGeneric(JSContext *cx, HandleObject obj, js::HandleId id, MutableHandleObject objp, MutableHandleShape propp) { /* NB: The logic of lookupGeneric is implicitly reflected in * BaselineIC.cpp's |EffectlesslyLookupProperty| logic. * If this changes, please remember to update the logic there as well. */ LookupGenericOp op = obj->getOps()->lookupGeneric; if (op) return op(cx, obj, id, objp, propp); return baseops::LookupProperty<CanGC>(cx, obj.as<NativeObject>(), id, objp, propp); } bool -baseops::LookupElement(JSContext *cx, HandleNativeObject obj, uint32_t index, - MutableHandleObject objp, MutableHandleShape propp) -{ - RootedId id(cx); - if (!IndexToId(cx, index, &id)) - return false; - - return LookupPropertyInline<CanGC>(cx, obj, id, objp, propp); -} - -bool -js::LookupNativeProperty(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, - MutableHandleObject objp, MutableHandleShape propp) -{ - return LookupPropertyInline<CanGC>(cx, obj, id, objp, propp); -} - -bool js::LookupName(JSContext *cx, HandlePropertyName name, HandleObject scopeChain, MutableHandleObject objp, MutableHandleObject pobjp, MutableHandleShape propp) { RootedId id(cx, NameToId(name)); for (RootedObject scope(cx, scopeChain); scope; scope = scope->enclosingScope()) { if (!JSObject::lookupGeneric(cx, scope, id, pobjp, propp)) return false; @@ -4912,279 +3339,16 @@ js::HasOwnProperty(JSContext *cx, Handle RootedObject pobj(cx); RootedShape shape(cx); if (!HasOwnProperty<CanGC>(cx, obj->getOps()->lookupGeneric, obj, id, &pobj, &shape)) return false; *resultp = (shape != nullptr); return true; } -template <AllowGC allowGC> -static MOZ_ALWAYS_INLINE bool -NativeGetInline(JSContext *cx, - typename MaybeRooted<JSObject*, allowGC>::HandleType obj, - typename MaybeRooted<JSObject*, allowGC>::HandleType receiver, - typename MaybeRooted<NativeObject*, allowGC>::HandleType pobj, - typename MaybeRooted<Shape*, allowGC>::HandleType shape, - typename MaybeRooted<Value, allowGC>::MutableHandleType vp) -{ - if (shape->hasSlot()) { - vp.set(pobj->getSlot(shape->slot())); - MOZ_ASSERT_IF(!vp.isMagic(JS_UNINITIALIZED_LEXICAL) && - !pobj->hasSingletonType() && - !pobj->template is<ScopeObject>() && - shape->hasDefaultGetter(), - js::types::TypeHasProperty(cx, pobj->type(), shape->propid(), vp)); - } else { - vp.setUndefined(); - } - if (shape->hasDefaultGetter()) - return true; - - { - jsbytecode *pc; - JSScript *script = cx->currentScript(&pc); - if (script && script->hasBaselineScript()) { - switch (JSOp(*pc)) { - case JSOP_GETPROP: - case JSOP_CALLPROP: - case JSOP_LENGTH: - script->baselineScript()->noteAccessedGetter(script->pcToOffset(pc)); - break; - default: - break; - } - } - } - - if (!allowGC) - return false; - - if (!shape->get(cx, - MaybeRooted<JSObject*, allowGC>::toHandle(receiver), - MaybeRooted<JSObject*, allowGC>::toHandle(obj), - MaybeRooted<JSObject*, allowGC>::toHandle(pobj), - MaybeRooted<Value, allowGC>::toMutableHandle(vp))) - { - return false; - } - - /* Update slotful shapes according to the value produced by the getter. */ - if (shape->hasSlot() && pobj->contains(cx, shape)) - pobj->setSlot(shape->slot(), vp); - - return true; -} - -bool -js::NativeGet(JSContext *cx, HandleObject obj, HandleNativeObject pobj, HandleShape shape, - MutableHandleValue vp) -{ - return NativeGetInline<CanGC>(cx, obj, obj, pobj, shape, vp); -} - -template <ExecutionMode mode> -bool -js::NativeSet(typename ExecutionModeTraits<mode>::ContextType cxArg, - HandleNativeObject obj, Handle<JSObject*> receiver, - HandleShape shape, bool strict, MutableHandleValue vp) -{ - MOZ_ASSERT(cxArg->isThreadLocal(obj)); - MOZ_ASSERT(obj->isNative()); - - if (shape->hasSlot()) { - /* If shape has a stub setter, just store vp. */ - if (shape->hasDefaultSetter()) { - if (mode == ParallelExecution) { - if (!obj->setSlotIfHasType(shape, vp)) - return false; - } else { - // Global properties declared with 'var' will be initially - // defined with an undefined value, so don't treat the initial - // assignments to such properties as overwrites. - bool overwriting = !obj->is<GlobalObject>() || !obj->getSlot(shape->slot()).isUndefined(); - obj->setSlotWithType(cxArg->asExclusiveContext(), shape, vp, overwriting); - } - - return true; - } - } - - if (mode == ParallelExecution) - return false; - JSContext *cx = cxArg->asJSContext(); - - if (!shape->hasSlot()) { - /* - * Allow API consumers to create shared properties with stub setters. - * Such properties effectively function as data descriptors which are - * not writable, so attempting to set such a property should do nothing - * or throw if we're in strict mode. - */ - if (!shape->hasGetterValue() && shape->hasDefaultSetter()) - return js_ReportGetterOnlyAssignment(cx, strict); - } - - RootedValue ovp(cx, vp); - - uint32_t sample = cx->runtime()->propertyRemovals; - if (!shape->set(cx, obj, receiver, strict, vp)) - return false; - - /* - * Update any slot for the shape with the value produced by the setter, - * unless the setter deleted the shape. - */ - if (shape->hasSlot() && - (MOZ_LIKELY(cx->runtime()->propertyRemovals == sample) || - obj->contains(cx, shape))) - { - obj->setSlot(shape->slot(), vp); - } - - return true; -} - -template bool -js::NativeSet<SequentialExecution>(JSContext *cx, - HandleNativeObject obj, HandleObject receiver, - HandleShape shape, bool strict, MutableHandleValue vp); -template bool -js::NativeSet<ParallelExecution>(ForkJoinContext *cx, - HandleNativeObject obj, HandleObject receiver, - HandleShape shape, bool strict, MutableHandleValue vp); - -template <AllowGC allowGC> -static MOZ_ALWAYS_INLINE bool -GetPropertyHelperInline(JSContext *cx, - typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, - typename MaybeRooted<JSObject*, allowGC>::HandleType receiver, - typename MaybeRooted<jsid, allowGC>::HandleType id, - typename MaybeRooted<Value, allowGC>::MutableHandleType vp) -{ - /* This call site is hot -- use the always-inlined variant of LookupNativeProperty(). */ - typename MaybeRooted<JSObject*, allowGC>::RootType obj2(cx); - typename MaybeRooted<Shape*, allowGC>::RootType shape(cx); - if (!LookupPropertyInline<allowGC>(cx, obj, id, &obj2, &shape)) - return false; - - if (!shape) { - if (!allowGC) - return false; - - vp.setUndefined(); - - if (!CallJSPropertyOp(cx, obj->getClass()->getProperty, - MaybeRooted<JSObject*, allowGC>::toHandle(obj), - MaybeRooted<jsid, allowGC>::toHandle(id), - MaybeRooted<Value, allowGC>::toMutableHandle(vp))) - { - return false; - } - - /* - * Give a strict warning if foo.bar is evaluated by a script for an - * object foo with no property named 'bar'. - */ - if (vp.isUndefined()) { - jsbytecode *pc = nullptr; - RootedScript script(cx, cx->currentScript(&pc)); - if (!pc) - return true; - JSOp op = (JSOp) *pc; - - if (op == JSOP_GETXPROP) { - /* Undefined property during a name lookup, report an error. */ - JSAutoByteString printable; - if (js_ValueToPrintable(cx, IdToValue(id), &printable)) - js_ReportIsNotDefined(cx, printable.ptr()); - return false; - } - - /* Don't warn if extra warnings not enabled or for random getprop operations. */ - if (!cx->compartment()->options().extraWarnings(cx) || (op != JSOP_GETPROP && op != JSOP_GETELEM)) - return true; - - /* Don't warn repeatedly for the same script. */ - if (!script || script->warnedAboutUndefinedProp()) - return true; - - /* - * Don't warn in self-hosted code (where the further presence of - * JS::RuntimeOptions::werror() would result in impossible-to-avoid - * errors to entirely-innocent client code). - */ - if (script->selfHosted()) - return true; - - /* We may just be checking if that object has an iterator. */ - if (JSID_IS_ATOM(id, cx->names().iteratorIntrinsic)) - return true; - - /* Do not warn about tests like (obj[prop] == undefined). */ - pc += js_CodeSpec[op].length; - if (Detecting(cx, script, pc)) - return true; - - unsigned flags = JSREPORT_WARNING | JSREPORT_STRICT; - script->setWarnedAboutUndefinedProp(); - - /* Ok, bad undefined property reference: whine about it. */ - RootedValue val(cx, IdToValue(id)); - if (!js_ReportValueErrorFlags(cx, flags, JSMSG_UNDEFINED_PROP, - JSDVG_IGNORE_STACK, val, js::NullPtr(), - nullptr, nullptr)) - { - return false; - } - } - return true; - } - - if (!obj2->isNative()) { - if (!allowGC) - return false; - HandleObject obj2Handle = MaybeRooted<JSObject*, allowGC>::toHandle(obj2); - HandleObject receiverHandle = MaybeRooted<JSObject*, allowGC>::toHandle(receiver); - HandleId idHandle = MaybeRooted<jsid, allowGC>::toHandle(id); - MutableHandleValue vpHandle = MaybeRooted<Value, allowGC>::toMutableHandle(vp); - return obj2->template is<ProxyObject>() - ? Proxy::get(cx, obj2Handle, receiverHandle, idHandle, vpHandle) - : JSObject::getGeneric(cx, obj2Handle, obj2Handle, idHandle, vpHandle); - } - - typename MaybeRooted<NativeObject*, allowGC>::HandleType nobj2 = - MaybeRooted<JSObject*, allowGC>::template downcastHandle<NativeObject>(obj2); - - if (IsImplicitDenseOrTypedArrayElement(shape)) { - vp.set(nobj2->getDenseOrTypedArrayElement(JSID_TO_INT(id))); - return true; - } - - /* This call site is hot -- use the always-inlined variant of NativeGet(). */ - if (!NativeGetInline<allowGC>(cx, obj, receiver, nobj2, shape, vp)) - return false; - - return true; -} - -bool -baseops::GetProperty(JSContext *cx, HandleNativeObject obj, HandleObject receiver, HandleId id, MutableHandleValue vp) -{ - /* This call site is hot -- use the always-inlined variant of GetPropertyHelper(). */ - return GetPropertyHelperInline<CanGC>(cx, obj, receiver, id, vp); -} - -bool -baseops::GetPropertyNoGC(JSContext *cx, NativeObject *obj, JSObject *receiver, jsid id, Value *vp) -{ - AutoAssertNoException nogc(cx); - return GetPropertyHelperInline<NoGC>(cx, obj, receiver, id, vp); -} - static MOZ_ALWAYS_INLINE bool LookupPropertyPureInline(JSObject *obj, jsid id, NativeObject **objp, Shape **propp) { if (!obj->isNative()) return false; NativeObject *current = &obj->as<NativeObject>(); while (true) { @@ -5337,51 +3501,16 @@ js::GetObjectElementOperationPure(Thread JSAtom *name = &prop.toString()->asAtom(); if (name->isIndex(&index)) return GetElementPure(cx, obj, index, vp); return GetPropertyPure(cx, obj, NameToId(name->asPropertyName()), vp); } bool -baseops::GetElement(JSContext *cx, HandleNativeObject obj, HandleObject receiver, uint32_t index, - MutableHandleValue vp) -{ - RootedId id(cx); - if (!IndexToId(cx, index, &id)) - return false; - - /* This call site is hot -- use the always-inlined variant of js_GetPropertyHelper(). */ - return GetPropertyHelperInline<CanGC>(cx, obj, receiver, id, vp); -} - -static bool -MaybeReportUndeclaredVarAssignment(JSContext *cx, JSString *propname) -{ - { - JSScript *script = cx->currentScript(nullptr, JSContext::ALLOW_CROSS_COMPARTMENT); - if (!script) - return true; - - // If the code is not strict and extra warnings aren't enabled, then no - // check is needed. - if (!script->strict() && !cx->compartment()->options().extraWarnings(cx)) - return true; - } - - JSAutoByteString bytes(cx, propname); - return !!bytes && - JS_ReportErrorFlagsAndNumber(cx, - (JSREPORT_WARNING | JSREPORT_STRICT - | JSREPORT_STRICT_MODE_ERROR), - js_GetErrorMessage, nullptr, - JSMSG_UNDECLARED_VAR, bytes.ptr()); -} - -bool JSObject::reportReadOnly(ThreadSafeContext *cxArg, jsid id, unsigned report) { if (cxArg->isForkJoinContext()) return cxArg->asForkJoinContext()->reportError(report); if (!cxArg->isJSContext()) return true; @@ -5429,394 +3558,16 @@ JSObject::callMethod(JSContext *cx, Hand { RootedValue fval(cx); RootedObject obj(cx, this); if (!JSObject::getGeneric(cx, obj, obj, id, &fval)) return false; return Invoke(cx, ObjectValue(*obj), fval, argc, argv, vp); } -template <ExecutionMode mode> -bool -baseops::SetPropertyHelper(typename ExecutionModeTraits<mode>::ContextType cxArg, - HandleNativeObject obj, HandleObject receiver, HandleId id, - QualifiedBool qualified, MutableHandleValue vp, bool strict) -{ - MOZ_ASSERT(cxArg->isThreadLocal(obj)); - - if (MOZ_UNLIKELY(obj->watched())) { - if (mode == ParallelExecution) - return false; - - /* Fire watchpoints, if any. */ - JSContext *cx = cxArg->asJSContext(); - WatchpointMap *wpmap = cx->compartment()->watchpointMap; - if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp)) - return false; - } - - RootedObject pobj(cxArg); - RootedShape shape(cxArg); - if (mode == ParallelExecution) { - NativeObject *npobj; - if (!LookupPropertyPure(obj, id, &npobj, shape.address())) - return false; - pobj = npobj; - } else { - JSContext *cx = cxArg->asJSContext(); - if (!LookupNativeProperty(cx, obj, id, &pobj, &shape)) - return false; - } - if (shape) { - if (!pobj->isNative()) { - if (pobj->is<ProxyObject>()) { - if (mode == ParallelExecution) - return false; - - JSContext *cx = cxArg->asJSContext(); - Rooted<PropertyDescriptor> pd(cx); - if (!Proxy::getPropertyDescriptor(cx, pobj, id, &pd)) - return false; - - if ((pd.attributes() & (JSPROP_SHARED | JSPROP_SHADOWABLE)) == JSPROP_SHARED) { - return !pd.setter() || - CallSetter(cx, receiver, id, pd.setter(), pd.attributes(), strict, vp); - } - - if (pd.isReadonly()) { - if (strict) - return JSObject::reportReadOnly(cx, id, JSREPORT_ERROR); - if (cx->compartment()->options().extraWarnings(cx)) - return JSObject::reportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING); - return true; - } - } - - shape = nullptr; - } - } else { - /* We should never add properties to lexical blocks. */ - MOZ_ASSERT(!obj->is<BlockObject>()); - - if (obj->isUnqualifiedVarObj() && !qualified) { - if (mode == ParallelExecution) - return false; - - if (!MaybeReportUndeclaredVarAssignment(cxArg->asJSContext(), JSID_TO_STRING(id))) - return false; - } - } - - /* - * Now either shape is null, meaning id was not found in obj or one of its - * prototypes; or shape is non-null, meaning id was found directly in pobj. - */ - unsigned attrs = JSPROP_ENUMERATE; - const Class *clasp = obj->getClass(); - PropertyOp getter = clasp->getProperty; - StrictPropertyOp setter = clasp->setProperty; - - if (IsImplicitDenseOrTypedArrayElement(shape)) { - /* ES5 8.12.4 [[Put]] step 2, for a dense data property on pobj. */ - if (pobj != obj) - shape = nullptr; - } else if (shape) { - /* ES5 8.12.4 [[Put]] step 2. */ - if (shape->isAccessorDescriptor()) { - if (shape->hasDefaultSetter()) { - /* Bail out of parallel execution if we are strict to throw. */ - if (mode == ParallelExecution) - return !strict; - - return js_ReportGetterOnlyAssignment(cxArg->asJSContext(), strict); - } - } else { - MOZ_ASSERT(shape->isDataDescriptor()); - - if (!shape->writable()) { - /* - * Error in strict mode code, warn with extra warnings - * options, otherwise do nothing. - * - * Bail out of parallel execution if we are strict to throw. - */ - if (mode == ParallelExecution) - return !strict; - - JSContext *cx = cxArg->asJSContext(); - if (strict) - return JSObject::reportReadOnly(cx, id, JSREPORT_ERROR); - if (cx->compartment()->options().extraWarnings(cx)) - return JSObject::reportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING); - return true; - } - } - - attrs = shape->attributes(); - if (pobj != obj) { - /* - * We found id in a prototype object: prepare to share or shadow. - */ - if (!shape->shadowable()) { - if (shape->hasDefaultSetter() && !shape->hasGetterValue()) - return true; - - if (mode == ParallelExecution) - return false; - - return shape->set(cxArg->asJSContext(), obj, receiver, strict, vp); - } - - /* - * Preserve attrs except JSPROP_SHARED, getter, and setter when - * shadowing any property that has no slot (is shared). We must - * clear the shared attribute for the shadowing shape so that the - * property in obj that it defines has a slot to retain the value - * being set, in case the setter simply cannot operate on instances - * of obj's class by storing the value in some class-specific - * location. - */ - if (!shape->hasSlot()) { - attrs &= ~JSPROP_SHARED; - getter = shape->getter(); - setter = shape->setter(); - } else { - /* Restore attrs to the ECMA default for new properties. */ - attrs = JSPROP_ENUMERATE; - } - - /* - * Forget we found the proto-property now that we've copied any - * needed member values. - */ - shape = nullptr; - } - } - - if (IsImplicitDenseOrTypedArrayElement(shape)) { - uint32_t index = JSID_TO_INT(id); - - if (IsAnyTypedArray(obj)) { - double d; - if (mode == ParallelExecution) { - // Bail if converting the value might invoke user-defined - // conversions. - if (vp.isObject()) - return false; - if (!NonObjectToNumber(cxArg, vp, &d)) - return false; - } else { - if (!ToNumber(cxArg->asJSContext(), vp, &d)) - return false; - } - - // Silently do nothing for out-of-bounds sets, for consistency with - // current behavior. (ES6 currently says to throw for this in - // strict mode code, so we may eventually need to change.) - uint32_t len = AnyTypedArrayLength(obj); - if (index < len) { - if (obj->is<TypedArrayObject>()) - TypedArrayObject::setElement(obj->as<TypedArrayObject>(), index, d); - else - SharedTypedArrayObject::setElement(obj->as<SharedTypedArrayObject>(), index, d); - } - return true; - } - - bool definesPast; - if (!WouldDefinePastNonwritableLength(cxArg, obj, index, strict, &definesPast)) - return false; - if (definesPast) { - /* Bail out of parallel execution if we are strict to throw. */ - if (mode == ParallelExecution) - return !strict; - return true; - } - - if (!obj->maybeCopyElementsForWrite(cxArg)) - return false; - - if (mode == ParallelExecution) - return obj->setDenseElementIfHasType(index, vp); - - obj->setDenseElementWithType(cxArg->asJSContext(), index, vp); - return true; - } - - if (obj->is<ArrayObject>() && id == NameToId(cxArg->names().length)) { - Rooted<ArrayObject*> arr(cxArg, &obj->as<ArrayObject>()); - return ArraySetLength<mode>(cxArg, arr, id, attrs, vp, strict); - } - - if (!shape) { - bool extensible; - if (mode == ParallelExecution) { - if (obj->is<ProxyObject>()) - return false; - extensible = obj->nonProxyIsExtensible(); - } else { - if (!JSObject::isExtensible(cxArg->asJSContext(), obj, &extensible)) - return false; - } - - if (!extensible) { - /* Error in strict mode code, warn with extra warnings option, otherwise do nothing. */ - if (strict) - return obj->reportNotExtensible(cxArg); - if (mode == SequentialExecution && - cxArg->asJSContext()->compartment()->options().extraWarnings(cxArg->asJSContext())) - { - return obj->reportNotExtensible(cxArg, JSREPORT_STRICT | JSREPORT_WARNING); - } - return true; - } - - if (mode == ParallelExecution) { - if (obj->isDelegate()) - return false; - - if (getter != JS_PropertyStub || !HasTypePropertyId(obj, id, vp)) - return false; - } else { - JSContext *cx = cxArg->asJSContext(); - - /* Purge the property cache of now-shadowed id in obj's scope chain. */ - if (!PurgeScopeChain(cx, obj, id)) - return false; - } - - return DefinePropertyOrElement<mode>(cxArg, obj, id, getter, setter, - attrs, vp, true, strict); - } - - return NativeSet<mode>(cxArg, obj, receiver, shape, strict, vp); -} - -template bool -baseops::SetPropertyHelper<SequentialExecution>(JSContext *cx, HandleNativeObject obj, - HandleObject receiver, HandleId id, - QualifiedBool qualified, - MutableHandleValue vp, bool strict); -template bool -baseops::SetPropertyHelper<ParallelExecution>(ForkJoinContext *cx, HandleNativeObject obj, - HandleObject receiver, HandleId id, - QualifiedBool qualified, - MutableHandleValue vp, bool strict); - -bool -baseops::SetElementHelper(JSContext *cx, HandleNativeObject obj, HandleObject receiver, uint32_t index, - MutableHandleValue vp, bool strict) -{ - RootedId id(cx); - if (!IndexToId(cx, index, &id)) - return false; - return baseops::SetPropertyHelper<SequentialExecution>(cx, obj, receiver, id, Qualified, vp, - strict); -} - -bool -baseops::GetAttributes(JSContext *cx, HandleNativeObject obj, HandleId id, unsigned *attrsp) -{ - RootedObject nobj(cx); - RootedShape shape(cx); - if (!baseops::LookupProperty<CanGC>(cx, obj, id, &nobj, &shape)) - return false; - if (!shape) { - *attrsp = 0; - return true; - } - if (!nobj->isNative()) - return JSObject::getGenericAttributes(cx, nobj, id, attrsp); - - *attrsp = GetShapeAttributes(nobj, shape); - return true; -} - -bool -baseops::SetAttributes(JSContext *cx, HandleNativeObject obj, HandleId id, unsigned *attrsp) -{ - RootedObject nobj(cx); - RootedShape shape(cx); - if (!baseops::LookupProperty<CanGC>(cx, obj, id, &nobj, &shape)) - return false; - if (!shape) - return true; - if (nobj->isNative() && IsImplicitDenseOrTypedArrayElement(shape)) { - if (IsAnyTypedArray(nobj.get())) { - if (*attrsp == (JSPROP_ENUMERATE | JSPROP_PERMANENT)) - return true; - JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_SET_ARRAY_ATTRS); - return false; - } - if (!NativeObject::sparsifyDenseElement(cx, nobj.as<NativeObject>(), JSID_TO_INT(id))) - return false; - shape = nobj->as<NativeObject>().lookup(cx, id); - } - if (nobj->isNative()) { - if (!NativeObject::changePropertyAttributes(cx, nobj.as<NativeObject>(), shape, *attrsp)) - return false; - if (*attrsp & JSPROP_READONLY) - MarkTypePropertyNonWritable(cx, nobj, id); - return true; - } else { - return JSObject::setGenericAttributes(cx, nobj, id, attrsp); - } -} - -bool -baseops::DeleteGeneric(JSContext *cx, HandleNativeObject obj, HandleId id, bool *succeeded) -{ - RootedObject proto(cx); - RootedShape shape(cx); - if (!baseops::LookupProperty<CanGC>(cx, obj, id, &proto, &shape)) - return false; - if (!shape || proto != obj) { - /* - * If no property, or the property comes from a prototype, call the - * class's delProperty hook, passing succeeded as the result parameter. - */ - return CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, id, succeeded); - } - - cx->runtime()->gc.poke(); - - if (IsImplicitDenseOrTypedArrayElement(shape)) { - if (IsAnyTypedArray(obj)) { - // Don't delete elements from typed arrays. - *succeeded = false; - return true; - } - - if (!CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, id, succeeded)) - return false; - if (!succeeded) - return true; - - NativeObject *nobj = &obj->as<NativeObject>(); - if (!nobj->maybeCopyElementsForWrite(cx)) - return false; - - nobj->setDenseElementHole(cx, JSID_TO_INT(id)); - return SuppressDeletedProperty(cx, obj, id); - } - - if (!shape->configurable()) { - *succeeded = false; - return true; - } - - RootedId propid(cx, shape->propid()); - if (!CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, propid, succeeded)) - return false; - if (!succeeded) - return true; - - return obj->removeProperty(cx, id) && SuppressDeletedProperty(cx, obj, id); -} - bool js::WatchGuts(JSContext *cx, JS::HandleObject origObj, JS::HandleId id, JS::HandleObject callable) { RootedObject obj(cx, GetInnerObject(origObj)); if (obj->isNative()) { // Use sparse indexes for watched objects, as dense elements can be // written to without checking the watchpoint map. if (!NativeObject::sparsifyDenseElements(cx, obj.as<NativeObject>())) @@ -6569,8 +4320,63 @@ JSObject::addSizeOfExcludingThis(mozilla #ifdef JS_HAS_CTYPES } else { // This must be the last case. info->objectsMallocHeapMisc += js::SizeOfDataIfCDataObject(mallocSizeOf, const_cast<JSObject *>(this)); #endif } } + +bool +JSObject::hasIdempotentProtoChain() const +{ + // Return false if obj (or an object on its proto chain) is non-native or + // has a resolve or lookup hook. + JSObject *obj = const_cast<JSObject *>(this); + while (true) { + if (!obj->isNative()) + return false; + + JSResolveOp resolve = obj->getClass()->resolve; + if (resolve != JS_ResolveStub && resolve != (JSResolveOp) js::fun_resolve) + return false; + + if (obj->getOps()->lookupProperty || obj->getOps()->lookupGeneric || obj->getOps()->lookupElement) + return false; + + obj = obj->getProto(); + if (!obj) + return true; + } +} + +void +JSObject::markChildren(JSTracer *trc) +{ + MarkTypeObject(trc, &type_, "type"); + + MarkShape(trc, &shape_, "shape"); + + const Class *clasp = type_->clasp(); + if (clasp->trace) + clasp->trace(trc, this); + + if (shape_->isNative()) { + NativeObject *nobj = &as<NativeObject>(); + MarkObjectSlots(trc, nobj, 0, nobj->slotSpan()); + + do { + if (nobj->denseElementsAreCopyOnWrite()) { + HeapPtrNativeObject &owner = nobj->getElementsHeader()->ownerObject(); + if (owner != nobj) { + MarkObject(trc, &owner, "objectElementsOwner"); + break; + } + } + + gc::MarkArraySlots(trc, + nobj->getDenseInitializedLength(), + nobj->getDenseElementsAllowCopyOnWrite(), + "objectElements"); + } while (false); + } +}
--- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -1058,27 +1058,16 @@ CreateThis(JSContext *cx, const js::Clas extern JSObject * CloneObject(JSContext *cx, HandleObject obj, Handle<js::TaggedProto> proto, HandleObject parent); extern NativeObject * DeepCloneObjectLiteral(JSContext *cx, HandleNativeObject obj, NewObjectKind newKind = GenericObject); /* - * Return successfully added or changed shape or nullptr on error. - */ -extern bool -DefineNativeProperty(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, HandleValue value, - PropertyOp getter, StrictPropertyOp setter, unsigned attrs); - -extern bool -LookupNativeProperty(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, - js::MutableHandleObject objp, js::MutableHandleShape propp); - -/* * Call the [[DefineOwnProperty]] internal method of obj. * * If obj is an array, this follows ES5 15.4.5.1. * If obj is any other native object, this follows ES5 8.12.9. * If obj is a proxy, this calls the proxy handler's defineProperty method. * Otherwise, this reports an error and returns false. */ extern bool @@ -1133,26 +1122,16 @@ LookupNameUnqualified(JSContext *cx, Han extern JSObject * js_FindVariableScope(JSContext *cx, JSFunction **funp); namespace js { bool -NativeGet(JSContext *cx, HandleObject obj, HandleNativeObject pobj, - HandleShape shape, MutableHandle<Value> vp); - -template <ExecutionMode mode> -bool -NativeSet(typename ExecutionModeTraits<mode>::ContextType cx, - HandleNativeObject obj, HandleObject receiver, - HandleShape shape, bool strict, MutableHandleValue vp); - -bool LookupPropertyPure(JSObject *obj, jsid id, NativeObject **objp, Shape **propp); bool GetPropertyPure(ThreadSafeContext *cx, JSObject *obj, jsid id, Value *vp); inline bool GetPropertyPure(ThreadSafeContext *cx, JSObject *obj, PropertyName *name, Value *vp) { @@ -1164,29 +1143,16 @@ GetOwnPropertyDescriptor(JSContext *cx, MutableHandle<PropertyDescriptor> desc); bool GetOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp); bool NewPropertyDescriptorObject(JSContext *cx, Handle<PropertyDescriptor> desc, MutableHandleValue vp); -/* - * If obj has an already-resolved data property for id, return true and - * store the property value in *vp. - */ -extern bool -HasDataProperty(JSContext *cx, NativeObject *obj, jsid id, Value *vp); - -inline bool -HasDataProperty(JSContext *cx, NativeObject *obj, PropertyName *name, Value *vp) -{ - return HasDataProperty(cx, obj, NameToId(name), vp); -} - extern bool IsDelegate(JSContext *cx, HandleObject obj, const Value &v, bool *result); // obj is a JSObject*, but we root it immediately up front. We do it // that way because we need a Rooted temporary in this method anyway. extern bool IsDelegateOfObject(JSContext *cx, HandleObject protoObj, JSObject* obj, bool *result); @@ -1257,11 +1223,21 @@ GetFirstArgumentAsObject(JSContext *cx, /* Helpers for throwing. These always return false. */ extern bool Throw(JSContext *cx, jsid id, unsigned errorNumber); extern bool Throw(JSContext *cx, JSObject *obj, unsigned errorNumber); +namespace baseops { + +extern bool +Watch(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable); + +extern bool +Unwatch(JSContext *cx, JS::HandleObject obj, JS::HandleId id); + +} /* namespace baseops */ + } /* namespace js */ #endif /* jsobj_h */
--- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -792,25 +792,16 @@ NewObjectMetadata(ExclusiveContext *cxAr if (!cx->compartment()->callObjectMetadataCallback(cx, pmetadata)) return false; } } return true; } -inline bool -DefineNativeProperty(ExclusiveContext *cx, HandleNativeObject obj, - PropertyName *name, HandleValue value, - PropertyOp getter, StrictPropertyOp setter, unsigned attrs) -{ - Rooted<jsid> id(cx, NameToId(name)); - return DefineNativeProperty(cx, obj, id, value, getter, setter, attrs); -} - namespace baseops { inline bool LookupProperty(ExclusiveContext *cx, HandleNativeObject obj, PropertyName *name, MutableHandleObject objp, MutableHandleShape propp) { Rooted<jsid> id(cx, NameToId(name)); return LookupProperty<CanGC>(cx, obj, id, objp, propp); @@ -821,16 +812,50 @@ DefineProperty(ExclusiveContext *cx, Han JSPropertyOp getter, JSStrictPropertyOp setter, unsigned attrs) { Rooted<jsid> id(cx, NameToId(name)); return DefineGeneric(cx, obj, id, value, getter, setter, attrs); } } /* namespace baseops */ +static inline unsigned +ApplyAttributes(unsigned attrs, bool enumerable, bool writable, bool configurable) +{ + /* + * Respect the fact that some callers may want to preserve existing attributes as much as + * possible, or install defaults otherwise. + */ + if (attrs & JSPROP_IGNORE_ENUMERATE) { + attrs &= ~JSPROP_IGNORE_ENUMERATE; + if (enumerable) + attrs |= JSPROP_ENUMERATE; + else + attrs &= ~JSPROP_ENUMERATE; + } + if (attrs & JSPROP_IGNORE_READONLY) { + attrs &= ~JSPROP_IGNORE_READONLY; + // Only update the writability if it's relevant + if (!(attrs & (JSPROP_GETTER | JSPROP_SETTER))) { + if (!writable) + attrs |= JSPROP_READONLY; + else + attrs &= ~JSPROP_READONLY; + } + } + if (attrs & JSPROP_IGNORE_PERMANENT) { + attrs &= ~JSPROP_IGNORE_PERMANENT; + if (!configurable) + attrs |= JSPROP_PERMANENT; + else + attrs &= ~JSPROP_PERMANENT; + } + return attrs; +} + } /* namespace js */ extern js::NativeObject * js_InitClass(JSContext *cx, js::HandleObject obj, JSObject *parent_proto, const js::Class *clasp, JSNative constructor, unsigned nargs, const JSPropertySpec *ps, const JSFunctionSpec *fs, const JSPropertySpec *static_ps, const JSFunctionSpec *static_fs, js::NativeObject **ctorp = nullptr,
--- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -883,22 +883,22 @@ InnerViewTable::removeViews(ArrayBufferO MOZ_ASSERT(p); map.remove(p); } bool InnerViewTable::sweepEntry(JSObject **pkey, ViewVector &views) { - if (IsObjectAboutToBeFinalized(pkey)) + if (IsObjectAboutToBeFinalizedFromAnyThread(pkey)) return true; MOZ_ASSERT(!views.empty()); for (size_t i = 0; i < views.length(); i++) { - if (IsObjectAboutToBeFinalized(&views[i])) { + if (IsObjectAboutToBeFinalizedFromAnyThread(&views[i])) { views[i--] = views.back(); views.popBack(); } } return views.empty(); }
--- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -709,16 +709,111 @@ GlobalHelperThreadState::canStartCompres } bool GlobalHelperThreadState::canStartGCHelperTask() { return !gcHelperWorklist().empty(); } +bool +GlobalHelperThreadState::canStartGCParallelTask() +{ + return !gcParallelWorklist().empty(); +} + +bool +js::GCParallelTask::startWithLockHeld() +{ + MOZ_ASSERT(HelperThreadState().isLocked()); + + // Tasks cannot be started twice. + MOZ_ASSERT(state == NotStarted); + + // If we do the shutdown GC before running anything, we may never + // have initialized the helper threads. Just use the serial path + // since we cannot safely intialize them at this point. + if (!HelperThreadState().threads) + return false; + + if (!HelperThreadState().gcParallelWorklist().append(this)) + return false; + state = Dispatched; + + HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER); + + return true; +} + +bool +js::GCParallelTask::start() +{ + AutoLockHelperThreadState helperLock; + return startWithLockHeld(); +} + +void +js::GCParallelTask::joinWithLockHeld() +{ + MOZ_ASSERT(HelperThreadState().isLocked()); + + if (state == NotStarted) + return; + + while (state != Finished) + HelperThreadState().wait(GlobalHelperThreadState::CONSUMER); + state = NotStarted; +} + +void +js::GCParallelTask::join() +{ + AutoLockHelperThreadState helperLock; + joinWithLockHeld(); +} + +void +js::GCParallelTask::runFromMainThread(JSRuntime *rt) +{ + MOZ_ASSERT(state == NotStarted); + MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(rt)); + uint64_t timeStart = PRMJ_Now(); + run(); + duration_ = PRMJ_Now() - timeStart; +} + +void +js::GCParallelTask::runFromHelperThread() +{ + MOZ_ASSERT(HelperThreadState().isLocked()); + + { + AutoUnlockHelperThreadState parallelSection; + uint64_t timeStart = PRMJ_Now(); + run(); + duration_ = PRMJ_Now() - timeStart; + } + + state = Finished; + HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER); +} + +void +HelperThread::handleGCParallelWorkload() +{ + MOZ_ASSERT(HelperThreadState().isLocked()); + MOZ_ASSERT(HelperThreadState().canStartGCParallelTask()); + MOZ_ASSERT(idle()); + + MOZ_ASSERT(!gcParallelTask); + gcParallelTask = HelperThreadState().gcParallelWorklist().popCopy(); + gcParallelTask->runFromHelperThread(); + gcParallelTask = nullptr; +} + static void LeaveParseTaskZone(JSRuntime *rt, ParseTask *task) { // Mark the zone as no longer in use by an ExclusiveContext, and available // to be collected by the GC. task->cx->leaveCompartment(task->cx->compartment()); rt->clearUsedByExclusiveThread(task->cx->zone()); } @@ -1232,17 +1327,18 @@ HelperThread::threadLoop() bool ionCompile = false; while (true) { if (terminate) return; if (HelperThreadState().canStartAsmJSCompile() || (ionCompile = HelperThreadState().pendingIonCompileHasSufficientPriority()) || HelperThreadState().canStartParseTask() || HelperThreadState().canStartCompressionTask() || - HelperThreadState().canStartGCHelperTask()) + HelperThreadState().canStartGCHelperTask() || + HelperThreadState().canStartGCParallelTask()) { break; } HelperThreadState().wait(GlobalHelperThreadState::PRODUCER); } // Dispatch tasks, prioritizing AsmJS work. if (HelperThreadState().canStartAsmJSCompile()) @@ -1250,12 +1346,14 @@ HelperThread::threadLoop() else if (ionCompile) handleIonWorkload(); else if (HelperThreadState().canStartParseTask()) handleParseWorkload(); else if (HelperThreadState().canStartCompressionTask()) handleCompressionWorkload(); else if (HelperThreadState().canStartGCHelperTask()) handleGCHelperWorkload(); + else if (HelperThreadState().canStartGCParallelTask()) + handleGCParallelWorkload(); else MOZ_CRASH("No task to perform"); } }
--- a/js/src/vm/HelperThreads.h +++ b/js/src/vm/HelperThreads.h @@ -42,16 +42,17 @@ class GlobalHelperThreadState // Number of threads to create. May be accessed without locking. size_t threadCount; typedef Vector<jit::IonBuilder*, 0, SystemAllocPolicy> IonBuilderVector; typedef Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> AsmJSParallelTaskVector; typedef Vector<ParseTask*, 0, SystemAllocPolicy> ParseTaskVector; typedef Vector<SourceCompressionTask*, 0, SystemAllocPolicy> SourceCompressionTaskVector; typedef Vector<GCHelperState *, 0, SystemAllocPolicy> GCHelperStateVector; + typedef Vector<GCParallelTask *, 0, SystemAllocPolicy> GCParallelTaskVector; typedef mozilla::LinkedList<jit::IonBuilder> IonBuilderList; // List of available threads, or null if the thread state has not been initialized. HelperThread *threads; private: // The lists below are all protected by |lock|. @@ -82,16 +83,19 @@ class GlobalHelperThreadState ParseTaskVector parseWaitingOnGC_; // Source compression worklist. SourceCompressionTaskVector compressionWorklist_; // Runtimes which have sweeping / allocating work to do. GCHelperStateVector gcHelperWorklist_; + // GC tasks needing to be done in parallel. + GCParallelTaskVector gcParallelWorklist_; + public: size_t maxIonCompilationThreads() const { return 1; } size_t maxAsmJSCompilationThreads() const { if (cpuCount < 2) return 2; return cpuCount; @@ -173,21 +177,27 @@ class GlobalHelperThreadState return compressionWorklist_; } GCHelperStateVector &gcHelperWorklist() { MOZ_ASSERT(isLocked()); return gcHelperWorklist_; } + GCParallelTaskVector &gcParallelWorklist() { + MOZ_ASSERT(isLocked()); + return gcParallelWorklist_; + } + bool canStartAsmJSCompile(); bool canStartIonCompile(); bool canStartParseTask(); bool canStartCompressionTask(); bool canStartGCHelperTask(); + bool canStartGCParallelTask(); // Unlike the methods above, the value returned by this method can change // over time, even if the helper thread state lock is held throughout. bool pendingIonCompileHasSufficientPriority(); jit::IonBuilder *highestPriorityPendingIonCompile(bool remove = false); HelperThread *lowestPriorityUnpausedIonCompileAtThreshold(); HelperThread *highestPriorityPausedIonCompile(); @@ -294,27 +304,36 @@ struct HelperThread ParseTask *parseTask; /* Any source being compressed on this thread. */ SourceCompressionTask *compressionTask; /* Any GC state for background sweeping or allocating being performed. */ GCHelperState *gcHelperState; + /* State required to perform a GC parallel task. */ + GCParallelTask *gcParallelTask; + bool idle() const { - return !ionBuilder && !asmData && !parseTask && !compressionTask && !gcHelperState; + return !ionBuilder && + !asmData && + !parseTask && + !compressionTask && + !gcHelperState && + !gcParallelTask; } void destroy(); void handleAsmJSWorkload(); void handleIonWorkload(); void handleParseWorkload(); void handleCompressionWorkload(); void handleGCHelperWorkload(); + void handleGCParallelWorkload(); static void ThreadMain(void *arg); void threadLoop(); }; /* Methods for interacting with helper threads. */ // Initialize helper threads unless already initialized.
--- a/js/src/vm/ObjectImpl-inl.h +++ b/js/src/vm/ObjectImpl-inl.h @@ -455,16 +455,236 @@ NewNativeObjectWithType(JSContext *cx, H inline NativeObject * NewNativeObjectWithType(JSContext *cx, HandleTypeObject type, JSObject *parent, NewObjectKind newKind = GenericObject) { return MaybeNativeObject(NewObjectWithType(cx, type, parent, newKind)); } +/* + * Call obj's resolve hook. + * + * cx, id, and flags are the parameters initially passed to the ongoing lookup; + * objp and propp are its out parameters. obj is an object along the prototype + * chain from where the lookup started. + * + * There are four possible outcomes: + * + * - On failure, report an error or exception and return false. + * + * - If we are already resolving a property of *curobjp, set *recursedp = true, + * and return true. + * + * - If the resolve hook finds or defines the sought property, set *objp and + * *propp appropriately, set *recursedp = false, and return true. + * + * - Otherwise no property was resolved. Set *propp = nullptr and + * *recursedp = false and return true. + */ +static MOZ_ALWAYS_INLINE bool +CallResolveOp(JSContext *cx, HandleNativeObject obj, HandleId id, MutableHandleObject objp, + MutableHandleShape propp, bool *recursedp) +{ + const Class *clasp = obj->getClass(); + JSResolveOp resolve = clasp->resolve; + + /* + * Avoid recursion on (obj, id) already being resolved on cx. + * + * Once we have successfully added an entry for (obj, key) to + * cx->resolvingTable, control must go through cleanup: before + * returning. But note that JS_DHASH_ADD may find an existing + * entry, in which case we bail to suppress runaway recursion. + */ + AutoResolving resolving(cx, obj, id); + if (resolving.alreadyStarted()) { + /* Already resolving id in obj -- suppress recursion. */ + *recursedp = true; + return true; + } + *recursedp = false; + + propp.set(nullptr); + + if (clasp->flags & JSCLASS_NEW_RESOLVE) { + JSNewResolveOp newresolve = reinterpret_cast<JSNewResolveOp>(resolve); + RootedObject obj2(cx, nullptr); + if (!newresolve(cx, obj, id, &obj2)) + return false; + + /* + * We trust the new style resolve hook to set obj2 to nullptr when + * the id cannot be resolved. But, when obj2 is not null, we do + * not assume that id must exist and do full nativeLookup for + * compatibility. + */ + if (!obj2) + return true; + + if (!obj2->isNative()) { + /* Whoops, newresolve handed back a foreign obj2. */ + MOZ_ASSERT(obj2 != obj); + return JSObject::lookupGeneric(cx, obj2, id, objp, propp); + } + + objp.set(obj2); + } else { + if (!resolve(cx, obj, id)) + return false; + + objp.set(obj); + } + + NativeObject *nobjp = &objp->as<NativeObject>(); + + if (JSID_IS_INT(id) && nobjp->containsDenseElement(JSID_TO_INT(id))) { + MarkDenseOrTypedArrayElementFound<CanGC>(propp); + return true; + } + + Shape *shape; + if (!nobjp->empty() && (shape = nobjp->lookup(cx, id))) + propp.set(shape); + else + objp.set(nullptr); + + return true; +} + +template <AllowGC allowGC> +static MOZ_ALWAYS_INLINE bool +LookupOwnPropertyInline(ExclusiveContext *cx, + typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, + typename MaybeRooted<jsid, allowGC>::HandleType id, + typename MaybeRooted<JSObject*, allowGC>::MutableHandleType objp, + typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp, + bool *donep) +{ + // Check for a native dense element. + if (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))) { + objp.set(obj); + MarkDenseOrTypedArrayElementFound<allowGC>(propp); + *donep = true; + return true; + } + + // Check for a typed array element. Integer lookups always finish here + // so that integer properties on the prototype are ignored even for out + // of bounds accesses. + if (IsAnyTypedArray(obj)) { + uint64_t index; + if (IsTypedArrayIndex(id, &index)) { + if (index < AnyTypedArrayLength(obj)) { + objp.set(obj); + MarkDenseOrTypedArrayElementFound<allowGC>(propp); + } else { + objp.set(nullptr); + propp.set(nullptr); + } + *donep = true; + return true; + } + } + + // Check for a native property. + if (Shape *shape = obj->lookup(cx, id)) { + objp.set(obj); + propp.set(shape); + *donep = true; + return true; + } + + // id was not found in obj. Try obj's resolve hook, if any. + if (obj->getClass()->resolve != JS_ResolveStub) { + if (!cx->shouldBeJSContext() || !allowGC) + return false; + + bool recursed; + if (!CallResolveOp(cx->asJSContext(), + MaybeRooted<NativeObject*, allowGC>::toHandle(obj), + MaybeRooted<jsid, allowGC>::toHandle(id), + MaybeRooted<JSObject*, allowGC>::toMutableHandle(objp), + MaybeRooted<Shape*, allowGC>::toMutableHandle(propp), + &recursed)) + { + return false; + } + + if (recursed) { + objp.set(nullptr); + propp.set(nullptr); + *donep = true; + return true; + } + + if (propp) { + *donep = true; + return true; + } + } + + *donep = false; + return true; +} + +template <AllowGC allowGC> +static MOZ_ALWAYS_INLINE bool +LookupPropertyInline(ExclusiveContext *cx, + typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, + typename MaybeRooted<jsid, allowGC>::HandleType id, + typename MaybeRooted<JSObject*, allowGC>::MutableHandleType objp, + typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp) +{ + /* NB: The logic of this procedure is implicitly reflected in + * BaselineIC.cpp's |EffectlesslyLookupProperty| logic. + * If this changes, please remember to update the logic there as well. + */ + + /* Search scopes starting with obj and following the prototype link. */ + typename MaybeRooted<NativeObject*, allowGC>::RootType current(cx, obj); + + while (true) { + bool done; + if (!LookupOwnPropertyInline<allowGC>(cx, current, id, objp, propp, &done)) + return false; + if (done) + return true; + + typename MaybeRooted<JSObject*, allowGC>::RootType proto(cx, current->getProto()); + + if (!proto) + break; + if (!proto->isNative()) { + if (!cx->shouldBeJSContext() || !allowGC) + return false; + return JSObject::lookupGeneric(cx->asJSContext(), + MaybeRooted<JSObject*, allowGC>::toHandle(proto), + MaybeRooted<jsid, allowGC>::toHandle(id), + MaybeRooted<JSObject*, allowGC>::toMutableHandle(objp), + MaybeRooted<Shape*, allowGC>::toMutableHandle(propp)); + } + + current = &proto->template as<NativeObject>(); + } + + objp.set(nullptr); + propp.set(nullptr); + return true; +} + +inline bool +DefineNativeProperty(ExclusiveContext *cx, HandleNativeObject obj, + PropertyName *name, HandleValue value, + PropertyOp getter, StrictPropertyOp setter, unsigned attrs) +{ + RootedId id(cx, NameToId(name)); + return DefineNativeProperty(cx, obj, id, value, getter, setter, attrs); +} + } // namespace js inline uint8_t * JSObject::fakeNativeFixedData(size_t nslots) const { return static_cast<const js::NativeObject*>(this)->fixedData(nslots); }
--- a/js/src/vm/ObjectImpl.cpp +++ b/js/src/vm/ObjectImpl.cpp @@ -1,27 +1,38 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "vm/ObjectImpl-inl.h" +#include "mozilla/CheckedInt.h" + +#include "jswatchpoint.h" + #include "gc/Marking.h" #include "js/Value.h" #include "vm/Debugger.h" #include "vm/TypedArrayCommon.h" #include "jsobjinlines.h" + +#include "vm/ArrayObject-inl.h" #include "vm/Shape-inl.h" using namespace js; using JS::GenericNaN; +using mozilla::DebugOnly; +using mozilla::RoundUpPow2; + +JS_STATIC_ASSERT(int32_t((NativeObject::NELEMENTS_LIMIT - 1) * sizeof(Value)) == + int64_t((NativeObject::NELEMENTS_LIMIT - 1) * sizeof(Value))); PropDesc::PropDesc() { setUndefined(); } void PropDesc::setUndefined() @@ -35,17 +46,16 @@ PropDesc::setUndefined() hasValue_ = false; hasWritable_ = false; hasEnumerable_ = false; hasConfigurable_ = false; isUndefined_ = true; } - bool PropDesc::checkGetter(JSContext *cx) { if (hasGet_) { if (!IsCallable(get_) && !get_.isUndefined()) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_GET_SET_FIELD, js_getter_str); return false; @@ -283,46 +293,2013 @@ js::NativeObject::dynamicSlotsCount(uint return SLOT_CAPACITY_MIN; uint32_t slots = mozilla::RoundUpPow2(span); MOZ_ASSERT(slots >= span); return slots; } void -JSObject::markChildren(JSTracer *trc) -{ - MarkTypeObject(trc, &type_, "type"); - - MarkShape(trc, &shape_, "shape"); - - const Class *clasp = type_->clasp(); - if (clasp->trace) - clasp->trace(trc, this); - - if (shape_->isNative()) { - NativeObject *nobj = &as<NativeObject>(); - MarkObjectSlots(trc, nobj, 0, nobj->slotSpan()); - - do { - if (nobj->denseElementsAreCopyOnWrite()) { - HeapPtrNativeObject &owner = nobj->getElementsHeader()->ownerObject(); - if (owner != nobj) { - MarkObject(trc, &owner, "objectElementsOwner"); - break; - } - } - - gc::MarkArraySlots(trc, - nobj->getDenseInitializedLength(), - nobj->getDenseElementsAllowCopyOnWrite(), - "objectElements"); - } while (false); - } -} - -void PropDesc::trace(JSTracer *trc) { gc::MarkValueRoot(trc, &value_, "PropDesc value"); gc::MarkValueRoot(trc, &get_, "PropDesc get"); gc::MarkValueRoot(trc, &set_, "PropDesc set"); } + +/* static */ inline bool +NativeObject::updateSlotsForSpan(ThreadSafeContext *cx, + HandleNativeObject obj, size_t oldSpan, size_t newSpan) +{ + MOZ_ASSERT(cx->isThreadLocal(obj)); + MOZ_ASSERT(oldSpan != newSpan); + + size_t oldCount = dynamicSlotsCount(obj->numFixedSlots(), oldSpan, obj->getClass()); + size_t newCount = dynamicSlotsCount(obj->numFixedSlots(), newSpan, obj->getClass()); + + if (oldSpan < newSpan) { + if (oldCount < newCount && !growSlots(cx, obj, oldCount, newCount)) + return false; + + if (newSpan == oldSpan + 1) + obj->initSlotUnchecked(oldSpan, UndefinedValue()); + else + obj->initializeSlotRange(oldSpan, newSpan - oldSpan); + } else { + /* Trigger write barriers on the old slots before reallocating. */ + obj->prepareSlotRangeForOverwrite(newSpan, oldSpan); + obj->invalidateSlotRange(newSpan, oldSpan - newSpan); + + if (oldCount > newCount) + shrinkSlots(cx, obj, oldCount, newCount); + } + + return true; +} + +/* static */ bool +NativeObject::setLastProperty(ThreadSafeContext *cx, HandleNativeObject obj, HandleShape shape) +{ + MOZ_ASSERT(cx->isThreadLocal(obj)); + MOZ_ASSERT(!obj->inDictionaryMode()); + MOZ_ASSERT(!shape->inDictionary()); + MOZ_ASSERT(shape->compartment() == obj->compartment()); + MOZ_ASSERT(shape->numFixedSlots() == obj->numFixedSlots()); + + size_t oldSpan = obj->lastProperty()->slotSpan(); + size_t newSpan = shape->slotSpan(); + + if (oldSpan == newSpan) { + obj->shape_ = shape; + return true; + } + + if (!updateSlotsForSpan(cx, obj, oldSpan, newSpan)) + return false; + + obj->shape_ = shape; + return true; +} + +void +NativeObject::setLastPropertyShrinkFixedSlots(Shape *shape) +{ + MOZ_ASSERT(!inDictionaryMode()); + MOZ_ASSERT(!shape->inDictionary()); + MOZ_ASSERT(shape->compartment() == compartment()); + MOZ_ASSERT(lastProperty()->slotSpan() == shape->slotSpan()); + + DebugOnly<size_t> oldFixed = numFixedSlots(); + DebugOnly<size_t> newFixed = shape->numFixedSlots(); + MOZ_ASSERT(newFixed < oldFixed); + MOZ_ASSERT(shape->slotSpan() <= oldFixed); + MOZ_ASSERT(shape->slotSpan() <= newFixed); + MOZ_ASSERT(dynamicSlotsCount(oldFixed, shape->slotSpan(), getClass()) == 0); + MOZ_ASSERT(dynamicSlotsCount(newFixed, shape->slotSpan(), getClass()) == 0); + + shape_ = shape; +} + +/* static */ bool +NativeObject::setSlotSpan(ThreadSafeContext *cx, HandleNativeObject obj, uint32_t span) +{ + MOZ_ASSERT(cx->isThreadLocal(obj)); + MOZ_ASSERT(obj->inDictionaryMode()); + + size_t oldSpan = obj->lastProperty()->base()->slotSpan(); + if (oldSpan == span) + return true; + + if (!updateSlotsForSpan(cx, obj, oldSpan, span)) + return false; + + obj->lastProperty()->base()->setSlotSpan(span); + return true; +} + +// This will not run the garbage collector. If a nursery cannot accomodate the slot array +// an attempt will be made to place the array in the tenured area. +static HeapSlot * +AllocateSlots(ThreadSafeContext *cx, JSObject *obj, uint32_t nslots) +{ +#ifdef JSGC_GENERATIONAL + if (cx->isJSContext()) + return cx->asJSContext()->runtime()->gc.nursery.allocateSlots(obj, nslots); +#endif +#ifdef JSGC_FJGENERATIONAL + if (cx->isForkJoinContext()) + return cx->asForkJoinContext()->nursery().allocateSlots(obj, nslots); +#endif + return obj->zone()->pod_malloc<HeapSlot>(nslots); +} + +// This will not run the garbage collector. If a nursery cannot accomodate the slot array +// an attempt will be made to place the array in the tenured area. +// +// If this returns null then the old slots will be left alone. +static HeapSlot * +ReallocateSlots(ThreadSafeContext *cx, JSObject *obj, HeapSlot *oldSlots, + uint32_t oldCount, uint32_t newCount) +{ +#ifdef JSGC_GENERATIONAL + if (cx->isJSContext()) { + return cx->asJSContext()->runtime()->gc.nursery.reallocateSlots(obj, oldSlots, + oldCount, newCount); + } +#endif +#ifdef JSGC_FJGENERATIONAL + if (cx->isForkJoinContext()) { + return cx->asForkJoinContext()->nursery().reallocateSlots(obj, oldSlots, + oldCount, newCount); + } +#endif + return obj->zone()->pod_realloc<HeapSlot>(oldSlots, oldCount, newCount); +} + +/* static */ bool +NativeObject::growSlots(ThreadSafeContext *cx, HandleNativeObject obj, uint32_t oldCount, uint32_t newCount) +{ + MOZ_ASSERT(cx->isThreadLocal(obj)); + MOZ_ASSERT(newCount > oldCount); + MOZ_ASSERT_IF(!obj->is<ArrayObject>(), newCount >= SLOT_CAPACITY_MIN); + + /* + * Slot capacities are determined by the span of allocated objects. Due to + * the limited number of bits to store shape slots, object growth is + * throttled well before the slot capacity can overflow. + */ + MOZ_ASSERT(newCount < NELEMENTS_LIMIT); + + if (!oldCount) { + obj->slots = AllocateSlots(cx, obj, newCount); + if (!obj->slots) + return false; + Debug_SetSlotRangeToCrashOnTouch(obj->slots, newCount); + return true; + } + + HeapSlot *newslots = ReallocateSlots(cx, obj, obj->slots, oldCount, newCount); + if (!newslots) + return false; /* Leave slots at its old size. */ + + obj->slots = newslots; + + Debug_SetSlotRangeToCrashOnTouch(obj->slots + oldCount, newCount - oldCount); + + return true; +} + +static void +FreeSlots(ThreadSafeContext *cx, HeapSlot *slots) +{ +#ifdef JSGC_GENERATIONAL + // Note: threads without a JSContext do not have access to GGC nursery allocated things. + if (cx->isJSContext()) + return cx->asJSContext()->runtime()->gc.nursery.freeSlots(slots); +#endif +#ifdef JSGC_FJGENERATIONAL + if (cx->isForkJoinContext()) + return cx->asForkJoinContext()->nursery().freeSlots(slots); +#endif + js_free(slots); +} + +/* static */ void +NativeObject::shrinkSlots(ThreadSafeContext *cx, HandleNativeObject obj, + uint32_t oldCount, uint32_t newCount) +{ + MOZ_ASSERT(cx->isThreadLocal(obj)); + MOZ_ASSERT(newCount < oldCount); + + if (newCount == 0) { + FreeSlots(cx, obj->slots); + obj->slots = nullptr; + return; + } + + MOZ_ASSERT_IF(!obj->is<ArrayObject>(), newCount >= SLOT_CAPACITY_MIN); + + HeapSlot *newslots = ReallocateSlots(cx, obj, obj->slots, oldCount, newCount); + if (!newslots) + return; /* Leave slots at its old size. */ + + obj->slots = newslots; +} + +/* static */ bool +NativeObject::sparsifyDenseElement(ExclusiveContext *cx, HandleNativeObject obj, uint32_t index) +{ + if (!obj->maybeCopyElementsForWrite(cx)) + return false; + + RootedValue value(cx, obj->getDenseElement(index)); + MOZ_ASSERT(!value.isMagic(JS_ELEMENTS_HOLE)); + + removeDenseElementForSparseIndex(cx, obj, index); + + uint32_t slot = obj->slotSpan(); + if (!obj->addDataProperty(cx, INT_TO_JSID(index), slot, JSPROP_ENUMERATE)) { + obj->setDenseElement(index, value); + return false; + } + + MOZ_ASSERT(slot == obj->slotSpan() - 1); + obj->initSlot(slot, value); + + return true; +} + +/* static */ bool +NativeObject::sparsifyDenseElements(js::ExclusiveContext *cx, HandleNativeObject obj) +{ + if (!obj->maybeCopyElementsForWrite(cx)) + return false; + + uint32_t initialized = obj->getDenseInitializedLength(); + + /* Create new properties with the value of non-hole dense elements. */ + for (uint32_t i = 0; i < initialized; i++) { + if (obj->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) + continue; + + if (!sparsifyDenseElement(cx, obj, i)) + return false; + } + + if (initialized) + obj->setDenseInitializedLength(0); + + /* + * Reduce storage for dense elements which are now holes. Explicitly mark + * the elements capacity as zero, so that any attempts to add dense + * elements will be caught in ensureDenseElements. + */ + if (obj->getDenseCapacity()) { + obj->shrinkElements(cx, 0); + obj->getElementsHeader()->capacity = 0; + } + + return true; +} + +bool +NativeObject::willBeSparseElements(uint32_t requiredCapacity, uint32_t newElementsHint) +{ + MOZ_ASSERT(isNative()); + MOZ_ASSERT(requiredCapacity > MIN_SPARSE_INDEX); + + uint32_t cap = getDenseCapacity(); + MOZ_ASSERT(requiredCapacity >= cap); + + if (requiredCapacity >= NELEMENTS_LIMIT) + return true; + + uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO; + if (newElementsHint >= minimalDenseCount) + return false; + minimalDenseCount -= newElementsHint; + + if (minimalDenseCount > cap) + return true; + + uint32_t len = getDenseInitializedLength(); + const Value *elems = getDenseElements(); + for (uint32_t i = 0; i < len; i++) { + if (!elems[i].isMagic(JS_ELEMENTS_HOLE) && !--minimalDenseCount) + return false; + } + return true; +} + +/* static */ NativeObject::EnsureDenseResult +NativeObject::maybeDensifySparseElements(js::ExclusiveContext *cx, HandleNativeObject obj) +{ + /* + * Wait until after the object goes into dictionary mode, which must happen + * when sparsely packing any array with more than MIN_SPARSE_INDEX elements + * (see PropertyTree::MAX_HEIGHT). + */ + if (!obj->inDictionaryMode()) + return ED_SPARSE; + + /* + * Only measure the number of indexed properties every log(n) times when + * populating the object. + */ + uint32_t slotSpan = obj->slotSpan(); + if (slotSpan != RoundUpPow2(slotSpan)) + return ED_SPARSE; + + /* Watch for conditions under which an object's elements cannot be dense. */ + if (!obj->nonProxyIsExtensible() || obj->watched()) + return ED_SPARSE; + + /* + * The indexes in the object need to be sufficiently dense before they can + * be converted to dense mode. + */ + uint32_t numDenseElements = 0; + uint32_t newInitializedLength = 0; + + RootedShape shape(cx, obj->lastProperty()); + while (!shape->isEmptyShape()) { + uint32_t index; + if (js_IdIsIndex(shape->propid(), &index)) { + if (shape->attributes() == JSPROP_ENUMERATE && + shape->hasDefaultGetter() && + shape->hasDefaultSetter()) + { + numDenseElements++; + newInitializedLength = Max(newInitializedLength, index + 1); + } else { + /* + * For simplicity, only densify the object if all indexed + * properties can be converted to dense elements. + */ + return ED_SPARSE; + } + } + shape = shape->previous(); + } + + if (numDenseElements * SPARSE_DENSITY_RATIO < newInitializedLength) + return ED_SPARSE; + + if (newInitializedLength >= NELEMENTS_LIMIT) + return ED_SPARSE; + + /* + * This object meets all necessary restrictions, convert all indexed + * properties into dense elements. + */ + + if (!obj->maybeCopyElementsForWrite(cx)) + return ED_FAILED; + + if (newInitializedLength > obj->getDenseCapacity()) { + if (!obj->growElements(cx, newInitializedLength)) + return ED_FAILED; + } + + obj->ensureDenseInitializedLength(cx, newInitializedLength, 0); + + RootedValue value(cx); + + shape = obj->lastProperty(); + while (!shape->isEmptyShape()) { + jsid id = shape->propid(); + uint32_t index; + if (js_IdIsIndex(id, &index)) { + value = obj->getSlot(shape->slot()); + + /* + * When removing a property from a dictionary, the specified + * property will be removed from the dictionary list and the + * last property will then be changed due to reshaping the object. + * Compute the next shape in the traverse, watching for such + * removals from the list. + */ + if (shape != obj->lastProperty()) { + shape = shape->previous(); + if (!obj->removeProperty(cx, id)) + return ED_FAILED; + } else { + if (!obj->removeProperty(cx, id)) + return ED_FAILED; + shape = obj->lastProperty(); + } + + obj->setDenseElement(index, value); + } else { + shape = shape->previous(); + } + } + + /* + * All indexed properties on the object are now dense, clear the indexed + * flag so that we will not start using sparse indexes again if we need + * to grow the object. + */ + if (!obj->clearFlag(cx, BaseShape::INDEXED)) + return ED_FAILED; + + return ED_OK; +} + +// This will not run the garbage collector. If a nursery cannot accomodate the element array +// an attempt will be made to place the array in the tenured area. +static ObjectElements * +AllocateElements(ThreadSafeContext *cx, JSObject *obj, uint32_t nelems) +{ +#ifdef JSGC_GENERATIONAL + if (cx->isJSContext()) + return cx->asJSContext()->runtime()->gc.nursery.allocateElements(obj, nelems); +#endif +#ifdef JSGC_FJGENERATIONAL + if (cx->isForkJoinContext()) + return cx->asForkJoinContext()->nursery().allocateElements(obj, nelems); +#endif + + return reinterpret_cast<js::ObjectElements *>(obj->zone()->pod_malloc<HeapSlot>(nelems)); +} + +// This will not run the garbage collector. If a nursery cannot accomodate the element array +// an attempt will be made to place the array in the tenured area. +static ObjectElements * +ReallocateElements(ThreadSafeContext *cx, JSObject *obj, ObjectElements *oldHeader, + uint32_t oldCount, uint32_t newCount) +{ +#ifdef JSGC_GENERATIONAL + if (cx->isJSContext()) { + return cx->asJSContext()->runtime()->gc.nursery.reallocateElements(obj, oldHeader, + oldCount, newCount); + } +#endif +#ifdef JSGC_FJGENERATIONAL + if (cx->isForkJoinContext()) { + return cx->asForkJoinContext()->nursery().reallocateElements(obj, oldHeader, + oldCount, newCount); + } +#endif + + return reinterpret_cast<js::ObjectElements *>( + obj->zone()->pod_realloc<HeapSlot>(reinterpret_cast<HeapSlot *>(oldHeader), + oldCount, newCount)); +} + +// Round up |reqAllocated| to a good size. Up to 1 Mebi (i.e. 1,048,576) the +// slot count is usually a power-of-two: +// +// 8, 16, 32, 64, ..., 256 Ki, 512 Ki, 1 Mi +// +// Beyond that, we use this formula: +// +// count(n+1) = Math.ceil(count(n) * 1.125) +// +// where |count(n)| is the size of the nth bucket measured in MiSlots. +// +// These counts lets us add N elements to an array in amortized O(N) time. +// Having the second class means that for bigger arrays the constant factor is +// higher, but we waste less space. +// +// There is one exception to the above rule: for the power-of-two cases, if the +// chosen capacity would be 2/3 or more of the array's length, the chosen +// capacity is adjusted (up or down) to be equal to the array's length +// (assuming length is at least as large as the required capacity). This avoids +// the allocation of excess elements which are unlikely to be needed, either in +// this resizing or a subsequent one. The 2/3 factor is chosen so that +// exceptional resizings will at most triple the capacity, as opposed to the +// usual doubling. +// +/* static */ uint32_t +NativeObject::goodAllocated(uint32_t reqAllocated, uint32_t length = 0) +{ + static const uint32_t Mebi = 1024 * 1024; + + // This table was generated with this JavaScript code and a small amount + // subsequent reformatting: + // + // for (let n = 1, i = 0; i < 57; i++) { + // print((n * 1024 * 1024) + ', '); + // n = Math.ceil(n * 1.125); + // } + // print('0'); + // + // The final element is a sentinel value. + static const uint32_t BigBuckets[] = { + 1048576, 2097152, 3145728, 4194304, 5242880, 6291456, 7340032, 8388608, + 9437184, 11534336, 13631488, 15728640, 17825792, 20971520, 24117248, + 27262976, 31457280, 35651584, 40894464, 46137344, 52428800, 59768832, + 68157440, 77594624, 88080384, 99614720, 112197632, 126877696, + 143654912, 162529280, 183500800, 206569472, 232783872, 262144000, + 295698432, 333447168, 375390208, 422576128, 476053504, 535822336, + 602931200, 678428672, 763363328, 858783744, 966787072, 1088421888, + 1224736768, 1377828864, 1550843904, 1744830464, 1962934272, 2208301056, + 2485125120, 2796552192, 3146776576, 3541041152, 3984588800, 0 + }; + + // This code relies very much on |goodAllocated| being a uint32_t. + uint32_t goodAllocated = reqAllocated; + if (goodAllocated < Mebi) { + goodAllocated = RoundUpPow2(goodAllocated); + + // Look for the abovementioned exception. + uint32_t goodCapacity = goodAllocated - ObjectElements::VALUES_PER_HEADER; + uint32_t reqCapacity = reqAllocated - ObjectElements::VALUES_PER_HEADER; + if (length >= reqCapacity && goodCapacity > (length / 3) * 2) + goodAllocated = length + ObjectElements::VALUES_PER_HEADER; + + if (goodAllocated < SLOT_CAPACITY_MIN) + goodAllocated = SLOT_CAPACITY_MIN; + + } else { + uint32_t i = 0; + while (true) { + uint32_t b = BigBuckets[i++]; + if (b >= goodAllocated) { + // Found the first bucket greater than or equal to + // |goodAllocated|. + goodAllocated = b; + break; + } else if (b == 0) { + // Hit the end; return the maximum possible goodAllocated. + goodAllocated = 0xffffffff; + break; + } + } + } + + return goodAllocated; +} + +bool +NativeObject::growElements(ThreadSafeContext *cx, uint32_t reqCapacity) +{ + MOZ_ASSERT(nonProxyIsExtensible()); + MOZ_ASSERT(canHaveNonEmptyElements()); + if (denseElementsAreCopyOnWrite()) + MOZ_CRASH(); + + uint32_t oldCapacity = getDenseCapacity(); + MOZ_ASSERT(oldCapacity < reqCapacity); + + using mozilla::CheckedInt; + + CheckedInt<uint32_t> checkedOldAllocated = + CheckedInt<uint32_t>(oldCapacity) + ObjectElements::VALUES_PER_HEADER; + CheckedInt<uint32_t> checkedReqAllocated = + CheckedInt<uint32_t>(reqCapacity) + ObjectElements::VALUES_PER_HEADER; + if (!checkedOldAllocated.isValid() || !checkedReqAllocated.isValid()) + return false; + + uint32_t reqAllocated = checkedReqAllocated.value(); + uint32_t oldAllocated = checkedOldAllocated.value(); + + uint32_t newAllocated; + if (is<ArrayObject>() && !as<ArrayObject>().lengthIsWritable()) { + MOZ_ASSERT(reqCapacity <= as<ArrayObject>().length()); + // Preserve the |capacity <= length| invariant for arrays with + // non-writable length. See also js::ArraySetLength which initially + // enforces this requirement. + newAllocated = reqAllocated; + } else { + newAllocated = goodAllocated(reqAllocated, getElementsHeader()->length); + } + + uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER; + MOZ_ASSERT(newCapacity > oldCapacity && newCapacity >= reqCapacity); + + // Don't let nelements get close to wrapping around uint32_t. + if (newCapacity >= NELEMENTS_LIMIT) + return false; + + uint32_t initlen = getDenseInitializedLength(); + + ObjectElements *newheader; + if (hasDynamicElements()) { + newheader = ReallocateElements(cx, this, getElementsHeader(), oldAllocated, newAllocated); + if (!newheader) + return false; // Leave elements at its old size. + } else { + newheader = AllocateElements(cx, this, newAllocated); + if (!newheader) + return false; // Leave elements at its old size. + js_memcpy(newheader, getElementsHeader(), + (ObjectElements::VALUES_PER_HEADER + initlen) * sizeof(Value)); + } + + newheader->capacity = newCapacity; + elements = newheader->elements(); + + Debug_SetSlotRangeToCrashOnTouch(elements + initlen, newCapacity - initlen); + + return true; +} + +void +NativeObject::shrinkElements(ThreadSafeContext *cx, uint32_t reqCapacity) +{ + MOZ_ASSERT(cx->isThreadLocal(this)); + MOZ_ASSERT(canHaveNonEmptyElements()); + if (denseElementsAreCopyOnWrite()) + MOZ_CRASH(); + + if (!hasDynamicElements()) + return; + + uint32_t oldCapacity = getDenseCapacity(); + MOZ_ASSERT(reqCapacity < oldCapacity); + + uint32_t oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER; + uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER; + uint32_t newAllocated = goodAllocated(reqAllocated); + if (newAllocated == oldAllocated) + return; // Leave elements at its old size. + + MOZ_ASSERT(newAllocated > ObjectElements::VALUES_PER_HEADER); + uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER; + + ObjectElements *newheader = ReallocateElements(cx, this, getElementsHeader(), + oldAllocated, newAllocated); + if (!newheader) { + cx->recoverFromOutOfMemory(); + return; // Leave elements at its old size. + } + + newheader->capacity = newCapacity; + elements = newheader->elements(); +} + +/* static */ bool +NativeObject::CopyElementsForWrite(ThreadSafeContext *cx, NativeObject *obj) +{ + MOZ_ASSERT(obj->denseElementsAreCopyOnWrite()); + + // The original owner of a COW elements array should never be modified. + MOZ_ASSERT(obj->getElementsHeader()->ownerObject() != obj); + + uint32_t initlen = obj->getDenseInitializedLength(); + uint32_t allocated = initlen + ObjectElements::VALUES_PER_HEADER; + uint32_t newAllocated = goodAllocated(allocated); + + uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER; + + if (newCapacity >= NELEMENTS_LIMIT) + return false; + + JSObject::writeBarrierPre(obj->getElementsHeader()->ownerObject()); + + ObjectElements *newheader = AllocateElements(cx, obj, newAllocated); + if (!newheader) + return false; + js_memcpy(newheader, obj->getElementsHeader(), + (ObjectElements::VALUES_PER_HEADER + initlen) * sizeof(Value)); + + newheader->capacity = newCapacity; + newheader->clearCopyOnWrite(); + obj->elements = newheader->elements(); + + Debug_SetSlotRangeToCrashOnTouch(obj->elements + initlen, newCapacity - initlen); + + return true; +} + +/* static */ bool +NativeObject::allocSlot(ThreadSafeContext *cx, HandleNativeObject obj, uint32_t *slotp) +{ + MOZ_ASSERT(cx->isThreadLocal(obj)); + + uint32_t slot = obj->slotSpan(); + MOZ_ASSERT(slot >= JSSLOT_FREE(obj->getClass())); + + /* + * If this object is in dictionary mode, try to pull a free slot from the + * shape table's slot-number freelist. + */ + if (obj->inDictionaryMode()) { + ShapeTable &table = obj->lastProperty()->table(); + uint32_t last = table.freelist; + if (last != SHAPE_INVALID_SLOT) { +#ifdef DEBUG + MOZ_ASSERT(last < slot); + uint32_t next = obj->getSlot(last).toPrivateUint32(); + MOZ_ASSERT_IF(next != SHAPE_INVALID_SLOT, next < slot); +#endif + + *slotp = last; + + const Value &vref = obj->getSlot(last); + table.freelist = vref.toPrivateUint32(); + obj->setSlot(last, UndefinedValue()); + return true; + } + } + + if (slot >= SHAPE_MAXIMUM_SLOT) { + js_ReportOutOfMemory(cx); + return false; + } + + *slotp = slot; + + if (obj->inDictionaryMode() && !setSlotSpan(cx, obj, slot + 1)) + return false; + + return true; +} + +void +NativeObject::freeSlot(uint32_t slot) +{ + MOZ_ASSERT(slot < slotSpan()); + + if (inDictionaryMode()) { + uint32_t &last = lastProperty()->table().freelist; + + /* Can't afford to check the whole freelist, but let's check the head. */ + MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan() && last != slot); + + /* + * Place all freed slots other than reserved slots (bug 595230) on the + * dictionary's free list. + */ + if (JSSLOT_FREE(getClass()) <= slot) { + MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan()); + setSlot(slot, PrivateUint32Value(last)); + last = slot; + return; + } + } + setSlot(slot, UndefinedValue()); +} + +Shape * +NativeObject::addDataProperty(ExclusiveContext *cx, jsid idArg, uint32_t slot, unsigned attrs) +{ + MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); + RootedNativeObject self(cx, this); + RootedId id(cx, idArg); + return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0); +} + +Shape * +NativeObject::addDataProperty(ExclusiveContext *cx, HandlePropertyName name, + uint32_t slot, unsigned attrs) +{ + MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); + RootedNativeObject self(cx, this); + RootedId id(cx, NameToId(name)); + return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0); +} + +/* + * Backward compatibility requires allowing addProperty hooks to mutate the + * nominal initial value of a slotful property, while GC safety wants that + * value to be stored before the call-out through the hook. Optimize to do + * both while saving cycles for classes that stub their addProperty hook. + */ +template <ExecutionMode mode> +static inline bool +CallAddPropertyHook(typename ExecutionModeTraits<mode>::ExclusiveContextType cxArg, + const Class *clasp, HandleNativeObject obj, HandleShape shape, + HandleValue nominal) +{ + if (clasp->addProperty != JS_PropertyStub) { + if (mode == ParallelExecution) + return false; + + ExclusiveContext *cx = cxArg->asExclusiveContext(); + if (!cx->shouldBeJSContext()) + return false; + + /* Make a local copy of value so addProperty can mutate its inout parameter. */ + RootedValue value(cx, nominal); + + Rooted<jsid> id(cx, shape->propid()); + if (!CallJSPropertyOp(cx->asJSContext(), clasp->addProperty, obj, id, &value)) { + obj->removeProperty(cx, shape->propid()); + return false; + } + if (value.get() != nominal) { + if (shape->hasSlot()) + obj->setSlotWithType(cx, shape, value); + } + } + return true; +} + +template <ExecutionMode mode> +static inline bool +CallAddPropertyHookDense(typename ExecutionModeTraits<mode>::ExclusiveContextType cxArg, + const Class *clasp, HandleNativeObject obj, uint32_t index, + HandleValue nominal) +{ + /* Inline addProperty for array objects. */ + if (obj->is<ArrayObject>()) { + ArrayObject *arr = &obj->as<ArrayObject>(); + uint32_t length = arr->length(); + if (index >= length) { + if (mode == ParallelExecution) { + /* We cannot deal with overflows in parallel. */ + if (length > INT32_MAX) + return false; + arr->setLengthInt32(index + 1); + } else { + arr->setLength(cxArg->asExclusiveContext(), index + 1); + } + } + return true; + } + + if (clasp->addProperty != JS_PropertyStub) { + if (mode == ParallelExecution) + return false; + + ExclusiveContext *cx = cxArg->asExclusiveContext(); + if (!cx->shouldBeJSContext()) + return false; + + if (!obj->maybeCopyElementsForWrite(cx)) + return false; + + /* Make a local copy of value so addProperty can mutate its inout parameter. */ + RootedValue value(cx, nominal); + + Rooted<jsid> id(cx, INT_TO_JSID(index)); + if (!CallJSPropertyOp(cx->asJSContext(), clasp->addProperty, obj, id, &value)) { + obj->setDenseElementHole(cx, index); + return false; + } + if (value.get() != nominal) + obj->setDenseElementWithType(cx, index, value); + } + + return true; +} + +template <ExecutionMode mode> +static bool +UpdateShapeTypeAndValue(typename ExecutionModeTraits<mode>::ExclusiveContextType cx, + NativeObject *obj, Shape *shape, const Value &value) +{ + jsid id = shape->propid(); + if (shape->hasSlot()) { + if (mode == ParallelExecution) { + if (!obj->setSlotIfHasType(shape, value, /* overwriting = */ false)) + return false; + } else { + obj->setSlotWithType(cx->asExclusiveContext(), shape, value, /* overwriting = */ false); + } + + // Per the acquired properties analysis, when the shape of a partially + // initialized object is changed to its fully initialized shape, its + // type can be updated as well. + if (types::TypeNewScript *newScript = obj->typeRaw()->newScript()) { + if (newScript->initializedShape() == shape) + obj->setType(newScript->initializedType()); + } + } + if (!shape->hasSlot() || !shape->hasDefaultGetter() || !shape->hasDefaultSetter()) { + if (mode == ParallelExecution) { + if (!types::IsTypePropertyIdMarkedNonData(obj, id)) + return false; + } else { + types::MarkTypePropertyNonData(cx->asExclusiveContext(), obj, id); + } + } + if (!shape->writable()) { + if (mode == ParallelExecution) { + if (!types::IsTypePropertyIdMarkedNonWritable(obj, id)) + return false; + } else { + types::MarkTypePropertyNonWritable(cx->asExclusiveContext(), obj, id); + } + } + return true; +} + +template <ExecutionMode mode> +static inline bool +DefinePropertyOrElement(typename ExecutionModeTraits<mode>::ExclusiveContextType cx, + HandleNativeObject obj, HandleId id, + PropertyOp getter, StrictPropertyOp setter, + unsigned attrs, HandleValue value, + bool callSetterAfterwards, bool setterIsStrict) +{ + /* Use dense storage for new indexed properties where possible. */ + if (JSID_IS_INT(id) && + getter == JS_PropertyStub && + setter == JS_StrictPropertyStub && + attrs == JSPROP_ENUMERATE && + (!obj->isIndexed() || !obj->containsPure(id)) && + !IsAnyTypedArray(obj)) + { + uint32_t index = JSID_TO_INT(id); + bool definesPast; + if (!WouldDefinePastNonwritableLength(cx, obj, index, setterIsStrict, &definesPast)) + return false; + if (definesPast) + return true; + + NativeObject::EnsureDenseResult result; + if (mode == ParallelExecution) { + if (obj->writeToIndexWouldMarkNotPacked(index)) + return false; + result = obj->ensureDenseElementsPreservePackedFlag(cx, index, 1); + } else { + result = obj->ensureDenseElements(cx->asExclusiveContext(), index, 1); + } + + if (result == NativeObject::ED_FAILED) + return false; + if (result == NativeObject::ED_OK) { + if (mode == ParallelExecution) { + if (!obj->setDenseElementIfHasType(index, value)) + return false; + } else { + obj->setDenseElementWithType(cx->asExclusiveContext(), index, value); + } + return CallAddPropertyHookDense<mode>(cx, obj->getClass(), obj, index, value); + } + } + + if (obj->is<ArrayObject>()) { + Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>()); + if (id == NameToId(cx->names().length)) { + if (mode == SequentialExecution && !cx->shouldBeJSContext()) + return false; + return ArraySetLength<mode>(ExecutionModeTraits<mode>::toContextType(cx), arr, id, + attrs, value, setterIsStrict); + } + + uint32_t index; + if (js_IdIsIndex(id, &index)) { + bool definesPast; + if (!WouldDefinePastNonwritableLength(cx, arr, index, setterIsStrict, &definesPast)) + return false; + if (definesPast) + return true; + } + } + + // Don't define new indexed properties on typed arrays. + if (IsAnyTypedArray(obj)) { + uint64_t index; + if (IsTypedArrayIndex(id, &index)) + return true; + } + + AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); + + RootedShape shape(cx, NativeObject::putProperty<mode>(cx, obj, id, getter, setter, + SHAPE_INVALID_SLOT, attrs, 0)); + if (!shape) + return false; + + if (!UpdateShapeTypeAndValue<mode>(cx, obj, shape, value)) + return false; + + /* + * Clear any existing dense index after adding a sparse indexed property, + * and investigate converting the object to dense indexes. + */ + if (JSID_IS_INT(id)) { + if (mode == ParallelExecution) + return false; + + if (!obj->maybeCopyElementsForWrite(cx)) + return false; + + ExclusiveContext *ncx = cx->asExclusiveContext(); + uint32_t index = JSID_TO_INT(id); + NativeObject::removeDenseElementForSparseIndex(ncx, obj, index); + NativeObject::EnsureDenseResult result = NativeObject::maybeDensifySparseElements(ncx, obj); + if (result == NativeObject::ED_FAILED) + return false; + if (result == NativeObject::ED_OK) { + MOZ_ASSERT(setter == JS_StrictPropertyStub); + return CallAddPropertyHookDense<mode>(cx, obj->getClass(), obj, index, value); + } + } + + if (!CallAddPropertyHook<mode>(cx, obj->getClass(), obj, shape, value)) + return false; + + if (callSetterAfterwards && setter != JS_StrictPropertyStub) { + if (!cx->shouldBeJSContext()) + return false; + RootedValue nvalue(cx, value); + return NativeSet<mode>(ExecutionModeTraits<mode>::toContextType(cx), + obj, obj, shape, setterIsStrict, &nvalue); + } + return true; +} + +static bool +NativeLookupOwnProperty(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, + MutableHandle<Shape*> shapep); + +static unsigned +ApplyOrDefaultAttributes(unsigned attrs, const Shape *shape = nullptr) +{ + bool enumerable = shape ? shape->enumerable() : false; + bool writable = shape ? shape->writable() : false; + bool configurable = shape ? shape->configurable() : false; + return ApplyAttributes(attrs, enumerable, writable, configurable); +} + +static bool +PurgeProtoChain(ExclusiveContext *cx, JSObject *objArg, HandleId id) +{ + /* Root locally so we can re-assign. */ + RootedObject obj(cx, objArg); + + RootedShape shape(cx); + while (obj) { + /* Lookups will not be cached through non-native protos. */ + if (!obj->isNative()) + break; + + shape = obj->as<NativeObject>().lookup(cx, id); + if (shape) + return obj->as<NativeObject>().shadowingShapeChange(cx, *shape); + + obj = obj->getProto(); + } + + return true; +} + +static bool +PurgeScopeChainHelper(ExclusiveContext *cx, HandleObject objArg, HandleId id) +{ + /* Re-root locally so we can re-assign. */ + RootedObject obj(cx, objArg); + + MOZ_ASSERT(obj->isNative()); + MOZ_ASSERT(obj->isDelegate()); + + /* Lookups on integer ids cannot be cached through prototypes. */ + if (JSID_IS_INT(id)) + return true; + + PurgeProtoChain(cx, obj->getProto(), id); + + /* + * We must purge the scope chain only for Call objects as they are the only + * kind of cacheable non-global object that can gain properties after outer + * properties with the same names have been cached or traced. Call objects + * may gain such properties via eval introducing new vars; see bug 490364. + */ + if (obj->is<CallObject>()) { + while ((obj = obj->enclosingScope()) != nullptr) { + if (!PurgeProtoChain(cx, obj, id)) + return false; + } + } + + return true; +} + +/* + * PurgeScopeChain does nothing if obj is not itself a prototype or parent + * scope, else it reshapes the scope and prototype chains it links. It calls + * PurgeScopeChainHelper, which asserts that obj is flagged as a delegate + * (i.e., obj has ever been on a prototype or parent chain). + */ +static inline bool +PurgeScopeChain(ExclusiveContext *cx, JS::HandleObject obj, JS::HandleId id) +{ + if (obj->isDelegate()) + return PurgeScopeChainHelper(cx, obj, id); + return true; +} + +bool +js::DefineNativeProperty(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, HandleValue value, + PropertyOp getter, StrictPropertyOp setter, unsigned attrs) +{ + MOZ_ASSERT(!(attrs & JSPROP_NATIVE_ACCESSORS)); + + AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); + + RootedShape shape(cx); + RootedValue updateValue(cx, value); + bool shouldDefine = true; + + /* + * If defining a getter or setter, we must check for its counterpart and + * update the attributes and property ops. A getter or setter is really + * only half of a property. + */ + if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) { + if (!NativeLookupOwnProperty(cx, obj, id, &shape)) + return false; + if (shape) { + /* + * If we are defining a getter whose setter was already defined, or + * vice versa, finish the job via obj->changeProperty. + */ + if (IsImplicitDenseOrTypedArrayElement(shape)) { + if (IsAnyTypedArray(obj)) { + /* Ignore getter/setter properties added to typed arrays. */ + return true; + } + if (!NativeObject::sparsifyDenseElement(cx, obj, JSID_TO_INT(id))) + return false; + shape = obj->lookup(cx, id); + } + if (shape->isAccessorDescriptor()) { + attrs = ApplyOrDefaultAttributes(attrs, shape); + shape = NativeObject::changeProperty<SequentialExecution>(cx, obj, shape, attrs, + JSPROP_GETTER | JSPROP_SETTER, + (attrs & JSPROP_GETTER) + ? getter + : shape->getter(), + (attrs & JSPROP_SETTER) + ? setter + : shape->setter()); + if (!shape) + return false; + shouldDefine = false; + } + } + } else if (!(attrs & JSPROP_IGNORE_VALUE)) { + /* + * We might still want to ignore redefining some of our attributes, if the + * request came through a proxy after Object.defineProperty(), but only if we're redefining + * a data property. + * FIXME: All this logic should be removed when Proxies use PropDesc, but we need to + * remove JSPropertyOp getters and setters first. + * FIXME: This is still wrong for various array types, and will set the wrong attributes + * by accident, but we can't use NativeLookupOwnProperty in this case, because of resolve + * loops. + */ + shape = obj->lookup(cx, id); + if (shape && shape->isDataDescriptor()) + attrs = ApplyOrDefaultAttributes(attrs, shape); + } else { + /* + * We have been asked merely to update some attributes by a caller of + * Object.defineProperty, laundered through the proxy system, and returning here. We can + * get away with just using JSObject::changeProperty here. + */ + if (!NativeLookupOwnProperty(cx, obj, id, &shape)) + return false; + + if (shape) { + // Don't forget about arrays. + if (IsImplicitDenseOrTypedArrayElement(shape)) { + if (obj->is<TypedArrayObject>()) { + /* + * Silently ignore attempts to change individial index attributes. + * FIXME: Uses the same broken behavior as for accessors. This should + * probably throw. + */ + return true; + } + if (!NativeObject::sparsifyDenseElement(cx, obj, JSID_TO_INT(id))) + return false; + shape = obj->lookup(cx, id); + } + + attrs = ApplyOrDefaultAttributes(attrs, shape); + + /* Keep everything from the shape that isn't the things we're changing */ + unsigned attrMask = ~(JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); + shape = NativeObject::changeProperty<SequentialExecution>(cx, obj, shape, attrs, attrMask, + shape->getter(), shape->setter()); + if (!shape) + return false; + if (shape->hasSlot()) + updateValue = obj->getSlot(shape->slot()); + shouldDefine = false; + } + } + + /* + * Purge the property cache of any properties named by id that are about + * to be shadowed in obj's scope chain. + */ + if (!PurgeScopeChain(cx, obj, id)) + return false; + + /* Use the object's class getter and setter by default. */ + const Class *clasp = obj->getClass(); + if (!getter && !(attrs & JSPROP_GETTER)) + getter = clasp->getProperty; + if (!setter && !(attrs & JSPROP_SETTER)) + setter = clasp->setProperty; + + if (shouldDefine) { + // Handle the default cases here. Anyone that wanted to set non-default attributes has + // cleared the IGNORE flags by now. Since we can never get here with JSPROP_IGNORE_VALUE + // relevant, just clear it. + attrs = ApplyOrDefaultAttributes(attrs) & ~JSPROP_IGNORE_VALUE; + return DefinePropertyOrElement<SequentialExecution>(cx, obj, id, getter, setter, + attrs, value, false, false); + } + + MOZ_ASSERT(shape); + + JS_ALWAYS_TRUE(UpdateShapeTypeAndValue<SequentialExecution>(cx, obj, shape, updateValue)); + + return CallAddPropertyHook<SequentialExecution>(cx, clasp, obj, shape, updateValue); +} + +static bool +NativeLookupOwnProperty(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, + MutableHandle<Shape*> shapep) +{ + RootedObject pobj(cx); + bool done; + + if (!LookupOwnPropertyInline<CanGC>(cx, obj, id, &pobj, shapep, &done)) + return false; + if (!done || pobj != obj) + shapep.set(nullptr); + return true; +} + +template <AllowGC allowGC> +bool +baseops::LookupProperty(ExclusiveContext *cx, + typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, + typename MaybeRooted<jsid, allowGC>::HandleType id, + typename MaybeRooted<JSObject*, allowGC>::MutableHandleType objp, + typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp) +{ + return LookupPropertyInline<allowGC>(cx, obj, id, objp, propp); +} + +template bool +baseops::LookupProperty<CanGC>(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, + MutableHandleObject objp, MutableHandleShape propp); + +template bool +baseops::LookupProperty<NoGC>(ExclusiveContext *cx, NativeObject *obj, jsid id, + FakeMutableHandle<JSObject*> objp, + FakeMutableHandle<Shape*> propp); + +bool +baseops::LookupElement(JSContext *cx, HandleNativeObject obj, uint32_t index, + MutableHandleObject objp, MutableHandleShape propp) +{ + RootedId id(cx); + if (!IndexToId(cx, index, &id)) + return false; + + return LookupPropertyInline<CanGC>(cx, obj, id, objp, propp); +} + +bool +js::LookupNativeProperty(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, + MutableHandleObject objp, MutableHandleShape propp) +{ + return LookupPropertyInline<CanGC>(cx, obj, id, objp, propp); +} + +bool +baseops::DefineGeneric(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, HandleValue value, + PropertyOp getter, StrictPropertyOp setter, unsigned attrs) +{ + return DefineNativeProperty(cx, obj, id, value, getter, setter, attrs); +} + +bool +baseops::DefineElement(ExclusiveContext *cx, HandleNativeObject obj, uint32_t index, HandleValue value, + PropertyOp getter, StrictPropertyOp setter, unsigned attrs) +{ + RootedId id(cx); + if (index <= JSID_INT_MAX) { + id = INT_TO_JSID(index); + return DefineNativeProperty(cx, obj, id, value, getter, setter, attrs); + } + + AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); + + if (!IndexToId(cx, index, &id)) + return false; + + return DefineNativeProperty(cx, obj, id, value, getter, setter, attrs); +} + +template <AllowGC allowGC> +static MOZ_ALWAYS_INLINE bool +NativeGetInline(JSContext *cx, + typename MaybeRooted<JSObject*, allowGC>::HandleType obj, + typename MaybeRooted<JSObject*, allowGC>::HandleType receiver, + typename MaybeRooted<NativeObject*, allowGC>::HandleType pobj, + typename MaybeRooted<Shape*, allowGC>::HandleType shape, + typename MaybeRooted<Value, allowGC>::MutableHandleType vp) +{ + if (shape->hasSlot()) { + vp.set(pobj->getSlot(shape->slot())); + MOZ_ASSERT_IF(!vp.isMagic(JS_UNINITIALIZED_LEXICAL) && + !pobj->hasSingletonType() && + !pobj->template is<ScopeObject>() && + shape->hasDefaultGetter(), + js::types::TypeHasProperty(cx, pobj->type(), shape->propid(), vp)); + } else { + vp.setUndefined(); + } + if (shape->hasDefaultGetter()) + return true; + + { + jsbytecode *pc; + JSScript *script = cx->currentScript(&pc); + if (script && script->hasBaselineScript()) { + switch (JSOp(*pc)) { + case JSOP_GETPROP: + case JSOP_CALLPROP: + case JSOP_LENGTH: + script->baselineScript()->noteAccessedGetter(script->pcToOffset(pc)); + break; + default: + break; + } + } + } + + if (!allowGC) + return false; + + if (!shape->get(cx, + MaybeRooted<JSObject*, allowGC>::toHandle(receiver), + MaybeRooted<JSObject*, allowGC>::toHandle(obj), + MaybeRooted<JSObject*, allowGC>::toHandle(pobj), + MaybeRooted<Value, allowGC>::toMutableHandle(vp))) + { + return false; + } + + /* Update slotful shapes according to the value produced by the getter. */ + if (shape->hasSlot() && pobj->contains(cx, shape)) + pobj->setSlot(shape->slot(), vp); + + return true; +} + +bool +js::NativeGet(JSContext *cx, HandleObject obj, HandleNativeObject pobj, HandleShape shape, + MutableHandleValue vp) +{ + return NativeGetInline<CanGC>(cx, obj, obj, pobj, shape, vp); +} + +template <ExecutionMode mode> +bool +js::NativeSet(typename ExecutionModeTraits<mode>::ContextType cxArg, + HandleNativeObject obj, Handle<JSObject*> receiver, + HandleShape shape, bool strict, MutableHandleValue vp) +{ + MOZ_ASSERT(cxArg->isThreadLocal(obj)); + MOZ_ASSERT(obj->isNative()); + + if (shape->hasSlot()) { + /* If shape has a stub setter, just store vp. */ + if (shape->hasDefaultSetter()) { + if (mode == ParallelExecution) { + if (!obj->setSlotIfHasType(shape, vp)) + return false; + } else { + // Global properties declared with 'var' will be initially + // defined with an undefined value, so don't treat the initial + // assignments to such properties as overwrites. + bool overwriting = !obj->is<GlobalObject>() || !obj->getSlot(shape->slot()).isUndefined(); + obj->setSlotWithType(cxArg->asExclusiveContext(), shape, vp, overwriting); + } + + return true; + } + } + + if (mode == ParallelExecution) + return false; + JSContext *cx = cxArg->asJSContext(); + + if (!shape->hasSlot()) { + /* + * Allow API consumers to create shared properties with stub setters. + * Such properties effectively function as data descriptors which are + * not writable, so attempting to set such a property should do nothing + * or throw if we're in strict mode. + */ + if (!shape->hasGetterValue() && shape->hasDefaultSetter()) + return js_ReportGetterOnlyAssignment(cx, strict); + } + + RootedValue ovp(cx, vp); + + uint32_t sample = cx->runtime()->propertyRemovals; + if (!shape->set(cx, obj, receiver, strict, vp)) + return false; + + /* + * Update any slot for the shape with the value produced by the setter, + * unless the setter deleted the shape. + */ + if (shape->hasSlot() && + (MOZ_LIKELY(cx->runtime()->propertyRemovals == sample) || + obj->contains(cx, shape))) + { + obj->setSlot(shape->slot(), vp); + } + + return true; +} + +template bool +js::NativeSet<SequentialExecution>(JSContext *cx, + HandleNativeObject obj, HandleObject receiver, + HandleShape shape, bool strict, MutableHandleValue vp); +template bool +js::NativeSet<ParallelExecution>(ForkJoinContext *cx, + HandleNativeObject obj, HandleObject receiver, + HandleShape shape, bool strict, MutableHandleValue vp); + +/* + * Given pc pointing after a property accessing bytecode, return true if the + * access is "object-detecting" in the sense used by web scripts, e.g., when + * checking whether document.all is defined. + */ +static bool +Detecting(JSContext *cx, JSScript *script, jsbytecode *pc) +{ + MOZ_ASSERT(script->containsPC(pc)); + + /* General case: a branch or equality op follows the access. */ + JSOp op = JSOp(*pc); + if (js_CodeSpec[op].format & JOF_DETECTING) + return true; + + jsbytecode *endpc = script->codeEnd(); + + if (op == JSOP_NULL) { + /* + * Special case #1: handle (document.all == null). Don't sweat + * about JS1.2's revision of the equality operators here. + */ + if (++pc < endpc) { + op = JSOp(*pc); + return op == JSOP_EQ || op == JSOP_NE; + } + return false; + } + + if (op == JSOP_GETGNAME || op == JSOP_NAME) { + /* + * Special case #2: handle (document.all == undefined). Don't worry + * about a local variable named |undefined| shadowing the immutable + * global binding...because, really? + */ + JSAtom *atom = script->getAtom(GET_UINT32_INDEX(pc)); + if (atom == cx->names().undefined && + (pc += js_CodeSpec[op].length) < endpc) { + op = JSOp(*pc); + return op == JSOP_EQ || op == JSOP_NE || op == JSOP_STRICTEQ || op == JSOP_STRICTNE; + } + } + + return false; +} + +template <AllowGC allowGC> +static MOZ_ALWAYS_INLINE bool +GetPropertyHelperInline(JSContext *cx, + typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, + typename MaybeRooted<JSObject*, allowGC>::HandleType receiver, + typename MaybeRooted<jsid, allowGC>::HandleType id, + typename MaybeRooted<Value, allowGC>::MutableHandleType vp) +{ + /* This call site is hot -- use the always-inlined variant of LookupNativeProperty(). */ + typename MaybeRooted<JSObject*, allowGC>::RootType obj2(cx); + typename MaybeRooted<Shape*, allowGC>::RootType shape(cx); + if (!LookupPropertyInline<allowGC>(cx, obj, id, &obj2, &shape)) + return false; + + if (!shape) { + if (!allowGC) + return false; + + vp.setUndefined(); + + if (!CallJSPropertyOp(cx, obj->getClass()->getProperty, + MaybeRooted<JSObject*, allowGC>::toHandle(obj), + MaybeRooted<jsid, allowGC>::toHandle(id), + MaybeRooted<Value, allowGC>::toMutableHandle(vp))) + { + return false; + } + + /* + * Give a strict warning if foo.bar is evaluated by a script for an + * object foo with no property named 'bar'. + */ + if (vp.isUndefined()) { + jsbytecode *pc = nullptr; + RootedScript script(cx, cx->currentScript(&pc)); + if (!pc) + return true; + JSOp op = (JSOp) *pc; + + if (op == JSOP_GETXPROP) { + /* Undefined property during a name lookup, report an error. */ + JSAutoByteString printable; + if (js_ValueToPrintable(cx, IdToValue(id), &printable)) + js_ReportIsNotDefined(cx, printable.ptr()); + return false; + } + + /* Don't warn if extra warnings not enabled or for random getprop operations. */ + if (!cx->compartment()->options().extraWarnings(cx) || (op != JSOP_GETPROP && op != JSOP_GETELEM)) + return true; + + /* Don't warn repeatedly for the same script. */ + if (!script || script->warnedAboutUndefinedProp()) + return true; + + /* + * Don't warn in self-hosted code (where the further presence of + * JS::RuntimeOptions::werror() would result in impossible-to-avoid + * errors to entirely-innocent client code). + */ + if (script->selfHosted()) + return true; + + /* We may just be checking if that object has an iterator. */ + if (JSID_IS_ATOM(id, cx->names().iteratorIntrinsic)) + return true; + + /* Do not warn about tests like (obj[prop] == undefined). */ + pc += js_CodeSpec[op].length; + if (Detecting(cx, script, pc)) + return true; + + unsigned flags = JSREPORT_WARNING | JSREPORT_STRICT; + script->setWarnedAboutUndefinedProp(); + + /* Ok, bad undefined property reference: whine about it. */ + RootedValue val(cx, IdToValue(id)); + if (!js_ReportValueErrorFlags(cx, flags, JSMSG_UNDEFINED_PROP, + JSDVG_IGNORE_STACK, val, js::NullPtr(), + nullptr, nullptr)) + { + return false; + } + } + return true; + } + + if (!obj2->isNative()) { + if (!allowGC) + return false; + HandleObject obj2Handle = MaybeRooted<JSObject*, allowGC>::toHandle(obj2); + HandleObject receiverHandle = MaybeRooted<JSObject*, allowGC>::toHandle(receiver); + HandleId idHandle = MaybeRooted<jsid, allowGC>::toHandle(id); + MutableHandleValue vpHandle = MaybeRooted<Value, allowGC>::toMutableHandle(vp); + return obj2->template is<ProxyObject>() + ? Proxy::get(cx, obj2Handle, receiverHandle, idHandle, vpHandle) + : JSObject::getGeneric(cx, obj2Handle, obj2Handle, idHandle, vpHandle); + } + + typename MaybeRooted<NativeObject*, allowGC>::HandleType nobj2 = + MaybeRooted<JSObject*, allowGC>::template downcastHandle<NativeObject>(obj2); + + if (IsImplicitDenseOrTypedArrayElement(shape)) { + vp.set(nobj2->getDenseOrTypedArrayElement(JSID_TO_INT(id))); + return true; + } + + /* This call site is hot -- use the always-inlined variant of NativeGet(). */ + if (!NativeGetInline<allowGC>(cx, obj, receiver, nobj2, shape, vp)) + return false; + + return true; +} + +bool +baseops::GetProperty(JSContext *cx, HandleNativeObject obj, HandleObject receiver, HandleId id, MutableHandleValue vp) +{ + /* This call site is hot -- use the always-inlined variant of GetPropertyHelper(). */ + return GetPropertyHelperInline<CanGC>(cx, obj, receiver, id, vp); +} + +bool +baseops::GetPropertyNoGC(JSContext *cx, NativeObject *obj, JSObject *receiver, jsid id, Value *vp) +{ + AutoAssertNoException nogc(cx); + return GetPropertyHelperInline<NoGC>(cx, obj, receiver, id, vp); +} + +bool +baseops::GetElement(JSContext *cx, HandleNativeObject obj, HandleObject receiver, uint32_t index, + MutableHandleValue vp) +{ + RootedId id(cx); + if (!IndexToId(cx, index, &id)) + return false; + + /* This call site is hot -- use the always-inlined variant of js_GetPropertyHelper(). */ + return GetPropertyHelperInline<CanGC>(cx, obj, receiver, id, vp); +} + +static bool +MaybeReportUndeclaredVarAssignment(JSContext *cx, JSString *propname) +{ + { + JSScript *script = cx->currentScript(nullptr, JSContext::ALLOW_CROSS_COMPARTMENT); + if (!script) + return true; + + // If the code is not strict and extra warnings aren't enabled, then no + // check is needed. + if (!script->strict() && !cx->compartment()->options().extraWarnings(cx)) + return true; + } + + JSAutoByteString bytes(cx, propname); + return !!bytes && + JS_ReportErrorFlagsAndNumber(cx, + (JSREPORT_WARNING | JSREPORT_STRICT + | JSREPORT_STRICT_MODE_ERROR), + js_GetErrorMessage, nullptr, + JSMSG_UNDECLARED_VAR, bytes.ptr()); +} + +template <ExecutionMode mode> +bool +baseops::SetPropertyHelper(typename ExecutionModeTraits<mode>::ContextType cxArg, + HandleNativeObject obj, HandleObject receiver, HandleId id, + QualifiedBool qualified, MutableHandleValue vp, bool strict) +{ + MOZ_ASSERT(cxArg->isThreadLocal(obj)); + + if (MOZ_UNLIKELY(obj->watched())) { + if (mode == ParallelExecution) + return false; + + /* Fire watchpoints, if any. */ + JSContext *cx = cxArg->asJSContext(); + WatchpointMap *wpmap = cx->compartment()->watchpointMap; + if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp)) + return false; + } + + RootedObject pobj(cxArg); + RootedShape shape(cxArg); + if (mode == ParallelExecution) { + NativeObject *npobj; + if (!LookupPropertyPure(obj, id, &npobj, shape.address())) + return false; + pobj = npobj; + } else { + JSContext *cx = cxArg->asJSContext(); + if (!LookupNativeProperty(cx, obj, id, &pobj, &shape)) + return false; + } + if (shape) { + if (!pobj->isNative()) { + if (pobj->is<ProxyObject>()) { + if (mode == ParallelExecution) + return false; + + JSContext *cx = cxArg->asJSContext(); + Rooted<PropertyDescriptor> pd(cx); + if (!Proxy::getPropertyDescriptor(cx, pobj, id, &pd)) + return false; + + if ((pd.attributes() & (JSPROP_SHARED | JSPROP_SHADOWABLE)) == JSPROP_SHARED) { + return !pd.setter() || + CallSetter(cx, receiver, id, pd.setter(), pd.attributes(), strict, vp); + } + + if (pd.isReadonly()) { + if (strict) + return JSObject::reportReadOnly(cx, id, JSREPORT_ERROR); + if (cx->compartment()->options().extraWarnings(cx)) + return JSObject::reportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING); + return true; + } + } + + shape = nullptr; + } + } else { + /* We should never add properties to lexical blocks. */ + MOZ_ASSERT(!obj->is<BlockObject>()); + + if (obj->isUnqualifiedVarObj() && !qualified) { + if (mode == ParallelExecution) + return false; + + if (!MaybeReportUndeclaredVarAssignment(cxArg->asJSContext(), JSID_TO_STRING(id))) + return false; + } + } + + /* + * Now either shape is null, meaning id was not found in obj or one of its + * prototypes; or shape is non-null, meaning id was found directly in pobj. + */ + unsigned attrs = JSPROP_ENUMERATE; + const Class *clasp = obj->getClass(); + PropertyOp getter = clasp->getProperty; + StrictPropertyOp setter = clasp->setProperty; + + if (IsImplicitDenseOrTypedArrayElement(shape)) { + /* ES5 8.12.4 [[Put]] step 2, for a dense data property on pobj. */ + if (pobj != obj) + shape = nullptr; + } else if (shape) { + /* ES5 8.12.4 [[Put]] step 2. */ + if (shape->isAccessorDescriptor()) { + if (shape->hasDefaultSetter()) { + /* Bail out of parallel execution if we are strict to throw. */ + if (mode == ParallelExecution) + return !strict; + + return js_ReportGetterOnlyAssignment(cxArg->asJSContext(), strict); + } + } else { + MOZ_ASSERT(shape->isDataDescriptor()); + + if (!shape->writable()) { + /* + * Error in strict mode code, warn with extra warnings + * options, otherwise do nothing. + * + * Bail out of parallel execution if we are strict to throw. + */ + if (mode == ParallelExecution) + return !strict; + + JSContext *cx = cxArg->asJSContext(); + if (strict) + return JSObject::reportReadOnly(cx, id, JSREPORT_ERROR); + if (cx->compartment()->options().extraWarnings(cx)) + return JSObject::reportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING); + return true; + } + } + + attrs = shape->attributes(); + if (pobj != obj) { + /* + * We found id in a prototype object: prepare to share or shadow. + */ + if (!shape->shadowable()) { + if (shape->hasDefaultSetter() && !shape->hasGetterValue()) + return true; + + if (mode == ParallelExecution) + return false; + + return shape->set(cxArg->asJSContext(), obj, receiver, strict, vp); + } + + /* + * Preserve attrs except JSPROP_SHARED, getter, and setter when + * shadowing any property that has no slot (is shared). We must + * clear the shared attribute for the shadowing shape so that the + * property in obj that it defines has a slot to retain the value + * being set, in case the setter simply cannot operate on instances + * of obj's class by storing the value in some class-specific + * location. + */ + if (!shape->hasSlot()) { + attrs &= ~JSPROP_SHARED; + getter = shape->getter(); + setter = shape->setter(); + } else { + /* Restore attrs to the ECMA default for new properties. */ + attrs = JSPROP_ENUMERATE; + } + + /* + * Forget we found the proto-property now that we've copied any + * needed member values. + */ + shape = nullptr; + } + } + + if (IsImplicitDenseOrTypedArrayElement(shape)) { + uint32_t index = JSID_TO_INT(id); + + if (IsAnyTypedArray(obj)) { + double d; + if (mode == ParallelExecution) { + // Bail if converting the value might invoke user-defined + // conversions. + if (vp.isObject()) + return false; + if (!NonObjectToNumber(cxArg, vp, &d)) + return false; + } else { + if (!ToNumber(cxArg->asJSContext(), vp, &d)) + return false; + } + + // Silently do nothing for out-of-bounds sets, for consistency with + // current behavior. (ES6 currently says to throw for this in + // strict mode code, so we may eventually need to change.) + uint32_t len = AnyTypedArrayLength(obj); + if (index < len) { + if (obj->is<TypedArrayObject>()) + TypedArrayObject::setElement(obj->as<TypedArrayObject>(), index, d); + else + SharedTypedArrayObject::setElement(obj->as<SharedTypedArrayObject>(), index, d); + } + return true; + } + + bool definesPast; + if (!WouldDefinePastNonwritableLength(cxArg, obj, index, strict, &definesPast)) + return false; + if (definesPast) { + /* Bail out of parallel execution if we are strict to throw. */ + if (mode == ParallelExecution) + return !strict; + return true; + } + + if (!obj->maybeCopyElementsForWrite(cxArg)) + return false; + + if (mode == ParallelExecution) + return obj->setDenseElementIfHasType(index, vp); + + obj->setDenseElementWithType(cxArg->asJSContext(), index, vp); + return true; + } + + if (obj->is<ArrayObject>() && id == NameToId(cxArg->names().length)) { + Rooted<ArrayObject*> arr(cxArg, &obj->as<ArrayObject>()); + return ArraySetLength<mode>(cxArg, arr, id, attrs, vp, strict); + } + + if (!shape) { + bool extensible; + if (mode == ParallelExecution) { + if (obj->is<ProxyObject>()) + return false; + extensible = obj->nonProxyIsExtensible(); + } else { + if (!JSObject::isExtensible(cxArg->asJSContext(), obj, &extensible)) + return false; + } + + if (!extensible) { + /* Error in strict mode code, warn with extra warnings option, otherwise do nothing. */ + if (strict) + return obj->reportNotExtensible(cxArg); + if (mode == SequentialExecution && + cxArg->asJSContext()->compartment()->options().extraWarnings(cxArg->asJSContext())) + { + return obj->reportNotExtensible(cxArg, JSREPORT_STRICT | JSREPORT_WARNING); + } + return true; + } + + if (mode == ParallelExecution) { + if (obj->isDelegate()) + return false; + + if (getter != JS_PropertyStub || !types::HasTypePropertyId(obj, id, vp)) + return false; + } else { + JSContext *cx = cxArg->asJSContext(); + + /* Purge the property cache of now-shadowed id in obj's scope chain. */ + if (!PurgeScopeChain(cx, obj, id)) + return false; + } + + return DefinePropertyOrElement<mode>(cxArg, obj, id, getter, setter, + attrs, vp, true, strict); + } + + return NativeSet<mode>(cxArg, obj, receiver, shape, strict, vp); +} + +template bool +baseops::SetPropertyHelper<SequentialExecution>(JSContext *cx, HandleNativeObject obj, + HandleObject receiver, HandleId id, + QualifiedBool qualified, + MutableHandleValue vp, bool strict); +template bool +baseops::SetPropertyHelper<ParallelExecution>(ForkJoinContext *cx, HandleNativeObject obj, + HandleObject receiver, HandleId id, + QualifiedBool qualified, + MutableHandleValue vp, bool strict); + +bool +baseops::SetElementHelper(JSContext *cx, HandleNativeObject obj, HandleObject receiver, uint32_t index, + MutableHandleValue vp, bool strict) +{ + RootedId id(cx); + if (!IndexToId(cx, index, &id)) + return false; + return baseops::SetPropertyHelper<SequentialExecution>(cx, obj, receiver, id, Qualified, vp, + strict); +} + +bool +baseops::GetAttributes(JSContext *cx, HandleNativeObject obj, HandleId id, unsigned *attrsp) +{ + RootedObject nobj(cx); + RootedShape shape(cx); + if (!baseops::LookupProperty<CanGC>(cx, obj, id, &nobj, &shape)) + return false; + if (!shape) { + *attrsp = 0; + return true; + } + if (!nobj->isNative()) + return JSObject::getGenericAttributes(cx, nobj, id, attrsp); + + *attrsp = GetShapeAttributes(nobj, shape); + return true; +} + +bool +baseops::SetAttributes(JSContext *cx, HandleNativeObject obj, HandleId id, unsigned *attrsp) +{ + RootedObject nobj(cx); + RootedShape shape(cx); + if (!baseops::LookupProperty<CanGC>(cx, obj, id, &nobj, &shape)) + return false; + if (!shape) + return true; + if (nobj->isNative() && IsImplicitDenseOrTypedArrayElement(shape)) { + if (IsAnyTypedArray(nobj.get())) { + if (*attrsp == (JSPROP_ENUMERATE | JSPROP_PERMANENT)) + return true; + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_SET_ARRAY_ATTRS); + return false; + } + if (!NativeObject::sparsifyDenseElement(cx, nobj.as<NativeObject>(), JSID_TO_INT(id))) + return false; + shape = nobj->as<NativeObject>().lookup(cx, id); + } + if (nobj->isNative()) { + if (!NativeObject::changePropertyAttributes(cx, nobj.as<NativeObject>(), shape, *attrsp)) + return false; + if (*attrsp & JSPROP_READONLY) + types::MarkTypePropertyNonWritable(cx, nobj, id); + return true; + } else { + return JSObject::setGenericAttributes(cx, nobj, id, attrsp); + } +} + +bool +baseops::DeleteGeneric(JSContext *cx, HandleNativeObject obj, HandleId id, bool *succeeded) +{ + RootedObject proto(cx); + RootedShape shape(cx); + if (!baseops::LookupProperty<CanGC>(cx, obj, id, &proto, &shape)) + return false; + if (!shape || proto != obj) { + /* + * If no property, or the property comes from a prototype, call the + * class's delProperty hook, passing succeeded as the result parameter. + */ + return CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, id, succeeded); + } + + cx->runtime()->gc.poke(); + + if (IsImplicitDenseOrTypedArrayElement(shape)) { + if (IsAnyTypedArray(obj)) { + // Don't delete elements from typed arrays. + *succeeded = false; + return true; + } + + if (!CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, id, succeeded)) + return false; + if (!succeeded) + return true; + + NativeObject *nobj = &obj->as<NativeObject>(); + if (!nobj->maybeCopyElementsForWrite(cx)) + return false; + + nobj->setDenseElementHole(cx, JSID_TO_INT(id)); + return SuppressDeletedProperty(cx, obj, id); + } + + if (!shape->configurable()) { + *succeeded = false; + return true; + } + + RootedId propid(cx, shape->propid()); + if (!CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, propid, succeeded)) + return false; + if (!succeeded) + return true; + + return obj->removeProperty(cx, id) && SuppressDeletedProperty(cx, obj, id); +}
--- a/js/src/vm/ObjectImpl.h +++ b/js/src/vm/ObjectImpl.h @@ -1314,23 +1314,51 @@ extern bool GetAttributes(JSContext *cx, HandleNativeObject obj, HandleId id, unsigned *attrsp); extern bool SetAttributes(JSContext *cx, HandleNativeObject obj, HandleId id, unsigned *attrsp); extern bool DeleteGeneric(JSContext *cx, HandleNativeObject obj, HandleId id, bool *succeeded); +} /* namespace js::baseops */ + +/* + * Return successfully added or changed shape or nullptr on error. + */ extern bool -Watch(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable); +DefineNativeProperty(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, HandleValue value, + PropertyOp getter, StrictPropertyOp setter, unsigned attrs); extern bool -Unwatch(JSContext *cx, JS::HandleObject obj, JS::HandleId id); +LookupNativeProperty(ExclusiveContext *cx, HandleNativeObject obj, HandleId id, + js::MutableHandleObject objp, js::MutableHandleShape propp); + +bool +NativeGet(JSContext *cx, HandleObject obj, HandleNativeObject pobj, + HandleShape shape, MutableHandle<Value> vp); + +template <ExecutionMode mode> +bool +NativeSet(typename ExecutionModeTraits<mode>::ContextType cx, + HandleNativeObject obj, HandleObject receiver, + HandleShape shape, bool strict, MutableHandleValue vp); -} /* namespace js::baseops */ +/* + * If obj has an already-resolved data property for id, return true and + * store the property value in *vp. + */ +extern bool +HasDataProperty(JSContext *cx, NativeObject *obj, jsid id, Value *vp); + +inline bool +HasDataProperty(JSContext *cx, NativeObject *obj, PropertyName *name, Value *vp) +{ + return HasDataProperty(cx, obj, NameToId(name), vp); +} } /* namespace js */ inline void * JSObject::fakeNativeGetPrivate() const { return static_cast<const js::NativeObject*>(this)->getPrivate(); }
--- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -766,35 +766,36 @@ RegExpCompartment::sweep(JSRuntime *rt) // compartment being subsequently cleared. This can happen if a GC is // restarted while in progress (i.e. performing a full GC in the // middle of an incremental GC) or if a RegExpShared referenced via the // stack is traced but is not in a zone being collected. // // Because of this we only treat the marked_ bit as a hint, and destroy // the RegExpShared if it was accidentally marked earlier but wasn't // marked by the current trace. - bool keep = shared->marked() && !IsStringAboutToBeFinalized(shared->source.unsafeGet()); + bool keep = shared->marked() && + !IsStringAboutToBeFinalizedFromAnyThread(shared->source.unsafeGet()); for (size_t i = 0; i < ArrayLength(shared->compilationArray); i++) { RegExpShared::RegExpCompilation &compilation = shared->compilationArray[i]; if (compilation.jitCode && - IsJitCodeAboutToBeFinalized(compilation.jitCode.unsafeGet())) + IsJitCodeAboutToBeFinalizedFromAnyThread(compilation.jitCode.unsafeGet())) { keep = false; } } if (keep || rt->isHeapCompacting()) { shared->clearMarked(); } else { js_delete(shared); e.removeFront(); } } if (matchResultTemplateObject_ && - IsObjectAboutToBeFinalized(matchResultTemplateObject_.unsafeGet())) + IsObjectAboutToBeFinalizedFromAnyThread(matchResultTemplateObject_.unsafeGet())) { matchResultTemplateObject_.set(nullptr); } } bool RegExpCompartment::get(JSContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g) {
--- a/js/src/vm/SavedStacks.cpp +++ b/js/src/vm/SavedStacks.cpp @@ -426,20 +426,20 @@ SavedStacks::saveCurrentStack(JSContext return insertFrames(cx, iter, frame, maxFrameCount); } void SavedStacks::sweep(JSRuntime *rt) { if (frames.initialized()) { for (SavedFrame::Set::Enum e(frames); !e.empty(); e.popFront()) { - JSObject *obj = static_cast<JSObject *>(e.front()); + JSObject *obj = e.front().unbarrieredGet(); JSObject *temp = obj; - if (IsObjectAboutToBeFinalized(&obj)) { + if (IsObjectAboutToBeFinalizedFromAnyThread(&obj)) { e.removeFront(); } else { SavedFrame *frame = &obj->as<SavedFrame>(); bool parentMoved = frame->parentMoved(); if (parentMoved) { frame->updatePrivateParent(); } @@ -454,17 +454,19 @@ SavedStacks::sweep(JSRuntime *rt) ReadBarriered<SavedFrame *>(frame)); } } } } sweepPCLocationMap(); - if (savedFrameProto && IsObjectAboutToBeFinalized(savedFrameProto.unsafeGet())) { + if (savedFrameProto.unbarrieredGet() && + IsObjectAboutToBeFinalizedFromAnyThread(savedFrameProto.unsafeGet())) + { savedFrameProto.set(nullptr); } } void SavedStacks::trace(JSTracer *trc) { if (!pcLocationMap.initialized()) @@ -645,17 +647,17 @@ SavedStacks::createFrameFromLookup(JSCon * Remove entries from the table whose JSScript is being collected. */ void SavedStacks::sweepPCLocationMap() { for (PCLocationMap::Enum e(pcLocationMap); !e.empty(); e.popFront()) { PCKey key = e.front().key(); JSScript *script = key.script.get(); - if (IsScriptAboutToBeFinalized(&script)) { + if (IsScriptAboutToBeFinalizedFromAnyThread(&script)) {