mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
Merge mozilla-central to autoland. a=merge on a CLOSED TREE
This commit is contained in:
commit
9cabbb0b47
@ -17,7 +17,7 @@ support-files =
|
||||
[browser_caching_attributes.js]
|
||||
[browser_caching_description.js]
|
||||
[browser_caching_name.js]
|
||||
skip-if = e10s && os == 'win' && debug # Bug 1338034, leaks
|
||||
skip-if = (e10s && os == 'win') || (os == 'mac' && debug) # Bug 1338034, leaks # Bug 1503084
|
||||
[browser_caching_relations.js]
|
||||
[browser_caching_states.js]
|
||||
[browser_caching_value.js]
|
||||
|
@ -215,7 +215,6 @@ var CaptivePortalWatcher = {
|
||||
// Returning true prevents the notification from closing.
|
||||
return true;
|
||||
},
|
||||
isDefault: true,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -482,8 +482,7 @@ var gPluginHandler = {
|
||||
let crashurl = formatURL("app.support.baseURL", true);
|
||||
crashurl += "plugin-crashed-notificationbar";
|
||||
link.href = crashurl;
|
||||
let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
|
||||
description.appendChild(link);
|
||||
notification.messageText.appendChild(link);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1054,11 +1054,6 @@ browser[tabmodalPromptShowing] {
|
||||
-moz-box-align: end;
|
||||
}
|
||||
|
||||
/* Translation */
|
||||
notification[value="translation"] {
|
||||
-moz-binding: url("chrome://browser/content/translation-infobar.xml#translationbar");
|
||||
}
|
||||
|
||||
/*** Visibility of downloads indicator controls ***/
|
||||
|
||||
/* Bug 924050: If we've loaded the indicator, for now we hide it in the menu panel,
|
||||
|
@ -242,6 +242,11 @@ XPCOMUtils.defineLazyGetter(this, "Win7Features", function() {
|
||||
return null;
|
||||
});
|
||||
|
||||
customElements.setElementCreationCallback("translation-notification", () => {
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://browser/content/translation-notification.js", window);
|
||||
});
|
||||
|
||||
var gBrowser;
|
||||
var gLastValidURLStr = "";
|
||||
var gInPrintPreviewMode = false;
|
||||
|
@ -47,10 +47,10 @@ async function test_decoder_doctor_notification(data, notificationMessage,
|
||||
}
|
||||
ok(notification, "Got decoder-doctor-notification notification");
|
||||
|
||||
is(notification.getAttribute("label"), notificationMessage,
|
||||
is(notification.messageText.textContent, notificationMessage,
|
||||
"notification message should match expectation");
|
||||
|
||||
let button = notification.children[0];
|
||||
let button = notification.querySelector("button");
|
||||
if (!label) {
|
||||
ok(!button, "There should not be button");
|
||||
return;
|
||||
|
@ -100,7 +100,7 @@ async function testRealRefresh(refreshPage, delay) {
|
||||
let notification = notificationBox.currentNotification;
|
||||
|
||||
ok(notification, "Notification should be visible");
|
||||
is(notification.value, "refresh-blocked",
|
||||
is(notification.getAttribute("value"), "refresh-blocked",
|
||||
"Should be showing the right notification");
|
||||
|
||||
// Then click the button to allow the refresh.
|
||||
|
@ -45,7 +45,7 @@ add_task(async function() {
|
||||
let notification = box.currentNotification;
|
||||
|
||||
ok(notification, "Notification should be visible");
|
||||
is(notification.value, "drmContentDisabled",
|
||||
is(notification.getAttribute("value"), "drmContentDisabled",
|
||||
"Should be showing the right notification");
|
||||
|
||||
// Verify the "Enable DRM" button is there.
|
||||
|
@ -27,7 +27,7 @@ add_task(async function() {
|
||||
ok(notification, "Infobar was shown.");
|
||||
is(notification.priority, notificationBox.PRIORITY_WARNING_MEDIUM,
|
||||
"Correct priority.");
|
||||
is(notification.getAttribute("label"),
|
||||
is(notification.messageText.textContent,
|
||||
"The GlobalTestPlugin plugin has crashed.",
|
||||
"Correct message.");
|
||||
});
|
||||
|
@ -132,11 +132,6 @@ var whitelist = [
|
||||
platforms: ["linux", "macosx"]},
|
||||
// Bug 1356031 (only used by devtools)
|
||||
{file: "chrome://global/skin/icons/error-16.png"},
|
||||
// Bug 1348526
|
||||
{file: "chrome://global/skin/tree/sort-asc-classic.png", platforms: ["linux"]},
|
||||
{file: "chrome://global/skin/tree/sort-asc.png", platforms: ["linux"]},
|
||||
{file: "chrome://global/skin/tree/sort-dsc-classic.png", platforms: ["linux"]},
|
||||
{file: "chrome://global/skin/tree/sort-dsc.png", platforms: ["linux"]},
|
||||
// Bug 1344267
|
||||
{file: "chrome://marionette/content/test_anonymous_content.xul"},
|
||||
{file: "chrome://marionette/content/test_dialog.properties"},
|
||||
|
@ -14,19 +14,8 @@ function promiseNotification(aBrowser, value, expected, input) {
|
||||
let notificationBox = aBrowser.getNotificationBox(aBrowser.selectedBrowser);
|
||||
if (expected) {
|
||||
info("Waiting for " + value + " notification");
|
||||
let checkForNotification = function() {
|
||||
if (notificationBox.getNotificationWithValue(value)) {
|
||||
info("Saw the notification");
|
||||
notificationObserver.disconnect();
|
||||
notificationObserver = null;
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
if (notificationObserver) {
|
||||
notificationObserver.disconnect();
|
||||
}
|
||||
notificationObserver = new MutationObserver(checkForNotification);
|
||||
notificationObserver.observe(notificationBox.stack, {childList: true});
|
||||
resolve(BrowserTestUtils.waitForNotificationInNotificationBox(
|
||||
notificationBox, value));
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
is(notificationBox.getNotificationWithValue(value), null,
|
||||
@ -152,7 +141,7 @@ function get_test_function_for_localhost_with_hostname(hostName, isPrivate) {
|
||||
let notificationBox = browser.getNotificationBox(tab.linkedBrowser);
|
||||
let notification = notificationBox.getNotificationWithValue("keyword-uri-fixup");
|
||||
let docLoadPromise = waitForDocLoadAndStopIt("http://" + hostName + "/", tab.linkedBrowser);
|
||||
notification.querySelector(".notification-button-default").click();
|
||||
notification.querySelector("button").click();
|
||||
|
||||
// check pref value
|
||||
let prefValue = Services.prefs.getBoolPref(pref);
|
||||
|
@ -49,7 +49,7 @@ add_task(async function test_slow_content_script() {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
|
||||
|
||||
let notification = await BrowserTestUtils.waitForGlobalNotificationBar(window, "process-hang");
|
||||
let text = document.getAnonymousElementByAttribute(notification, "anonid", "messageText").textContent;
|
||||
let text = notification.messageText.textContent;
|
||||
|
||||
ok(text.includes("\u201cSlow Script Extension\u201d"),
|
||||
"Label is correct");
|
||||
|
@ -22,13 +22,12 @@ add_task(async function() {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
is(notification.type, "info", "We expect this notification to have the type of 'info'.");
|
||||
isnot(notification.image, null, "We expect this notification to have an icon.");
|
||||
is(notification.getAttribute("type"), "info",
|
||||
"We expect this notification to have the type of 'info'.");
|
||||
ok(notification.messageImage.getAttribute("src"),
|
||||
"We expect this notification to have an icon.");
|
||||
|
||||
let buttons = notification.getElementsByClassName("notification-button-default");
|
||||
is(buttons.length, 1, "We expect see one default button.");
|
||||
|
||||
buttons = notification.getElementsByClassName("notification-button");
|
||||
let buttons = notification.getElementsByClassName("notification-button");
|
||||
is(buttons.length, 1, "We expect see one button.");
|
||||
|
||||
let button = buttons[0];
|
||||
|
@ -333,7 +333,8 @@ function testShowNotification() {
|
||||
ok(updateBox, "Update notification box should have been displayed");
|
||||
if (updateBox) {
|
||||
if (testCase.notificationText) {
|
||||
is(updateBox.label, testCase.notificationText, "Update notification box " +
|
||||
is(updateBox.messageText.textContent, testCase.notificationText,
|
||||
"Update notification box " +
|
||||
"should have the label provided by the update");
|
||||
}
|
||||
if (testCase.notificationButtonLabel) {
|
||||
|
@ -228,7 +228,8 @@ TranslationUI.prototype = {
|
||||
showTranslationInfoBar() {
|
||||
let notificationBox = this.notificationBox;
|
||||
let notif = notificationBox.appendNotification("", "translation", null,
|
||||
notificationBox.PRIORITY_INFO_HIGH);
|
||||
notificationBox.PRIORITY_INFO_HIGH, null, null,
|
||||
"translation-notification");
|
||||
notif.init(this);
|
||||
return notif;
|
||||
},
|
||||
|
11
browser/components/translation/content/.eslintrc.js
Normal file
11
browser/components/translation/content/.eslintrc.js
Normal file
@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"env": {
|
||||
"mozilla/browser-window": true,
|
||||
},
|
||||
|
||||
"plugins": [
|
||||
"mozilla",
|
||||
]
|
||||
};
|
@ -2,5 +2,5 @@
|
||||
# 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/.
|
||||
browser.jar:
|
||||
content/browser/translation-infobar.xml
|
||||
content/browser/translation-notification.js
|
||||
content/browser/microsoft-translator-attribution.png
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
7
browser/components/translation/content/moz.build
Normal file
7
browser/components/translation/content/moz.build
Normal file
@ -0,0 +1,7 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=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/.
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
@ -0,0 +1,349 @@
|
||||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
class MozTranslationNotification extends MozElements.Notification {
|
||||
connectedCallback() {
|
||||
this.appendChild(MozXULElement.parseXULToFragment(`
|
||||
<hbox anonid="details" align="center" flex="1">
|
||||
<image class="translate-infobar-element messageImage"/>
|
||||
<panel anonid="welcomePanel" class="translation-welcome-panel" type="arrow" align="start">
|
||||
<image class="translation-welcome-logo"/>
|
||||
<vbox flex="1" class="translation-welcome-content">
|
||||
<description class="translation-welcome-headline" anonid="welcomeHeadline"/>
|
||||
<description class="translation-welcome-body" anonid="welcomeBody"/>
|
||||
<hbox align="center">
|
||||
<label anonid="learnMore" class="plain text-link" onclick="openTrustedLinkIn('https://support.mozilla.org/kb/automatic-translation', 'tab'); this.parentNode.parentNode.parentNode.hidePopup();"/>
|
||||
<spacer flex="1"/>
|
||||
<button class="translate-infobar-element" anonid="thanksButton" onclick="this.parentNode.parentNode.parentNode.hidePopup();"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</panel>
|
||||
<deck anonid="translationStates" selectedIndex="0">
|
||||
<hbox class="translate-offer-box" align="center">
|
||||
<label class="translate-infobar-element" value="&translation.thisPageIsIn.label;"/>
|
||||
<menulist class="translate-infobar-element" anonid="detectedLanguage">
|
||||
<menupopup/>
|
||||
</menulist>
|
||||
<label class="translate-infobar-element" value="&translation.translateThisPage.label;"/>
|
||||
<button class="translate-infobar-element" label="&translation.translate.button;" anonid="translate" oncommand="this.closest('notification').translate();"/>
|
||||
<button class="translate-infobar-element" label="&translation.notNow.button;" anonid="notNow" oncommand="this.closest('notification').closeCommand();"/>
|
||||
</hbox>
|
||||
<vbox class="translating-box" pack="center">
|
||||
<label class="translate-infobar-element" value="&translation.translatingContent.label;"/>
|
||||
</vbox>
|
||||
<hbox class="translated-box" align="center">
|
||||
<label class="translate-infobar-element" value="&translation.translatedFrom.label;"/>
|
||||
<menulist class="translate-infobar-element" anonid="fromLanguage" oncommand="this.closest('notification').translate();">
|
||||
<menupopup/>
|
||||
</menulist>
|
||||
<label class="translate-infobar-element" value="&translation.translatedTo.label;"/>
|
||||
<menulist class="translate-infobar-element" anonid="toLanguage" oncommand="this.closest('notification').translate();">
|
||||
<menupopup/>
|
||||
</menulist>
|
||||
<label class="translate-infobar-element" value="&translation.translatedToSuffix.label;"/>
|
||||
<button anonid="showOriginal" class="translate-infobar-element" label="&translation.showOriginal.button;" oncommand="this.closest('notification').showOriginal();"/>
|
||||
<button anonid="showTranslation" class="translate-infobar-element" label="&translation.showTranslation.button;" oncommand="this.closest('notification').showTranslation();"/>
|
||||
</hbox>
|
||||
<hbox class="translation-error" align="center">
|
||||
<label class="translate-infobar-element" value="&translation.errorTranslating.label;"/>
|
||||
<button class="translate-infobar-element" label="&translation.tryAgain.button;" anonid="tryAgain" oncommand="this.closest('notification').translate();"/>
|
||||
</hbox>
|
||||
<vbox class="translation-unavailable" pack="center">
|
||||
<label class="translate-infobar-element" value="&translation.serviceUnavailable.label;"/>
|
||||
</vbox>
|
||||
</deck>
|
||||
<spacer flex="1"/>
|
||||
<button type="menu" class="translate-infobar-element options-menu-button" anonid="options" label="&translation.options.menu;">
|
||||
<menupopup class="translation-menupopup cui-widget-panel cui-widget-panelview
|
||||
cui-widget-panelWithFooter PanelUI-subView" onpopupshowing="this.closest('notification').optionsShowing();">
|
||||
<menuitem anonid="neverForLanguage" oncommand="this.closest('notification').neverForLanguage();"/>
|
||||
<menuitem anonid="neverForSite" oncommand="this.closest('notification').neverForSite();" label="&translation.options.neverForSite.label;" accesskey="&translation.options.neverForSite.accesskey;"/>
|
||||
<menuseparator/>
|
||||
<menuitem oncommand="openPreferences('paneGeneral', {origin:'translationInfobar'});" label="&translation.options.preferences.label;" accesskey="&translation.options.preferences.accesskey;"/>
|
||||
<menuitem class="subviewbutton panel-subview-footer" oncommand="this.closest('notification').openProviderAttribution();">
|
||||
<deck anonid="translationEngine" selectedIndex="0">
|
||||
<hbox class="translation-attribution">
|
||||
<label/>
|
||||
<image src="chrome://browser/content/microsoft-translator-attribution.png" aria-label="Microsoft Translator"/>
|
||||
<label/>
|
||||
</hbox>
|
||||
<label class="translation-attribution"/>
|
||||
</deck>
|
||||
</menuitem>
|
||||
</menupopup>
|
||||
</button>
|
||||
</hbox>
|
||||
<toolbarbutton anonid="closeButton" ondblclick="event.stopPropagation();"
|
||||
class="messageCloseButton close-icon tabbable"
|
||||
tooltiptext="&closeNotification.tooltip;"
|
||||
oncommand="this.parentNode.closeCommand();"/>
|
||||
`, [
|
||||
"chrome://global/locale/notification.dtd",
|
||||
"chrome://browser/locale/translation.dtd",
|
||||
]));
|
||||
|
||||
for (let [propertyName, selector] of [
|
||||
["details", "[anonid=details]"],
|
||||
["messageImage", ".messageImage"],
|
||||
["spacer", "[anonid=spacer]"],
|
||||
]) {
|
||||
this[propertyName] = this.querySelector(selector);
|
||||
}
|
||||
}
|
||||
|
||||
set state(val) {
|
||||
let deck = this._getAnonElt("translationStates");
|
||||
|
||||
let activeElt = document.activeElement;
|
||||
if (activeElt && deck.contains(activeElt))
|
||||
activeElt.blur();
|
||||
|
||||
let stateName;
|
||||
for (let name of ["OFFER", "TRANSLATING", "TRANSLATED", "ERROR"]) {
|
||||
if (Translation["STATE_" + name] == val) {
|
||||
stateName = name.toLowerCase();
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.setAttribute("state", stateName);
|
||||
|
||||
if (val == Translation.STATE_TRANSLATED)
|
||||
this._handleButtonHiding();
|
||||
|
||||
deck.selectedIndex = val;
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this._getAnonElt("translationStates").selectedIndex;
|
||||
}
|
||||
|
||||
init(aTranslation) {
|
||||
this.translation = aTranslation;
|
||||
|
||||
let sortByLocalizedName = function(aList) {
|
||||
let names = Services.intl.getLanguageDisplayNames(undefined, aList);
|
||||
return aList.map((code, i) => [code, names[i]])
|
||||
.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
};
|
||||
|
||||
// Fill the lists of supported source languages.
|
||||
let detectedLanguage = this._getAnonElt("detectedLanguage");
|
||||
let fromLanguage = this._getAnonElt("fromLanguage");
|
||||
let sourceLanguages =
|
||||
sortByLocalizedName(Translation.supportedSourceLanguages);
|
||||
for (let [code, name] of sourceLanguages) {
|
||||
detectedLanguage.appendItem(name, code);
|
||||
fromLanguage.appendItem(name, code);
|
||||
}
|
||||
detectedLanguage.value = this.translation.detectedLanguage;
|
||||
|
||||
// translatedFrom is only set if we have already translated this page.
|
||||
if (aTranslation.translatedFrom)
|
||||
fromLanguage.value = aTranslation.translatedFrom;
|
||||
|
||||
// Fill the list of supported target languages.
|
||||
let toLanguage = this._getAnonElt("toLanguage");
|
||||
let targetLanguages =
|
||||
sortByLocalizedName(Translation.supportedTargetLanguages);
|
||||
for (let [code, name] of targetLanguages)
|
||||
toLanguage.appendItem(name, code);
|
||||
|
||||
if (aTranslation.translatedTo)
|
||||
toLanguage.value = aTranslation.translatedTo;
|
||||
|
||||
if (aTranslation.state)
|
||||
this.state = aTranslation.state;
|
||||
|
||||
// Show attribution for the preferred translator.
|
||||
let engineIndex = Object.keys(Translation.supportedEngines)
|
||||
.indexOf(Translation.translationEngine);
|
||||
// We currently only have attribution for the Bing and Yandex engines.
|
||||
if (engineIndex >= 0) {
|
||||
--engineIndex;
|
||||
}
|
||||
let attributionNode = this._getAnonElt("translationEngine");
|
||||
if (engineIndex != -1) {
|
||||
attributionNode.selectedIndex = engineIndex;
|
||||
} else {
|
||||
// Hide the attribution menuitem
|
||||
let footer = attributionNode.parentNode;
|
||||
footer.hidden = true;
|
||||
// Make the 'Translation preferences' item the new footer.
|
||||
footer = footer.previousSibling;
|
||||
footer.setAttribute("class", "subviewbutton panel-subview-footer");
|
||||
// And hide the menuseparator.
|
||||
footer.previousSibling.hidden = true;
|
||||
}
|
||||
|
||||
const kWelcomePref = "browser.translation.ui.welcomeMessageShown";
|
||||
if (Services.prefs.prefHasUserValue(kWelcomePref) ||
|
||||
this.translation.browser != gBrowser.selectedBrowser)
|
||||
return;
|
||||
|
||||
this.addEventListener("transitionend", function() {
|
||||
// These strings are hardcoded because they need to reach beta
|
||||
// without riding the trains.
|
||||
let localizedStrings = {
|
||||
en: ["Hey look! It's something new!",
|
||||
"Now the Web is even more accessible with our new in-page translation feature. Click the translate button to try it!",
|
||||
"Learn more.",
|
||||
"Thanks",
|
||||
],
|
||||
"es-AR": ["\xA1Mir\xE1! \xA1Hay algo nuevo!",
|
||||
"Ahora la web es a\xFAn m\xE1s accesible con nuestra nueva funcionalidad de traducci\xF3n integrada. \xA1Hac\xE9 clic en el bot\xF3n traducir para probarla!",
|
||||
"Conoc\xE9 m\xE1s.",
|
||||
"Gracias",
|
||||
],
|
||||
"es-ES": ["\xA1Mira! \xA1Hay algo nuevo!",
|
||||
"Con la nueva funcionalidad de traducci\xF3n integrada, ahora la Web es a\xFAn m\xE1s accesible. \xA1Pulsa el bot\xF3n Traducir y pru\xE9bala!",
|
||||
"M\xE1s informaci\xF3n.",
|
||||
"Gracias",
|
||||
],
|
||||
pl: ["Sp\xF3jrz tutaj! To co\u015B nowego!",
|
||||
"Sie\u0107 sta\u0142a si\u0119 w\u0142a\u015Bnie jeszcze bardziej dost\u0119pna dzi\u0119ki opcji bezpo\u015Bredniego t\u0142umaczenia stron. Kliknij przycisk t\u0142umaczenia, aby spr\xF3bowa\u0107!",
|
||||
"Dowiedz si\u0119 wi\u0119cej",
|
||||
"Dzi\u0119kuj\u0119",
|
||||
],
|
||||
tr: ["Bak\u0131n, burada yeni bir \u015Fey var!",
|
||||
"Yeni sayfa i\xE7i \xE7eviri \xF6zelli\u011Fimiz sayesinde Web art\u0131k \xE7ok daha anla\u015F\u0131l\u0131r olacak. Denemek i\xE7in \xC7evir d\xFC\u011Fmesine t\u0131klay\u0131n!",
|
||||
"Daha fazla bilgi al\u0131n.",
|
||||
"Te\u015Fekk\xFCrler",
|
||||
],
|
||||
vi: ["Nh\xECn n\xE0y! \u0110\u1ED3 m\u1EDBi!",
|
||||
"Gi\u1EDD \u0111\xE2y ch\xFAng ta c\xF3 th\u1EC3 ti\u1EBFp c\u1EADn web d\u1EC5 d\xE0ng h\u01A1n n\u1EEFa v\u1EDBi t\xEDnh n\u0103ng d\u1ECBch ngay trong trang. Hay nh\u1EA5n n\xFAt d\u1ECBch \u0111\u1EC3 th\u1EED!",
|
||||
"T\xECm hi\u1EC3u th\xEAm.",
|
||||
"C\u1EA3m \u01A1n",
|
||||
],
|
||||
};
|
||||
|
||||
let locale = Services.locale.appLocaleAsLangTag;
|
||||
if (!(locale in localizedStrings))
|
||||
locale = "en";
|
||||
let strings = localizedStrings[locale];
|
||||
|
||||
this._getAnonElt("welcomeHeadline").setAttribute("value", strings[0]);
|
||||
this._getAnonElt("welcomeBody").textContent = strings[1];
|
||||
this._getAnonElt("learnMore").setAttribute("value", strings[2]);
|
||||
this._getAnonElt("thanksButton").setAttribute("label", strings[3]);
|
||||
|
||||
let panel = this._getAnonElt("welcomePanel");
|
||||
panel.openPopup(this._getAnonElt("messageImage"),
|
||||
"bottomcenter topleft");
|
||||
|
||||
Services.prefs.setBoolPref(kWelcomePref, true);
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
_getAnonElt(aAnonId) {
|
||||
return this.querySelector("[anonid=" + aAnonId + "]");
|
||||
}
|
||||
|
||||
translate() {
|
||||
if (this.state == Translation.STATE_OFFER) {
|
||||
this._getAnonElt("fromLanguage").value =
|
||||
this._getAnonElt("detectedLanguage").value;
|
||||
this._getAnonElt("toLanguage").value =
|
||||
Translation.defaultTargetLanguage;
|
||||
}
|
||||
|
||||
this.translation.translate(this._getAnonElt("fromLanguage").value,
|
||||
this._getAnonElt("toLanguage").value);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when the infobar should be closed per user's wish (e.g.
|
||||
* by clicking the notification's close button
|
||||
*/
|
||||
closeCommand() {
|
||||
this.close();
|
||||
this.translation.infobarClosed();
|
||||
}
|
||||
|
||||
_handleButtonHiding() {
|
||||
let originalShown = this.translation.originalShown;
|
||||
this._getAnonElt("showOriginal").hidden = originalShown;
|
||||
this._getAnonElt("showTranslation").hidden = !originalShown;
|
||||
}
|
||||
|
||||
showOriginal() {
|
||||
this.translation.showOriginalContent();
|
||||
this._handleButtonHiding();
|
||||
}
|
||||
|
||||
showTranslation() {
|
||||
this.translation.showTranslatedContent();
|
||||
this._handleButtonHiding();
|
||||
}
|
||||
|
||||
optionsShowing() {
|
||||
// Get the source language name.
|
||||
let lang;
|
||||
if (this.state == Translation.STATE_OFFER)
|
||||
lang = this._getAnonElt("detectedLanguage").value;
|
||||
else {
|
||||
lang = this._getAnonElt("fromLanguage").value;
|
||||
|
||||
// If we have never attempted to translate the page before the
|
||||
// service became unavailable, "fromLanguage" isn't set.
|
||||
if (!lang && this.state == Translation.STATE_UNAVAILABLE)
|
||||
lang = this.translation.detectedLanguage;
|
||||
}
|
||||
|
||||
let langName = Services.intl.getLanguageDisplayNames(undefined, [lang])[0];
|
||||
|
||||
// Set the label and accesskey on the menuitem.
|
||||
let bundle =
|
||||
Services.strings.createBundle("chrome://browser/locale/translation.properties");
|
||||
let item = this._getAnonElt("neverForLanguage");
|
||||
const kStrId = "translation.options.neverForLanguage";
|
||||
item.setAttribute("label",
|
||||
bundle.formatStringFromName(kStrId + ".label", [langName], 1));
|
||||
item.setAttribute("accesskey",
|
||||
bundle.GetStringFromName(kStrId + ".accesskey"));
|
||||
item.langCode = lang;
|
||||
|
||||
// We may need to disable the menuitems if they have already been used.
|
||||
// Check if translation is already disabled for this language:
|
||||
let neverForLangs =
|
||||
Services.prefs.getCharPref("browser.translation.neverForLanguages");
|
||||
item.disabled = neverForLangs.split(",").includes(lang);
|
||||
|
||||
// Check if translation is disabled for the domain:
|
||||
let uri = this.translation.browser.currentURI;
|
||||
let perms = Services.perms;
|
||||
item = this._getAnonElt("neverForSite");
|
||||
item.disabled =
|
||||
perms.testExactPermission(uri, "translate") == perms.DENY_ACTION;
|
||||
}
|
||||
|
||||
neverForLanguage() {
|
||||
const kPrefName = "browser.translation.neverForLanguages";
|
||||
|
||||
let val = Services.prefs.getCharPref(kPrefName);
|
||||
if (val)
|
||||
val += ",";
|
||||
val += this._getAnonElt("neverForLanguage").langCode;
|
||||
|
||||
Services.prefs.setCharPref(kPrefName, val);
|
||||
|
||||
this.closeCommand();
|
||||
}
|
||||
|
||||
neverForSite() {
|
||||
let uri = this.translation.browser.currentURI;
|
||||
let perms = Services.perms;
|
||||
perms.add(uri, "translate", perms.DENY_ACTION);
|
||||
|
||||
this.closeCommand();
|
||||
}
|
||||
|
||||
openProviderAttribution() {
|
||||
Translation.openProviderAttribution();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("translation-notification", MozTranslationNotification,
|
||||
{ extends: "notification" });
|
@ -2,6 +2,10 @@
|
||||
# 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/.
|
||||
|
||||
DIRS += [
|
||||
'content',
|
||||
]
|
||||
|
||||
with Files("**"):
|
||||
BUG_COMPONENT = ("Firefox", "Translation")
|
||||
|
||||
@ -17,8 +21,6 @@ EXTRA_JS_MODULES.translation = [
|
||||
'YandexTranslator.jsm'
|
||||
]
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += [
|
||||
'test/browser.ini'
|
||||
]
|
||||
|
@ -1,439 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<!DOCTYPE bindings [
|
||||
<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
|
||||
%notificationDTD;
|
||||
<!ENTITY % translationDTD SYSTEM "chrome://browser/locale/translation.dtd" >
|
||||
%translationDTD;
|
||||
]>
|
||||
|
||||
<bindings id="translationBindings"
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl">
|
||||
<binding id="translationbar" extends="chrome://global/content/bindings/notification.xml#notification">
|
||||
<content>
|
||||
<xul:hbox anonid="details" align="center" flex="1">
|
||||
<xul:image class="translate-infobar-element messageImage"
|
||||
anonid="messageImage"/>
|
||||
<xul:panel anonid="welcomePanel" class="translation-welcome-panel"
|
||||
type="arrow" align="start">
|
||||
<xul:image class="translation-welcome-logo"/>
|
||||
<xul:vbox flex="1" class="translation-welcome-content">
|
||||
<xul:description class="translation-welcome-headline"
|
||||
anonid="welcomeHeadline"/>
|
||||
<xul:description class="translation-welcome-body" anonid="welcomeBody"/>
|
||||
<xul:hbox align="center">
|
||||
<xul:label anonid="learnMore" class="plain text-link"
|
||||
onclick="openTrustedLinkIn('https://support.mozilla.org/kb/automatic-translation', 'tab'); this.parentNode.parentNode.parentNode.hidePopup();"/>
|
||||
<xul:spacer flex="1"/>
|
||||
<xul:button class="translate-infobar-element" anonid="thanksButton"
|
||||
onclick="this.parentNode.parentNode.parentNode.hidePopup();"/>
|
||||
</xul:hbox>
|
||||
</xul:vbox>
|
||||
</xul:panel>
|
||||
<xul:deck anonid="translationStates" selectedIndex="0">
|
||||
|
||||
<!-- offer to translate -->
|
||||
<xul:hbox class="translate-offer-box" align="center">
|
||||
<xul:label class="translate-infobar-element" value="&translation.thisPageIsIn.label;"/>
|
||||
<xul:menulist class="translate-infobar-element" anonid="detectedLanguage">
|
||||
<xul:menupopup/>
|
||||
</xul:menulist>
|
||||
<xul:label class="translate-infobar-element" value="&translation.translateThisPage.label;"/>
|
||||
<xul:button class="translate-infobar-element"
|
||||
label="&translation.translate.button;"
|
||||
anonid="translate"
|
||||
oncommand="document.getBindingParent(this).translate();"/>
|
||||
<xul:button class="translate-infobar-element"
|
||||
label="&translation.notNow.button;" anonid="notNow"
|
||||
oncommand="document.getBindingParent(this).closeCommand();"/>
|
||||
</xul:hbox>
|
||||
|
||||
<!-- translating -->
|
||||
<xul:vbox class="translating-box" pack="center">
|
||||
<xul:label class="translate-infobar-element"
|
||||
value="&translation.translatingContent.label;"/>
|
||||
</xul:vbox>
|
||||
|
||||
<!-- translated -->
|
||||
<xul:hbox class="translated-box" align="center">
|
||||
<xul:label class="translate-infobar-element"
|
||||
value="&translation.translatedFrom.label;"/>
|
||||
<xul:menulist class="translate-infobar-element"
|
||||
anonid="fromLanguage"
|
||||
oncommand="document.getBindingParent(this).translate()">
|
||||
<xul:menupopup/>
|
||||
</xul:menulist>
|
||||
<xul:label class="translate-infobar-element"
|
||||
value="&translation.translatedTo.label;"/>
|
||||
<xul:menulist class="translate-infobar-element"
|
||||
anonid="toLanguage"
|
||||
oncommand="document.getBindingParent(this).translate()">
|
||||
<xul:menupopup/>
|
||||
</xul:menulist>
|
||||
<xul:label class="translate-infobar-element"
|
||||
value="&translation.translatedToSuffix.label;"/>
|
||||
<xul:button anonid="showOriginal"
|
||||
class="translate-infobar-element"
|
||||
label="&translation.showOriginal.button;"
|
||||
oncommand="document.getBindingParent(this).showOriginal();"/>
|
||||
<xul:button anonid="showTranslation"
|
||||
class="translate-infobar-element"
|
||||
label="&translation.showTranslation.button;"
|
||||
oncommand="document.getBindingParent(this).showTranslation();"/>
|
||||
</xul:hbox>
|
||||
|
||||
<!-- error -->
|
||||
<xul:hbox class="translation-error" align="center">
|
||||
<xul:label class="translate-infobar-element"
|
||||
value="&translation.errorTranslating.label;"/>
|
||||
<xul:button class="translate-infobar-element"
|
||||
label="&translation.tryAgain.button;"
|
||||
anonid="tryAgain"
|
||||
oncommand="document.getBindingParent(this).translate();"/>
|
||||
</xul:hbox>
|
||||
|
||||
<!-- unavailable -->
|
||||
<xul:vbox class="translation-unavailable" pack="center">
|
||||
<xul:label class="translate-infobar-element"
|
||||
value="&translation.serviceUnavailable.label;"/>
|
||||
</xul:vbox>
|
||||
|
||||
</xul:deck>
|
||||
<xul:spacer flex="1"/>
|
||||
|
||||
<xul:button type="menu"
|
||||
class="translate-infobar-element options-menu-button"
|
||||
anonid="options"
|
||||
label="&translation.options.menu;">
|
||||
<xul:menupopup class="translation-menupopup cui-widget-panel cui-widget-panelview
|
||||
cui-widget-panelWithFooter PanelUI-subView"
|
||||
onpopupshowing="document.getBindingParent(this).optionsShowing();">
|
||||
<xul:menuitem anonid="neverForLanguage"
|
||||
oncommand="document.getBindingParent(this).neverForLanguage();"/>
|
||||
<xul:menuitem anonid="neverForSite"
|
||||
oncommand="document.getBindingParent(this).neverForSite();"
|
||||
label="&translation.options.neverForSite.label;"
|
||||
accesskey="&translation.options.neverForSite.accesskey;"/>
|
||||
<xul:menuseparator/>
|
||||
<xul:menuitem oncommand="openPreferences('paneGeneral', {origin:'translationInfobar'});"
|
||||
label="&translation.options.preferences.label;"
|
||||
accesskey="&translation.options.preferences.accesskey;"/>
|
||||
<xul:menuitem class="subviewbutton panel-subview-footer"
|
||||
oncommand="document.getBindingParent(this).openProviderAttribution();">
|
||||
<xul:deck anonid="translationEngine" selectedIndex="0">
|
||||
<xul:hbox class="translation-attribution">
|
||||
<xul:label>&translation.options.attribution.beforeLogo;</xul:label>
|
||||
<xul:image src="chrome://browser/content/microsoft-translator-attribution.png"
|
||||
aria-label="Microsoft Translator"/>
|
||||
<xul:label>&translation.options.attribution.afterLogo;</xul:label>
|
||||
</xul:hbox>
|
||||
<xul:label class="translation-attribution">&translation.options.attribution.yandexTranslate;</xul:label>
|
||||
</xul:deck>
|
||||
</xul:menuitem>
|
||||
</xul:menupopup>
|
||||
</xul:button>
|
||||
|
||||
</xul:hbox>
|
||||
<xul:toolbarbutton ondblclick="event.stopPropagation();"
|
||||
anonid="closeButton"
|
||||
class="messageCloseButton close-icon tabbable"
|
||||
xbl:inherits="hidden=hideclose"
|
||||
tooltiptext="&closeNotification.tooltip;"
|
||||
oncommand="document.getBindingParent(this).closeCommand();"/>
|
||||
</content>
|
||||
<implementation>
|
||||
<property name="state"
|
||||
onget="return this._getAnonElt('translationStates').selectedIndex;">
|
||||
<setter>
|
||||
<![CDATA[
|
||||
let deck = this._getAnonElt("translationStates");
|
||||
|
||||
let activeElt = document.activeElement;
|
||||
if (activeElt && deck.contains(activeElt))
|
||||
activeElt.blur();
|
||||
|
||||
let stateName;
|
||||
for (let name of ["OFFER", "TRANSLATING", "TRANSLATED", "ERROR"]) {
|
||||
if (Translation["STATE_" + name] == val) {
|
||||
stateName = name.toLowerCase();
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.setAttribute("state", stateName);
|
||||
|
||||
if (val == Translation.STATE_TRANSLATED)
|
||||
this._handleButtonHiding();
|
||||
|
||||
deck.selectedIndex = val;
|
||||
]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<method name="init">
|
||||
<parameter name="aTranslation"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.translation = aTranslation;
|
||||
|
||||
let sortByLocalizedName = function(aList) {
|
||||
let names = Services.intl.getLanguageDisplayNames(undefined, aList);
|
||||
return aList.map((code, i) => [code, names[i]])
|
||||
.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
};
|
||||
|
||||
// Fill the lists of supported source languages.
|
||||
let detectedLanguage = this._getAnonElt("detectedLanguage");
|
||||
let fromLanguage = this._getAnonElt("fromLanguage");
|
||||
let sourceLanguages =
|
||||
sortByLocalizedName(Translation.supportedSourceLanguages);
|
||||
for (let [code, name] of sourceLanguages) {
|
||||
detectedLanguage.appendItem(name, code);
|
||||
fromLanguage.appendItem(name, code);
|
||||
}
|
||||
detectedLanguage.value = this.translation.detectedLanguage;
|
||||
|
||||
// translatedFrom is only set if we have already translated this page.
|
||||
if (aTranslation.translatedFrom)
|
||||
fromLanguage.value = aTranslation.translatedFrom;
|
||||
|
||||
// Fill the list of supported target languages.
|
||||
let toLanguage = this._getAnonElt("toLanguage");
|
||||
let targetLanguages =
|
||||
sortByLocalizedName(Translation.supportedTargetLanguages);
|
||||
for (let [code, name] of targetLanguages)
|
||||
toLanguage.appendItem(name, code);
|
||||
|
||||
if (aTranslation.translatedTo)
|
||||
toLanguage.value = aTranslation.translatedTo;
|
||||
|
||||
if (aTranslation.state)
|
||||
this.state = aTranslation.state;
|
||||
|
||||
// Show attribution for the preferred translator.
|
||||
let engineIndex = Object.keys(Translation.supportedEngines)
|
||||
.indexOf(Translation.translationEngine);
|
||||
// We currently only have attribution for the Bing and Yandex engines.
|
||||
if (engineIndex >= 0) {
|
||||
--engineIndex;
|
||||
}
|
||||
let attributionNode = this._getAnonElt("translationEngine");
|
||||
if (engineIndex != -1) {
|
||||
attributionNode.selectedIndex = engineIndex;
|
||||
} else {
|
||||
// Hide the attribution menuitem
|
||||
let footer = attributionNode.parentNode;
|
||||
footer.hidden = true;
|
||||
// Make the 'Translation preferences' item the new footer.
|
||||
footer = footer.previousSibling;
|
||||
footer.setAttribute("class", "subviewbutton panel-subview-footer");
|
||||
// And hide the menuseparator.
|
||||
footer.previousSibling.hidden = true;
|
||||
}
|
||||
|
||||
const kWelcomePref = "browser.translation.ui.welcomeMessageShown";
|
||||
if (Services.prefs.prefHasUserValue(kWelcomePref) ||
|
||||
this.translation.browser != gBrowser.selectedBrowser)
|
||||
return;
|
||||
|
||||
this.addEventListener("transitionend", function() {
|
||||
// These strings are hardcoded because they need to reach beta
|
||||
// without riding the trains.
|
||||
let localizedStrings = {
|
||||
en: ["Hey look! It's something new!",
|
||||
"Now the Web is even more accessible with our new in-page translation feature. Click the translate button to try it!",
|
||||
"Learn more.",
|
||||
"Thanks"],
|
||||
"es-AR": ["\xA1Mir\xE1! \xA1Hay algo nuevo!",
|
||||
"Ahora la web es a\xFAn m\xE1s accesible con nuestra nueva funcionalidad de traducci\xF3n integrada. \xA1Hac\xE9 clic en el bot\xF3n traducir para probarla!",
|
||||
"Conoc\xE9 m\xE1s.",
|
||||
"Gracias"],
|
||||
"es-ES": ["\xA1Mira! \xA1Hay algo nuevo!",
|
||||
"Con la nueva funcionalidad de traducci\xF3n integrada, ahora la Web es a\xFAn m\xE1s accesible. \xA1Pulsa el bot\xF3n Traducir y pru\xE9bala!",
|
||||
"M\xE1s informaci\xF3n.",
|
||||
"Gracias"],
|
||||
pl: ["Sp\xF3jrz tutaj! To co\u015B nowego!",
|
||||
"Sie\u0107 sta\u0142a si\u0119 w\u0142a\u015Bnie jeszcze bardziej dost\u0119pna dzi\u0119ki opcji bezpo\u015Bredniego t\u0142umaczenia stron. Kliknij przycisk t\u0142umaczenia, aby spr\xF3bowa\u0107!",
|
||||
"Dowiedz si\u0119 wi\u0119cej",
|
||||
"Dzi\u0119kuj\u0119"],
|
||||
tr: ["Bak\u0131n, burada yeni bir \u015Fey var!",
|
||||
"Yeni sayfa i\xE7i \xE7eviri \xF6zelli\u011Fimiz sayesinde Web art\u0131k \xE7ok daha anla\u015F\u0131l\u0131r olacak. Denemek i\xE7in \xC7evir d\xFC\u011Fmesine t\u0131klay\u0131n!",
|
||||
"Daha fazla bilgi al\u0131n.",
|
||||
"Te\u015Fekk\xFCrler"],
|
||||
vi: ["Nh\xECn n\xE0y! \u0110\u1ED3 m\u1EDBi!",
|
||||
"Gi\u1EDD \u0111\xE2y ch\xFAng ta c\xF3 th\u1EC3 ti\u1EBFp c\u1EADn web d\u1EC5 d\xE0ng h\u01A1n n\u1EEFa v\u1EDBi t\xEDnh n\u0103ng d\u1ECBch ngay trong trang. Hay nh\u1EA5n n\xFAt d\u1ECBch \u0111\u1EC3 th\u1EED!",
|
||||
"T\xECm hi\u1EC3u th\xEAm.",
|
||||
"C\u1EA3m \u01A1n"],
|
||||
};
|
||||
|
||||
let locale = Services.locale.appLocaleAsLangTag;
|
||||
if (!(locale in localizedStrings))
|
||||
locale = "en";
|
||||
let strings = localizedStrings[locale];
|
||||
|
||||
this._getAnonElt("welcomeHeadline").setAttribute("value", strings[0]);
|
||||
this._getAnonElt("welcomeBody").textContent = strings[1];
|
||||
this._getAnonElt("learnMore").setAttribute("value", strings[2]);
|
||||
this._getAnonElt("thanksButton").setAttribute("label", strings[3]);
|
||||
|
||||
let panel = this._getAnonElt("welcomePanel");
|
||||
panel.openPopup(this._getAnonElt("messageImage"),
|
||||
"bottomcenter topleft");
|
||||
|
||||
Services.prefs.setBoolPref(kWelcomePref, true);
|
||||
}, {once: true});
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_getAnonElt">
|
||||
<parameter name="aAnonId"/>
|
||||
<body>
|
||||
return document.getAnonymousElementByAttribute(this, "anonid", aAnonId);
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="translate">
|
||||
<body>
|
||||
<![CDATA[
|
||||
if (this.state == Translation.STATE_OFFER) {
|
||||
this._getAnonElt("fromLanguage").value =
|
||||
this._getAnonElt("detectedLanguage").value;
|
||||
this._getAnonElt("toLanguage").value =
|
||||
Translation.defaultTargetLanguage;
|
||||
}
|
||||
|
||||
this.translation.translate(this._getAnonElt("fromLanguage").value,
|
||||
this._getAnonElt("toLanguage").value);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<!-- To be called when the infobar should be closed per user's wish (e.g.
|
||||
by clicking the notification's close button -->
|
||||
<method name="closeCommand">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.close();
|
||||
this.translation.infobarClosed();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
<method name="_handleButtonHiding">
|
||||
<body>
|
||||
<![CDATA[
|
||||
let originalShown = this.translation.originalShown;
|
||||
this._getAnonElt("showOriginal").hidden = originalShown;
|
||||
this._getAnonElt("showTranslation").hidden = !originalShown;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="showOriginal">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.translation.showOriginalContent();
|
||||
this._handleButtonHiding();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="showTranslation">
|
||||
<body>
|
||||
<![CDATA[
|
||||
this.translation.showTranslatedContent();
|
||||
this._handleButtonHiding();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="optionsShowing">
|
||||
<body>
|
||||
<![CDATA[
|
||||
// Get the source language name.
|
||||
let lang;
|
||||
if (this.state == Translation.STATE_OFFER)
|
||||
lang = this._getAnonElt("detectedLanguage").value;
|
||||
else {
|
||||
lang = this._getAnonElt("fromLanguage").value;
|
||||
|
||||
// If we have never attempted to translate the page before the
|
||||
// service became unavailable, "fromLanguage" isn't set.
|
||||
if (!lang && this.state == Translation.STATE_UNAVAILABLE)
|
||||
lang = this.translation.detectedLanguage;
|
||||
}
|
||||
|
||||
let langName = Services.intl.getLanguageDisplayNames(undefined, [lang])[0];
|
||||
|
||||
// Set the label and accesskey on the menuitem.
|
||||
let bundle =
|
||||
Services.strings.createBundle("chrome://browser/locale/translation.properties");
|
||||
let item = this._getAnonElt("neverForLanguage");
|
||||
const kStrId = "translation.options.neverForLanguage";
|
||||
item.setAttribute("label",
|
||||
bundle.formatStringFromName(kStrId + ".label",
|
||||
[langName], 1));
|
||||
item.setAttribute("accesskey",
|
||||
bundle.GetStringFromName(kStrId + ".accesskey"));
|
||||
item.langCode = lang;
|
||||
|
||||
// We may need to disable the menuitems if they have already been used.
|
||||
// Check if translation is already disabled for this language:
|
||||
let neverForLangs =
|
||||
Services.prefs.getCharPref("browser.translation.neverForLanguages");
|
||||
item.disabled = neverForLangs.split(",").includes(lang);
|
||||
|
||||
// Check if translation is disabled for the domain:
|
||||
let uri = this.translation.browser.currentURI;
|
||||
let perms = Services.perms;
|
||||
item = this._getAnonElt("neverForSite");
|
||||
item.disabled =
|
||||
perms.testExactPermission(uri, "translate") == perms.DENY_ACTION;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="neverForLanguage">
|
||||
<body>
|
||||
<![CDATA[
|
||||
const kPrefName = "browser.translation.neverForLanguages";
|
||||
|
||||
let val = Services.prefs.getCharPref(kPrefName);
|
||||
if (val)
|
||||
val += ",";
|
||||
val += this._getAnonElt("neverForLanguage").langCode;
|
||||
|
||||
Services.prefs.setCharPref(kPrefName, val);
|
||||
|
||||
this.closeCommand();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="neverForSite">
|
||||
<body>
|
||||
<![CDATA[
|
||||
let uri = this.translation.browser.currentURI;
|
||||
let perms = Services.perms;
|
||||
perms.add(uri, "translate", perms.DENY_ACTION);
|
||||
|
||||
this.closeCommand();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="openProviderAttribution">
|
||||
<body>
|
||||
<![CDATA[
|
||||
Translation.openProviderAttribution();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
</implementation>
|
||||
</binding>
|
||||
</bindings>
|
@ -107,6 +107,10 @@
|
||||
flex: 0 1 100%;
|
||||
}
|
||||
|
||||
#street-address {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
#country-warning-message {
|
||||
box-sizing: border-box;
|
||||
font-size: 1rem;
|
||||
|
@ -279,9 +279,7 @@ add_task(async function test_other_ignored() {
|
||||
Assert.ok(notification, "There should be a notification");
|
||||
|
||||
// Dismiss notification, creating the .dmp.ignore file
|
||||
let closeButton =
|
||||
document.getAnonymousElementByAttribute(notification, "anonid", "close-button");
|
||||
closeButton.click();
|
||||
notification.querySelector(".messageCloseButton").click();
|
||||
gNotificationBox.removeNotification(notification, true);
|
||||
await waitForIgnoredReports(toIgnore);
|
||||
|
||||
@ -471,9 +469,7 @@ add_task(async function test_can_ignore() {
|
||||
Assert.ok(notification, "There should be a notification");
|
||||
|
||||
// Dismiss the notification by clicking on the "X" button.
|
||||
let closeButton =
|
||||
document.getAnonymousElementByAttribute(notification, "anonid", "close-button");
|
||||
closeButton.click();
|
||||
notification.querySelector(".messageCloseButton").click();
|
||||
// We'll not wait for the notification to finish its transition -
|
||||
// we'll just remove it right away.
|
||||
gNotificationBox.removeNotification(notification, true);
|
||||
@ -545,9 +541,7 @@ add_task(async function test_shutdown_while_not_showing() {
|
||||
Assert.ok(notification, "There should be a notification");
|
||||
|
||||
// Dismiss the notification by clicking on the "X" button.
|
||||
let closeButton =
|
||||
document.getAnonymousElementByAttribute(notification, "anonid", "close-button");
|
||||
closeButton.click();
|
||||
notification.querySelector(".messageCloseButton").click();
|
||||
// We'll not wait for the notification to finish its transition -
|
||||
// we'll just remove it right away.
|
||||
gNotificationBox.removeNotification(notification, true);
|
||||
|
@ -4,25 +4,25 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
%endif
|
||||
notification[value="translation"] .messageImage {
|
||||
list-style-image: url(chrome://browser/skin/translation-16.png);
|
||||
list-style-image: url(chrome://browser/skin/translation-16.png) !important;
|
||||
-moz-image-region: rect(0, 32px, 16px, 16px);
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
notification[value="translation"] .messageImage {
|
||||
list-style-image: url(chrome://browser/skin/translation-16@2x.png);
|
||||
list-style-image: url(chrome://browser/skin/translation-16@2x.png) !important;
|
||||
-moz-image-region: rect(0, 64px, 32px, 32px);
|
||||
}
|
||||
}
|
||||
|
||||
notification[value="translation"][state="translating"] .messageImage {
|
||||
list-style-image: url(chrome://browser/skin/translating-16.png);
|
||||
list-style-image: url(chrome://browser/skin/translating-16.png) !important;
|
||||
-moz-image-region: auto;
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
notification[value="translation"][state="translating"] .messageImage {
|
||||
list-style-image: url(chrome://browser/skin/translating-16@2x.png);
|
||||
list-style-image: url(chrome://browser/skin/translating-16@2x.png) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,8 +82,10 @@ function testOpenTestFile() {
|
||||
is(nb.allNotifications.length, 1, "There is just one notification");
|
||||
const cn = nb.currentNotification;
|
||||
is(cn.priority, nb.PRIORITY_WARNING_HIGH, "notification priority is correct");
|
||||
is(cn.value, "file-import-convert-failed", "notification value is corrent");
|
||||
is(cn.type, "warning", "notification type is correct");
|
||||
is(cn.getAttribute("value"), "file-import-convert-failed",
|
||||
"notification value is corrent");
|
||||
is(cn.getAttribute("type"), "warning",
|
||||
"notification type is correct");
|
||||
done();
|
||||
});
|
||||
ok(true, "importFromFile does not cause exception");
|
||||
|
@ -152,7 +152,7 @@ function testOpenDeletedFile() {
|
||||
"The missing file was successfully removed from the menu.");
|
||||
ok(gScratchpad.notificationBox.currentNotification,
|
||||
"The notification was successfully displayed.");
|
||||
is(gScratchpad.notificationBox.currentNotification.label,
|
||||
is(gScratchpad.notificationBox.currentNotification.messageText.textContent,
|
||||
gScratchpad.strings.GetStringFromName("fileNoLongerExists.notification"),
|
||||
"The notification label is correct.");
|
||||
|
||||
|
@ -63,7 +63,7 @@ const CSSUsageFront = protocol.FrontClassWithSpec(cssUsageSpec, {
|
||||
}
|
||||
} else {
|
||||
if (notification) {
|
||||
notification.remove();
|
||||
notification.close();
|
||||
notification = undefined;
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "nsIContentPolicy.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/dom/HTMLAllCollection.h"
|
||||
#include "mozilla/dom/FeaturePolicyUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsGlobalWindow.h"
|
||||
#include "nsString.h"
|
||||
@ -1022,6 +1023,12 @@ nsHTMLDocument::SetDomain(const nsAString& aDomain, ErrorResult& rv)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FeaturePolicyUtils::IsFeatureAllowed(this,
|
||||
NS_LITERAL_STRING("document-domain"))) {
|
||||
rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (aDomain.IsEmpty()) {
|
||||
rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return;
|
||||
|
@ -65,24 +65,17 @@ bool
|
||||
PaymentRequest::PrefEnabled(JSContext* aCx, JSObject* aObj)
|
||||
{
|
||||
#if defined(NIGHTLY_BUILD)
|
||||
const char* supportedRegions[] = { "US", "CA" };
|
||||
|
||||
if (!XRE_IsContentProcess()) {
|
||||
return false;
|
||||
}
|
||||
if (!StaticPrefs::dom_payments_request_enabled()) {
|
||||
return false;
|
||||
}
|
||||
RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
|
||||
MOZ_ASSERT(manager);
|
||||
nsAutoString region;
|
||||
Preferences::GetString("browser.search.region", region);
|
||||
bool regionIsSupported = false;
|
||||
for (const char* each : supportedRegions) {
|
||||
if (region.EqualsASCII(each)) {
|
||||
regionIsSupported = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!regionIsSupported) {
|
||||
if (!manager->IsRegionSupported(region)) {
|
||||
return false;
|
||||
}
|
||||
nsAutoCString locale;
|
||||
|
@ -304,6 +304,43 @@ ConvertResponseData(const IPCPaymentResponseData& aIPCData,
|
||||
/* PaymentRequestManager */
|
||||
|
||||
StaticRefPtr<PaymentRequestManager> gPaymentManager;
|
||||
const char kSupportedRegionsPref[] = "dom.payments.request.supportedRegions";
|
||||
|
||||
void
|
||||
SupportedRegionsPrefChangedCallback(const char* aPrefName, nsTArray<nsString>* aRetval)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!strcmp(aPrefName, kSupportedRegionsPref));
|
||||
|
||||
nsAutoString supportedRegions;
|
||||
Preferences::GetString(aPrefName, supportedRegions);
|
||||
aRetval->Clear();
|
||||
for (const nsAString& each : supportedRegions.Split(',')) {
|
||||
aRetval->AppendElement(each);
|
||||
}
|
||||
}
|
||||
|
||||
PaymentRequestManager::PaymentRequestManager()
|
||||
{
|
||||
Preferences::RegisterCallbackAndCall(SupportedRegionsPrefChangedCallback,
|
||||
kSupportedRegionsPref,
|
||||
&this->mSupportedRegions);
|
||||
}
|
||||
|
||||
PaymentRequestManager::~PaymentRequestManager()
|
||||
{
|
||||
MOZ_ASSERT(mActivePayments.Count() == 0);
|
||||
Preferences::UnregisterCallback(SupportedRegionsPrefChangedCallback,
|
||||
kSupportedRegionsPref,
|
||||
&this->mSupportedRegions);
|
||||
mSupportedRegions.Clear();
|
||||
}
|
||||
|
||||
bool
|
||||
PaymentRequestManager::IsRegionSupported(const nsAString& region) const
|
||||
{
|
||||
return mSupportedRegions.Contains(region);
|
||||
}
|
||||
|
||||
PaymentRequestChild*
|
||||
PaymentRequestManager::GetPaymentChild(PaymentRequest* aRequest)
|
||||
|
@ -74,16 +74,15 @@ public:
|
||||
const nsAString& aPayerEmail,
|
||||
const nsAString& aPayerPhone);
|
||||
|
||||
bool IsRegionSupported(const nsAString& region) const;
|
||||
|
||||
// Called to ensure that we don't "leak" aRequest if we shut down while it had
|
||||
// an active request to the parent.
|
||||
void RequestIPCOver(PaymentRequest* aRequest);
|
||||
|
||||
private:
|
||||
PaymentRequestManager() = default;
|
||||
~PaymentRequestManager()
|
||||
{
|
||||
MOZ_ASSERT(mActivePayments.Count() == 0);
|
||||
}
|
||||
PaymentRequestManager();
|
||||
~PaymentRequestManager();
|
||||
|
||||
PaymentRequestChild* GetPaymentChild(PaymentRequest* aRequest);
|
||||
|
||||
@ -95,6 +94,8 @@ private:
|
||||
|
||||
// Strong pointer to requests with ongoing IPC messages to the parent.
|
||||
nsDataHashtable<nsRefPtrHashKey<PaymentRequest>, uint32_t> mActivePayments;
|
||||
|
||||
nsTArray<nsString> mSupportedRegions;
|
||||
};
|
||||
|
||||
} // end of namespace dom
|
||||
|
@ -30,6 +30,7 @@ static FeatureMap sSupportedFeatures[] = {
|
||||
{ "microphone", FeaturePolicyUtils::FeaturePolicyValue::eSelf },
|
||||
{ "midi", FeaturePolicyUtils::FeaturePolicyValue::eSelf },
|
||||
{ "payment", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "document-domain", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
// TODO: not supported yet!!!
|
||||
{ "speaker", FeaturePolicyUtils::FeaturePolicyValue::eSelf },
|
||||
{ "vr", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
|
@ -54,6 +54,6 @@ support-files =
|
||||
|
||||
[test_richtext2.html]
|
||||
subsuite = clipboard
|
||||
skip-if = os == 'android' && debug # Bug 1202045
|
||||
skip-if = os == 'android' # Bug 1202045
|
||||
[test_richtext.html]
|
||||
|
||||
|
@ -16,6 +16,7 @@ function GetPermissionsFile(profile)
|
||||
add_task(async function test() {
|
||||
/* Create and set up the permissions database */
|
||||
let profile = do_get_profile();
|
||||
Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
|
||||
|
||||
let db = Services.storage.openDatabase(GetPermissionsFile(profile));
|
||||
db.schemaVersion = 4;
|
||||
|
@ -45,6 +45,7 @@ function GetPermissionsFile(profile)
|
||||
add_task(function test() {
|
||||
/* Create and set up the permissions database */
|
||||
let profile = do_get_profile();
|
||||
Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
|
||||
|
||||
// Make sure that we can't resolve the nsINavHistoryService
|
||||
try {
|
||||
|
@ -16,6 +16,7 @@ function GetPermissionsFile(profile)
|
||||
add_task(async function test() {
|
||||
/* Create and set up the permissions database */
|
||||
let profile = do_get_profile();
|
||||
Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
|
||||
|
||||
let db = Services.storage.openDatabase(GetPermissionsFile(profile));
|
||||
db.schemaVersion = 5;
|
||||
|
@ -16,6 +16,7 @@ function GetPermissionsFile(profile)
|
||||
add_task(function test() {
|
||||
/* Create and set up the permissions database */
|
||||
let profile = do_get_profile();
|
||||
Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
|
||||
|
||||
let db = Services.storage.openDatabase(GetPermissionsFile(profile));
|
||||
db.schemaVersion = 5;
|
||||
|
@ -16,6 +16,7 @@ function GetPermissionsFile(profile)
|
||||
add_task(async function test() {
|
||||
/* Create and set up the permissions database */
|
||||
let profile = do_get_profile();
|
||||
Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
|
||||
|
||||
let db = Services.storage.openDatabase(GetPermissionsFile(profile));
|
||||
db.schemaVersion = 6;
|
||||
|
@ -16,6 +16,7 @@ function GetPermissionsFile(profile)
|
||||
add_task(function test() {
|
||||
/* Create and set up the permissions database */
|
||||
let profile = do_get_profile();
|
||||
Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
|
||||
|
||||
let db = Services.storage.openDatabase(GetPermissionsFile(profile));
|
||||
db.schemaVersion = 6;
|
||||
|
@ -16,6 +16,7 @@ function GetPermissionsFile(profile)
|
||||
add_task(async function test() {
|
||||
/* Create and set up the permissions database */
|
||||
let profile = do_get_profile();
|
||||
Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
|
||||
|
||||
let db = Services.storage.openDatabase(GetPermissionsFile(profile));
|
||||
db.schemaVersion = 7;
|
||||
|
@ -7,6 +7,7 @@
|
||||
var test_generator = do_run_test();
|
||||
|
||||
function run_test() {
|
||||
Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
|
||||
do_test_pending();
|
||||
test_generator.next();
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function run_test() {
|
||||
Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
|
||||
|
||||
// initialize the permission manager service
|
||||
let pm = Cc["@mozilla.org/permissionmanager;1"].
|
||||
getService(Ci.nsIPermissionManager);
|
||||
|
@ -276,12 +276,6 @@ BufferTextureData::GetFormat() const
|
||||
already_AddRefed<gfx::DrawTarget>
|
||||
BufferTextureData::BorrowDrawTarget()
|
||||
{
|
||||
if (mDrawTarget) {
|
||||
mDrawTarget->SetTransform(gfx::Matrix());
|
||||
RefPtr<gfx::DrawTarget> dt = mDrawTarget;
|
||||
return dt.forget();
|
||||
}
|
||||
|
||||
if (mDescriptor.type() != BufferDescriptor::TRGBDescriptor) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -289,37 +283,24 @@ BufferTextureData::BorrowDrawTarget()
|
||||
const RGBDescriptor& rgb = mDescriptor.get_RGBDescriptor();
|
||||
|
||||
uint32_t stride = ImageDataSerializer::GetRGBStride(rgb);
|
||||
RefPtr<gfx::DrawTarget> dt;
|
||||
if (gfx::Factory::DoesBackendSupportDataDrawtarget(mMoz2DBackend)) {
|
||||
mDrawTarget = gfx::Factory::CreateDrawTargetForData(mMoz2DBackend,
|
||||
GetBuffer(), rgb.size(),
|
||||
stride, rgb.format(), true);
|
||||
} else {
|
||||
dt = gfx::Factory::CreateDrawTargetForData(mMoz2DBackend,
|
||||
GetBuffer(), rgb.size(),
|
||||
stride, rgb.format(), true);
|
||||
}
|
||||
if (!dt) {
|
||||
// Fall back to supported platform backend. Note that mMoz2DBackend
|
||||
// does not match the draw target type.
|
||||
mDrawTarget = gfxPlatform::CreateDrawTargetForData(GetBuffer(), rgb.size(),
|
||||
stride, rgb.format(),
|
||||
true);
|
||||
dt = gfxPlatform::CreateDrawTargetForData(GetBuffer(), rgb.size(),
|
||||
stride, rgb.format(),
|
||||
true);
|
||||
}
|
||||
|
||||
if (mDrawTarget) {
|
||||
RefPtr<gfx::DrawTarget> dt = mDrawTarget;
|
||||
return dt.forget();
|
||||
}
|
||||
|
||||
// TODO - should we warn? should we really fallback to cairo? perhaps
|
||||
// at least update mMoz2DBackend...
|
||||
if (mMoz2DBackend != gfx::BackendType::CAIRO) {
|
||||
gfxCriticalNote << "Falling to CAIRO from " << (int)mMoz2DBackend;
|
||||
mDrawTarget = gfx::Factory::CreateDrawTargetForData(gfx::BackendType::CAIRO,
|
||||
GetBuffer(), rgb.size(),
|
||||
stride, rgb.format(), true);
|
||||
}
|
||||
|
||||
if (!mDrawTarget) {
|
||||
if (!dt) {
|
||||
gfxCriticalNote << "BorrowDrawTarget failure, original backend " << (int)mMoz2DBackend;
|
||||
}
|
||||
|
||||
RefPtr<gfx::DrawTarget> dt = mDrawTarget;
|
||||
return dt.forget();
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,6 @@ protected:
|
||||
, mMoz2DBackend(aMoz2DBackend)
|
||||
{}
|
||||
|
||||
RefPtr<gfx::DrawTarget> mDrawTarget;
|
||||
BufferDescriptor mDescriptor;
|
||||
gfx::BackendType mMoz2DBackend;
|
||||
};
|
||||
|
@ -76,7 +76,7 @@ AnimationSurfaceProvider::Reset()
|
||||
{
|
||||
// We want to go back to the beginning.
|
||||
bool mayDiscard;
|
||||
bool restartDecoder;
|
||||
bool restartDecoder = false;
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
@ -100,12 +100,23 @@ AnimationSurfaceProvider::Reset()
|
||||
// this should not take too long to acquire.
|
||||
MutexAutoLock lock(mDecodingMutex);
|
||||
|
||||
// Recreate the decoder so we can regenerate the frames again.
|
||||
mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder);
|
||||
MOZ_ASSERT(mDecoder);
|
||||
// We may have hit an error while redecoding. Because FrameAnimator is
|
||||
// tightly coupled to our own state, that means we would need to go through
|
||||
// some heroics to resume animating in those cases. The typical reason for
|
||||
// a redecode to fail is out of memory, and recycling should prevent most of
|
||||
// those errors. When image.animated.generate-full-frames has shipped
|
||||
// enabled on a release or two, we can simply remove the old FrameAnimator
|
||||
// blending code and simplify this quite a bit -- just always pop the next
|
||||
// full frame and timeout off the stack.
|
||||
if (mDecoder) {
|
||||
mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder);
|
||||
MOZ_ASSERT(mDecoder);
|
||||
|
||||
MutexAutoLock lock2(mFramesMutex);
|
||||
restartDecoder = mFrames->Reset();
|
||||
MutexAutoLock lock2(mFramesMutex);
|
||||
restartDecoder = mFrames->Reset();
|
||||
} else {
|
||||
MOZ_ASSERT(mFrames->HasRedecodeError());
|
||||
}
|
||||
}
|
||||
|
||||
if (restartDecoder) {
|
||||
|
@ -696,9 +696,8 @@ WasmBulkMemSupported(JSContext* cx, unsigned argc, Value* vp)
|
||||
}
|
||||
|
||||
static bool
|
||||
WasmGcEnabled(JSContext* cx, unsigned argc, Value* vp)
|
||||
TestGCEnabled(JSContext* cx)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
#ifdef ENABLE_WASM_GC
|
||||
bool isSupported = cx->options().wasmBaseline() && cx->options().wasmGc();
|
||||
# ifdef ENABLE_WASM_CRANELIFT
|
||||
@ -706,6 +705,28 @@ WasmGcEnabled(JSContext* cx, unsigned argc, Value* vp)
|
||||
isSupported = false;
|
||||
}
|
||||
# endif
|
||||
return isSupported;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool
|
||||
WasmGcEnabled(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
args.rval().setBoolean(TestGCEnabled(cx));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
WasmGeneralizedTables(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
// Generalized tables depend on anyref, though not currently on (ref T)
|
||||
// types nor on structures or other GC-proposal features.
|
||||
bool isSupported = TestGCEnabled(cx);
|
||||
#else
|
||||
bool isSupported = false;
|
||||
#endif
|
||||
@ -6144,6 +6165,12 @@ gc::ZealModeHelpText),
|
||||
"wasmGcEnabled(bool)",
|
||||
" Returns a boolean indicating whether the WebAssembly GC support is enabled."),
|
||||
|
||||
JS_FN_HELP("wasmGeneralizedTables", WasmGeneralizedTables, 1, 0,
|
||||
"wasmGeneralizedTables(bool)",
|
||||
" Returns a boolean indicating whether generalized tables are available.\n"
|
||||
" This feature set includes 'anyref' as a table type, and new instructions\n"
|
||||
" including table.get, table.set, table.grow, and table.size."),
|
||||
|
||||
JS_FN_HELP("isLazyFunction", IsLazyFunction, 1, 0,
|
||||
"isLazyFunction(fun)",
|
||||
" True if fun is a lazy JSFunction."),
|
||||
|
63
js/src/jit-test/tests/ion/merge-phi-usage-analysis.js
Normal file
63
js/src/jit-test/tests/ion/merge-phi-usage-analysis.js
Normal file
@ -0,0 +1,63 @@
|
||||
|
||||
function expensive() {
|
||||
with({}) {}
|
||||
}
|
||||
|
||||
function phi_merge_0(i) {
|
||||
// These computations can overflow, if the output is not truncated.
|
||||
i = i | 0;
|
||||
var a0 = i + i;
|
||||
var a1 = i + i;
|
||||
|
||||
if ((a1 | 0) - ((2 * i) | 0)) {
|
||||
// Good candidate for branch pruning, which marks only a1 as having
|
||||
// removed uses.
|
||||
expensive();
|
||||
expensive();
|
||||
expensive();
|
||||
expensive();
|
||||
expensive();
|
||||
}
|
||||
|
||||
// Simple branch made to let GVN merge the Phi instructions.
|
||||
if (a1 % 3 == 1) {
|
||||
a1 = 2 * i;
|
||||
a0 = 2 * i;
|
||||
}
|
||||
|
||||
// a0 is never used, but a1 is truncated.
|
||||
return a1 | 0;
|
||||
}
|
||||
|
||||
function phi_merge_1(i) {
|
||||
// These computations can overflow, if the output is not truncated.
|
||||
i = i | 0;
|
||||
var a1 = i + i;
|
||||
var a0 = i + i;
|
||||
|
||||
if ((a1 | 0) - ((2 * i) | 0)) {
|
||||
// Good candidate for branch pruning, which marks only a1 as having
|
||||
// removed uses.
|
||||
expensive();
|
||||
expensive();
|
||||
expensive();
|
||||
expensive();
|
||||
expensive();
|
||||
}
|
||||
|
||||
// Simple branch made to let GVN merge the Phi instructions.
|
||||
if (a1 % 3 == 1) {
|
||||
a1 = 2 * i;
|
||||
a0 = 2 * i;
|
||||
}
|
||||
|
||||
// a0 is never used, but a1 is truncated.
|
||||
return a1 | 0;
|
||||
}
|
||||
|
||||
for (var j = 0; j < 300; j++) {
|
||||
for (var i = 1; i == (i | 0); i = 2 * i + 1) {
|
||||
assertEq(phi_merge_0(i) < 0x80000000, true);
|
||||
assertEq(phi_merge_1(i) < 0x80000000, true);
|
||||
}
|
||||
}
|
@ -55,9 +55,10 @@ for (let type of ['i32', 'i64']) {
|
||||
assertEq(valText(text(UNSHARED)), false);
|
||||
}
|
||||
|
||||
{
|
||||
// 'wake' remains a backwards-compatible alias for 'notify'
|
||||
for ( let notify of ['wake', 'notify']) {
|
||||
let text = (shared) => `(module (memory 1 1 ${shared})
|
||||
(func (result i32) (atomic.wake (i32.const 0) (i32.const 1)))
|
||||
(func (result i32) (atomic.${notify} (i32.const 0) (i32.const 1)))
|
||||
(export "" 0))`;
|
||||
assertEq(valText(text(SHARED)), true);
|
||||
assertEq(valText(text(UNSHARED)), false);
|
||||
@ -74,11 +75,11 @@ for (let [type,align,good] of [['i32',1,false],['i32',2,false],['i32',4,true],['
|
||||
assertEq(valText(text), good);
|
||||
}
|
||||
|
||||
// Required explicit alignment for WAKE is 4
|
||||
// Required explicit alignment for NOTIFY is 4
|
||||
|
||||
for (let align of [1, 2, 4, 8]) {
|
||||
let text = `(module (memory 1 1 shared)
|
||||
(func (result i32) (atomic.wake align=${align} (i32.const 0) (i32.const 1)))
|
||||
(func (result i32) (atomic.notify align=${align} (i32.const 0) (i32.const 1)))
|
||||
(export "" 0))`;
|
||||
assertEq(valText(text), align == 4);
|
||||
}
|
||||
@ -466,7 +467,7 @@ var BoundsAndAlignment =
|
||||
|
||||
BoundsAndAlignment.run();
|
||||
|
||||
// Bounds and alignment checks on wait and wake
|
||||
// Bounds and alignment checks on wait and notify
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 shared)
|
||||
(func (param i32) (result i32)
|
||||
@ -494,15 +495,15 @@ assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 shared)
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 shared)
|
||||
(func (param i32) (result i32)
|
||||
(atomic.wake (get_local 0) (i32.const 1)))
|
||||
(atomic.notify (get_local 0) (i32.const 1)))
|
||||
(export "" 0))`).exports[""](65536),
|
||||
RuntimeError, oob);
|
||||
|
||||
// Minimum run-time alignment for WAKE is 4
|
||||
// Minimum run-time alignment for NOTIFY is 4
|
||||
for (let addr of [1,2,3,5,6,7]) {
|
||||
assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 shared)
|
||||
(func (export "f") (param i32) (result i32)
|
||||
(atomic.wake (get_local 0) (i32.const 1))))`).exports.f(addr),
|
||||
(atomic.notify (get_local 0) (i32.const 1))))`).exports.f(addr),
|
||||
RuntimeError, unaligned);
|
||||
}
|
||||
|
||||
|
@ -115,20 +115,7 @@ assertErrorMessage(() => wasmEval(moduleWithSections([v2vSigSection, declSection
|
||||
wasmEval(moduleWithSections([v2vSigSection, declSection([0,0,0]), tableSection(4), elemSection([{offset:0, elems:[0,1,0,2]}]), bodySection([v2vBody, v2vBody, v2vBody])]));
|
||||
wasmEval(moduleWithSections([sigSection([v2vSig,i2vSig]), declSection([0,0,1]), tableSection(3), elemSection([{offset:0,elems:[0,1,2]}]), bodySection([v2vBody, v2vBody, v2vBody])]));
|
||||
|
||||
function invalidTableSection2() {
|
||||
var body = [];
|
||||
body.push(...varU32(2)); // number of tables
|
||||
body.push(...varU32(AnyFuncCode));
|
||||
body.push(...varU32(0x0));
|
||||
body.push(...varU32(0));
|
||||
body.push(...varU32(AnyFuncCode));
|
||||
body.push(...varU32(0x0));
|
||||
body.push(...varU32(0));
|
||||
return { name: tableId, body };
|
||||
}
|
||||
|
||||
wasmEval(moduleWithSections([tableSection0()]));
|
||||
assertErrorMessage(() => wasmEval(moduleWithSections([invalidTableSection2()])), CompileError, /number of tables must be at most one/);
|
||||
|
||||
wasmEval(moduleWithSections([memorySection(0)]));
|
||||
|
||||
@ -282,6 +269,8 @@ var reservedMisc =
|
||||
0x00: true, 0x01: true, 0x02: true, 0x03: true, 0x04: true, 0x05: true, 0x06: true, 0x07: true,
|
||||
// Bulk memory (proposed)
|
||||
0x08: true, 0x09: true, 0x0a: true, 0x0b: true, 0x0c: true, 0x0d: true, 0x0e: true,
|
||||
// Table (proposed)
|
||||
0x0f: true, 0x10: true, 0x11: true, 0x12: true,
|
||||
// Structure operations (experimental, internal)
|
||||
0x50: true, 0x51: true, 0x52: true, 0x53: true };
|
||||
|
||||
|
@ -18,7 +18,11 @@ assertErrorMessage(() => new WebAssembly.Module(bad_order),
|
||||
WebAssembly.CompileError,
|
||||
/expected custom section/);
|
||||
|
||||
// Version numbers. Version 1 is good, version 2 is bad.
|
||||
// Version numbers. Version 1 and 2 are good, version 3 is bad.
|
||||
|
||||
new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 1))`));
|
||||
|
||||
new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
@ -26,7 +30,7 @@ new WebAssembly.Module(wasmTextToBinary(
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 2))`)),
|
||||
(gc_feature_opt_in 3))`)),
|
||||
WebAssembly.CompileError,
|
||||
/unsupported version of the gc feature/);
|
||||
|
||||
|
@ -0,0 +1,6 @@
|
||||
// |jit-test| skip-if: wasmGeneralizedTables()
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Table({element:"anyref", initial:10}),
|
||||
TypeError,
|
||||
/"element" property of table descriptor must be "anyfunc"/);
|
||||
|
439
js/src/jit-test/tests/wasm/gc/tables-generalized.js
Normal file
439
js/src/jit-test/tests/wasm/gc/tables-generalized.js
Normal file
@ -0,0 +1,439 @@
|
||||
// |jit-test| skip-if: !wasmGeneralizedTables()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// General table management in wasm
|
||||
|
||||
// Wasm: Create table-of-anyref
|
||||
|
||||
new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 1)
|
||||
(table 10 anyref))`));
|
||||
|
||||
// Wasm: Import table-of-anyref
|
||||
// JS: create table-of-anyref
|
||||
|
||||
new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 1)
|
||||
(table (import "m" "t") 10 anyref))`)),
|
||||
{m:{t: new WebAssembly.Table({element:"anyref", initial:10})}});
|
||||
|
||||
new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 1)
|
||||
(import "m" "t" (table 10 anyref)))`)),
|
||||
{m:{t: new WebAssembly.Table({element:"anyref", initial:10})}});
|
||||
|
||||
// Wasm: Export table-of-anyref, initial values shall be null
|
||||
|
||||
{
|
||||
let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 1)
|
||||
(table (export "t") 10 anyref))`)));
|
||||
let t = ins.exports.t;
|
||||
assertEq(t.length, 10);
|
||||
for (let i=0; i < t.length; i++)
|
||||
assertEq(t.get(0), null);
|
||||
}
|
||||
|
||||
// JS: Exported table can be grown, and values are preserved
|
||||
|
||||
{
|
||||
let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 1)
|
||||
(table (export "t") 10 anyref))`)));
|
||||
let t = ins.exports.t;
|
||||
let objs = [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}];
|
||||
for (let i in objs)
|
||||
t.set(i, objs[i]);
|
||||
ins.exports.t.grow(10);
|
||||
assertEq(ins.exports.t.length, 20);
|
||||
for (let i in objs)
|
||||
assertEq(t.get(i), objs[i]);
|
||||
}
|
||||
|
||||
// Wasm: table.copy between tables of anyref (currently source and destination
|
||||
// are both table zero)
|
||||
|
||||
{
|
||||
let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 1)
|
||||
(table (export "t") 10 anyref)
|
||||
(func (export "f")
|
||||
(table.copy (i32.const 5) (i32.const 0) (i32.const 3))))`)));
|
||||
let t = ins.exports.t;
|
||||
let objs = [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}];
|
||||
for (let i in objs)
|
||||
t.set(i, objs[i]);
|
||||
ins.exports.f();
|
||||
assertEq(t.get(0), objs[0]);
|
||||
assertEq(t.get(1), objs[1]);
|
||||
assertEq(t.get(2), objs[2]);
|
||||
assertEq(t.get(3), objs[3]);
|
||||
assertEq(t.get(4), objs[4]);
|
||||
assertEq(t.get(5), objs[0]);
|
||||
assertEq(t.get(6), objs[1]);
|
||||
assertEq(t.get(7), objs[2]);
|
||||
assertEq(t.get(8), objs[8]);
|
||||
assertEq(t.get(9), objs[9]);
|
||||
}
|
||||
|
||||
// Wasm: element segments targeting table-of-anyref is forbidden
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 1)
|
||||
(func $f1 (result i32) (i32.const 0))
|
||||
(table 10 anyref)
|
||||
(elem (i32.const 0) $f1))`)),
|
||||
WebAssembly.CompileError,
|
||||
/only tables of 'anyfunc' may have element segments/);
|
||||
|
||||
// Wasm: table.init on table-of-anyref is forbidden
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 1)
|
||||
(func $f1 (result i32) (i32.const 0))
|
||||
(table 10 anyref)
|
||||
(elem passive $f1)
|
||||
(func
|
||||
(table.init 0 (i32.const 0) (i32.const 0) (i32.const 0))))`)),
|
||||
WebAssembly.CompileError,
|
||||
/only tables of 'anyfunc' may have element segments/);
|
||||
|
||||
// Wasm: table types must match at link time
|
||||
|
||||
assertErrorMessage(
|
||||
() => new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 1)
|
||||
(import "m" "t" (table 10 anyref)))`)),
|
||||
{m:{t: new WebAssembly.Table({element:"anyfunc", initial:10})}}),
|
||||
WebAssembly.LinkError,
|
||||
/imported table type mismatch/);
|
||||
|
||||
// call_indirect cannot reference table-of-anyref
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 1)
|
||||
(table 10 anyref)
|
||||
(type $t (func (param i32) (result i32)))
|
||||
(func (result i32)
|
||||
(call_indirect $t (i32.const 37))))`)),
|
||||
WebAssembly.CompileError,
|
||||
/indirect calls must go through a table of 'anyfunc'/);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// additional js api tests
|
||||
|
||||
{
|
||||
let tbl = new WebAssembly.Table({element:"anyref", initial:10});
|
||||
|
||||
// Initial value.
|
||||
assertEq(tbl.get(0), null);
|
||||
|
||||
// Identity preserving.
|
||||
let x = {hi: 48};
|
||||
tbl.set(0, x);
|
||||
assertEq(tbl.get(0), x);
|
||||
tbl.set(2, dummy);
|
||||
assertEq(tbl.get(2), dummy);
|
||||
tbl.set(2, null);
|
||||
assertEq(tbl.get(2), null);
|
||||
|
||||
// Temporary semantics is to convert to object and leave as object; once we
|
||||
// have a better wrapped anyref this will change, we won't be able to
|
||||
// observe the boxing.
|
||||
tbl.set(1, 42);
|
||||
let y = tbl.get(1);
|
||||
assertEq(typeof y, "object");
|
||||
assertEq(y instanceof Number, true);
|
||||
assertEq(y + 0, 42);
|
||||
|
||||
// Temporary semantics is to throw on undefined
|
||||
assertErrorMessage(() => tbl.set(0, undefined),
|
||||
TypeError,
|
||||
/can't convert undefined to object/);
|
||||
}
|
||||
|
||||
function dummy() { return 37 }
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// table.get and table.set
|
||||
|
||||
// table.get in bounds - returns right value type & value
|
||||
// table.get out of bounds - fails
|
||||
|
||||
{
|
||||
let ins = wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table (export "t") 10 anyref)
|
||||
(func (export "f") (param i32) (result anyref)
|
||||
(table.get (get_local 0))))`);
|
||||
let x = {};
|
||||
ins.exports.t.set(0, x);
|
||||
assertEq(ins.exports.f(0), x);
|
||||
assertEq(ins.exports.f(1), null);
|
||||
assertErrorMessage(() => ins.exports.f(10), RangeError, /index out of bounds/);
|
||||
assertErrorMessage(() => ins.exports.f(-5), RangeError, /index out of bounds/);
|
||||
}
|
||||
|
||||
// table.get with non-i32 index - fails validation
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table 10 anyref)
|
||||
(func (export "f") (param f64) (result anyref)
|
||||
(table.get (get_local 0))))`)),
|
||||
WebAssembly.CompileError,
|
||||
/type mismatch/);
|
||||
|
||||
// table.get on table of anyfunc - fails validation because anyfunc is not expressible
|
||||
// Both with and without anyref support
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(table 10 anyfunc)
|
||||
(func (export "f") (param i32)
|
||||
(drop (table.get (get_local 0)))))`)),
|
||||
WebAssembly.CompileError,
|
||||
/table.get only on tables of anyref/);
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table 10 anyfunc)
|
||||
(func (export "f") (param i32)
|
||||
(drop (table.get (get_local 0)))))`)),
|
||||
WebAssembly.CompileError,
|
||||
/table.get only on tables of anyref/);
|
||||
|
||||
// table.get when there are no tables - fails validation
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(func (export "f") (param i32)
|
||||
(drop (table.get (get_local 0)))))`)),
|
||||
WebAssembly.CompileError,
|
||||
/table index out of range for table.get/);
|
||||
|
||||
// table.set in bounds with i32 x anyref - works, no value generated
|
||||
// table.set with (ref T) - works
|
||||
// table.set with null - works
|
||||
// table.set out of bounds - fails
|
||||
|
||||
{
|
||||
let ins = wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table (export "t") 10 anyref)
|
||||
(type $dummy (struct (field i32)))
|
||||
(func (export "set_anyref") (param i32) (param anyref)
|
||||
(table.set (get_local 0) (get_local 1)))
|
||||
(func (export "set_null") (param i32)
|
||||
(table.set (get_local 0) (ref.null anyref)))
|
||||
(func (export "set_ref") (param i32) (param anyref)
|
||||
(table.set (get_local 0) (struct.narrow anyref (ref $dummy) (get_local 1))))
|
||||
(func (export "make_struct") (result anyref)
|
||||
(struct.new $dummy (i32.const 37))))`);
|
||||
let x = {};
|
||||
ins.exports.set_anyref(3, x);
|
||||
assertEq(ins.exports.t.get(3), x);
|
||||
ins.exports.set_null(3);
|
||||
assertEq(ins.exports.t.get(3), null);
|
||||
let dummy = ins.exports.make_struct();
|
||||
ins.exports.set_ref(5, dummy);
|
||||
assertEq(ins.exports.t.get(5), dummy);
|
||||
|
||||
assertErrorMessage(() => ins.exports.set_anyref(10, x), RangeError, /index out of bounds/);
|
||||
assertErrorMessage(() => ins.exports.set_anyref(-1, x), RangeError, /index out of bounds/);
|
||||
}
|
||||
|
||||
// table.set with non-i32 index - fails validation
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table 10 anyref)
|
||||
(func (export "f") (param f64)
|
||||
(table.set (get_local 0) (ref.null anyref))))`)),
|
||||
WebAssembly.CompileError,
|
||||
/type mismatch/);
|
||||
|
||||
// table.set with non-anyref value - fails validation
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table 10 anyref)
|
||||
(func (export "f") (param f64)
|
||||
(table.set (i32.const 0) (get_local 0))))`)),
|
||||
WebAssembly.CompileError,
|
||||
/type mismatch/);
|
||||
|
||||
// table.set on table of anyfunc - fails validation
|
||||
// We need the gc_feature_opt_in here because of the anyref parameter; if we change
|
||||
// that to some other type, it's the validation of that type that fails us.
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table 10 anyfunc)
|
||||
(func (export "f") (param anyref)
|
||||
(table.set (i32.const 0) (get_local 0))))`)),
|
||||
WebAssembly.CompileError,
|
||||
/table.set only on tables of anyref/);
|
||||
|
||||
// table.set when there are no tables - fails validation
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(func (export "f") (param anyref)
|
||||
(table.set (i32.const 0) (get_local 0))))`)),
|
||||
WebAssembly.CompileError,
|
||||
/table index out of range for table.set/);
|
||||
|
||||
// we can grow table of anyref
|
||||
// table.grow with zero delta - always works even at maximum
|
||||
// table.grow with delta - works and returns correct old value
|
||||
// table.grow with delta at upper limit - fails
|
||||
// table.grow with negative delta - fails
|
||||
|
||||
let ins = wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table (export "t") 10 20 anyref)
|
||||
(func (export "grow") (param i32) (result i32)
|
||||
(table.grow (get_local 0) (ref.null anyref))))`);
|
||||
assertEq(ins.exports.grow(0), 10);
|
||||
assertEq(ins.exports.t.length, 10);
|
||||
assertEq(ins.exports.grow(1), 10);
|
||||
assertEq(ins.exports.t.length, 11);
|
||||
assertEq(ins.exports.t.get(10), null);
|
||||
assertEq(ins.exports.grow(9), 11);
|
||||
assertEq(ins.exports.t.length, 20);
|
||||
assertEq(ins.exports.t.get(19), null);
|
||||
assertEq(ins.exports.grow(0), 20);
|
||||
|
||||
// The JS API throws if it can't grow
|
||||
assertErrorMessage(() => ins.exports.t.grow(1), RangeError, /failed to grow table/);
|
||||
assertErrorMessage(() => ins.exports.t.grow(-1), TypeError, /bad [Tt]able grow delta/);
|
||||
|
||||
// The wasm API does not throw if it can't grow, but returns -1
|
||||
assertEq(ins.exports.grow(1), -1);
|
||||
assertEq(ins.exports.t.length, 20);
|
||||
assertEq(ins.exports.grow(-1), -1);
|
||||
assertEq(ins.exports.t.length, 20)
|
||||
|
||||
// Special case for private tables without a maximum
|
||||
|
||||
{
|
||||
let ins = wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table 10 anyref)
|
||||
(func (export "grow") (param i32) (result i32)
|
||||
(table.grow (get_local 0) (ref.null anyref))))`);
|
||||
assertEq(ins.exports.grow(0), 10);
|
||||
assertEq(ins.exports.grow(1), 10);
|
||||
assertEq(ins.exports.grow(9), 11);
|
||||
assertEq(ins.exports.grow(0), 20);
|
||||
}
|
||||
|
||||
// Can't grow table of anyfunc yet
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2) ;; Required because of the 'anyref' null value below
|
||||
(table $t 2 anyfunc)
|
||||
(func $f
|
||||
(drop (table.grow (i32.const 1) (ref.null anyref)))))`),
|
||||
WebAssembly.CompileError,
|
||||
/table.grow only on tables of anyref/);
|
||||
|
||||
// table.grow with non-i32 argument - fails validation
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table 10 anyref)
|
||||
(func (export "f") (param f64)
|
||||
(table.grow (get_local 0) (ref.null anyref))))`)),
|
||||
WebAssembly.CompileError,
|
||||
/type mismatch/);
|
||||
|
||||
// table.grow when there are no tables - fails validation
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(func (export "f") (param i32)
|
||||
(table.grow (get_local 0) (ref.null anyref))))`)),
|
||||
WebAssembly.CompileError,
|
||||
/table index out of range for table.grow/);
|
||||
|
||||
// table.grow on table of anyref with non-null ref value
|
||||
|
||||
{
|
||||
let ins = wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(type $S (struct (field i32) (field f64)))
|
||||
(table (export "t") 2 anyref)
|
||||
(func (export "f") (result i32)
|
||||
(table.grow (i32.const 1) (struct.new $S (i32.const 0) (f64.const 3.14)))))`);
|
||||
assertEq(ins.exports.t.length, 2);
|
||||
assertEq(ins.exports.f(), 2);
|
||||
assertEq(ins.exports.t.length, 3);
|
||||
assertEq(typeof ins.exports.t.get(2), "object");
|
||||
}
|
||||
|
||||
// table.size on table of anyref
|
||||
|
||||
for (let visibility of ['', '(export "t")', '(import "m" "t")']) {
|
||||
let exp = {m:{t: new WebAssembly.Table({element:"anyref",
|
||||
initial: 10,
|
||||
maximum: 20})}};
|
||||
let ins = wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table ${visibility} 10 20 anyref)
|
||||
(func (export "grow") (param i32) (result i32)
|
||||
(table.grow (get_local 0) (ref.null anyref)))
|
||||
(func (export "size") (result i32)
|
||||
(table.size)))`,
|
||||
exp);
|
||||
assertEq(ins.exports.grow(0), 10);
|
||||
assertEq(ins.exports.size(), 10);
|
||||
assertEq(ins.exports.grow(1), 10);
|
||||
assertEq(ins.exports.size(), 11);
|
||||
assertEq(ins.exports.grow(9), 11);
|
||||
assertEq(ins.exports.size(), 20);
|
||||
assertEq(ins.exports.grow(0), 20);
|
||||
assertEq(ins.exports.size(), 20);
|
||||
}
|
||||
|
||||
// table.size on table of anyfunc
|
||||
|
||||
{
|
||||
let ins = wasmEvalText(
|
||||
`(module
|
||||
(table (export "t") 2 anyfunc)
|
||||
(func (export "f") (result i32)
|
||||
(table.size)))`);
|
||||
assertEq(ins.exports.f(), 2);
|
||||
ins.exports.t.grow(1);
|
||||
assertEq(ins.exports.f(), 3);
|
||||
}
|
454
js/src/jit-test/tests/wasm/gc/tables-multiple.js
Normal file
454
js/src/jit-test/tests/wasm/gc/tables-multiple.js
Normal file
@ -0,0 +1,454 @@
|
||||
// |jit-test| skip-if: !wasmGeneralizedTables()
|
||||
|
||||
// Note that negative tests not having to do with table indices have been taken
|
||||
// care of by tables-generalized.js.
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Positive tests
|
||||
|
||||
// - multiple local tables of misc type: syntax, validation, instantiation
|
||||
// - element segments can point to a table
|
||||
// - call-indirect can specify a table and will use it
|
||||
// - be sure to test table stuff w/o gc_feature_opt_in so that we get ion coverage too
|
||||
|
||||
var ins = wasmEvalText(
|
||||
`(module
|
||||
(table $t1 2 anyfunc)
|
||||
(table $t2 2 anyfunc)
|
||||
(type $ftype (func (param i32) (result i32)))
|
||||
(elem $t1 (i32.const 0) $f1 $f2)
|
||||
(elem $t2 (i32.const 0) $f3 $f4)
|
||||
(func $f1 (param $n i32) (result i32)
|
||||
(i32.add (get_local $n) (i32.const 1)))
|
||||
(func $f2 (param $n i32) (result i32)
|
||||
(i32.add (get_local $n) (i32.const 2)))
|
||||
(func $f3 (param $n i32) (result i32)
|
||||
(i32.add (get_local $n) (i32.const 3)))
|
||||
(func $f4 (param $n i32) (result i32)
|
||||
(i32.add (get_local $n) (i32.const 4)))
|
||||
(func (export "f") (param $fn i32) (param $n i32) (result i32)
|
||||
(call_indirect $t1 $ftype (get_local $n) (get_local $fn)))
|
||||
(func (export "g") (param $fn i32) (param $n i32) (result i32)
|
||||
(call_indirect $t2 $ftype (get_local $n) (get_local $fn))))`).exports;
|
||||
|
||||
assertEq(ins.f(0, 10), 11);
|
||||
assertEq(ins.f(1, 10), 12);
|
||||
assertEq(ins.g(0, 10), 13);
|
||||
assertEq(ins.g(1, 10), 14);
|
||||
|
||||
// - export multiple tables.
|
||||
// note the first and third tables make the export list not start at zero,
|
||||
// and make it sparse
|
||||
|
||||
var ins = wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table $t0 (import "m" "t") 2 anyfunc)
|
||||
(table $t1 (export "t1") 2 anyfunc)
|
||||
(table 1 anyref)
|
||||
(table $t2 (export "t2") 3 anyfunc))`,
|
||||
{m:{t: new WebAssembly.Table({element:"anyfunc", initial:2})}}).exports;
|
||||
|
||||
assertEq(ins.t1 instanceof WebAssembly.Table, true);
|
||||
assertEq(ins.t1.length, 2);
|
||||
assertEq(ins.t2 instanceof WebAssembly.Table, true);
|
||||
assertEq(ins.t2.length, 3);
|
||||
|
||||
// - multiple imported tables of misc type
|
||||
// - table.get and table.set can point to a table
|
||||
|
||||
var exp = {m:{t0: new WebAssembly.Table({element:"anyfunc", initial:2}),
|
||||
t1: new WebAssembly.Table({element:"anyref", initial:3}),
|
||||
t2: new WebAssembly.Table({element:"anyfunc", initial:4}),
|
||||
t3: new WebAssembly.Table({element:"anyref", initial:5})}};
|
||||
var ins = wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
|
||||
(table $t0 (import "m" "t0") 2 anyfunc)
|
||||
(type $id_i32_t (func (param i32) (result i32)))
|
||||
(func $id_i32 (param i32) (result i32) (get_local 0))
|
||||
(elem $t0 (i32.const 1) $id_i32)
|
||||
|
||||
(table $t1 (import "m" "t1") 3 anyref)
|
||||
|
||||
(table $t2 (import "m" "t2") 4 anyfunc)
|
||||
(type $id_f64_t (func (param f64) (result f64)))
|
||||
(func $id_f64 (param f64) (result f64) (get_local 0))
|
||||
(elem $t2 (i32.const 3) $id_f64)
|
||||
|
||||
(table $t3 (import "m" "t3") 5 anyref)
|
||||
|
||||
(func (export "f0") (param i32) (result i32)
|
||||
(call_indirect $t0 $id_i32_t (get_local 0) (i32.const 1)))
|
||||
|
||||
(func (export "f1") (param anyref)
|
||||
(table.set $t1 (i32.const 2) (get_local 0)))
|
||||
|
||||
(func (export "f2") (param f64) (result f64)
|
||||
(call_indirect $t2 $id_f64_t (get_local 0) (i32.const 3)))
|
||||
|
||||
(func (export "f3")
|
||||
(table.set $t3 (i32.const 4) (table.get $t1 (i32.const 2)))))`,
|
||||
|
||||
exp).exports;
|
||||
|
||||
assertEq(ins.f0(37), 37);
|
||||
|
||||
var x = {}
|
||||
ins.f1(x);
|
||||
assertEq(exp.m.t1.get(2), x);
|
||||
|
||||
assertEq(ins.f2(3.25), 3.25);
|
||||
|
||||
ins.f3();
|
||||
assertEq(exp.m.t3.get(4), x);
|
||||
|
||||
// - table.grow can point to a table
|
||||
// - growing a table grows the right table but not the others
|
||||
// - table.size on tables other than table 0
|
||||
|
||||
var exp = {m:{t0: new WebAssembly.Table({element:"anyref", initial:2}),
|
||||
t1: new WebAssembly.Table({element:"anyref", initial:3})}};
|
||||
var ins = wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
|
||||
(table $t0 (import "m" "t0") 2 anyref)
|
||||
(table $t1 (import "m" "t1") 3 anyref)
|
||||
(func (export "f") (result i32)
|
||||
(table.grow $t1 (i32.const 5) (ref.null anyref)))
|
||||
(func (export "size0") (result i32)
|
||||
(table.size $t0))
|
||||
(func (export "size1") (result i32)
|
||||
(table.size $t1)))`,
|
||||
exp);
|
||||
|
||||
assertEq(ins.exports.f(), 3);
|
||||
assertEq(exp.m.t1.length, 8);
|
||||
assertEq(ins.exports.size0(), 2);
|
||||
assertEq(ins.exports.size1(), 8);
|
||||
|
||||
// - table.copy can point to tables
|
||||
|
||||
var exp = {m:{t0: new WebAssembly.Table({element:"anyref", initial:2}),
|
||||
t1: new WebAssembly.Table({element:"anyref", initial:3})}};
|
||||
var ins = wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
|
||||
(table $t0 (import "m" "t0") 2 anyref)
|
||||
(table $t1 (import "m" "t1") 3 anyref)
|
||||
(func (export "f") (param $dest i32) (param $src i32) (param $len i32)
|
||||
(table.copy $t1 (get_local $dest) $t0 (get_local $src) (get_local $len))))`,
|
||||
exp);
|
||||
|
||||
exp.m.t0.set(0, {x:0});
|
||||
exp.m.t0.set(1, {x:1});
|
||||
ins.exports.f(1, 0, 2);
|
||||
assertEq(exp.m.t1.get(1), exp.m.t0.get(0));
|
||||
assertEq(exp.m.t1.get(2), exp.m.t0.get(1));
|
||||
|
||||
// - the table.copy syntax makes sense even in the non-parenthesized case
|
||||
|
||||
var ins = wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table $t0 2 anyref)
|
||||
(table $t1 2 anyref)
|
||||
(func (export "copy") (param $dest i32) (param $src i32) (param $len i32)
|
||||
get_local $dest
|
||||
get_local $src
|
||||
get_local $len
|
||||
table.copy $t1 $t0)
|
||||
(func (export "set") (param $n i32) (param $v anyref)
|
||||
(table.set $t0 (get_local $n) (get_local $v)))
|
||||
(func (export "get") (param $n i32) (result anyref)
|
||||
(table.get $t1 (get_local $n))))`,
|
||||
exp);
|
||||
|
||||
var values = [{x:0}, {x:1}];
|
||||
ins.exports.set(0, values[0]);
|
||||
ins.exports.set(1, values[1]);
|
||||
ins.exports.copy(0, 0, 2);
|
||||
assertEq(ins.exports.get(0), values[0]);
|
||||
assertEq(ins.exports.get(1), values[1]);
|
||||
|
||||
// Copy beween an external table and a local table. These cases are interesting
|
||||
// mostly because the representations of the tables must be compatible; the copy
|
||||
// in effect forces a representation for the function in the local table that
|
||||
// captures the function's instance, at the latest at the time the copy is
|
||||
// executed.
|
||||
//
|
||||
// In the case where the function is imported, it comes from a different module.
|
||||
//
|
||||
// Also tests:
|
||||
// - local tables can be exported and re-imported in another module
|
||||
// - table.copy between tables of anyfunc is possible without gc_feature_opt_in
|
||||
|
||||
var arg = 4;
|
||||
for (let [x,y,result,init] of [['(export "t")', '', arg*13, true],
|
||||
['', '(export "t")', arg*13, true],
|
||||
['(import "m" "t")', '', arg*13, true],
|
||||
['', '(import "m" "t")', arg-11, false]])
|
||||
{
|
||||
var otherins = wasmEvalText(
|
||||
`(module
|
||||
(table $t (export "t") 2 anyfunc)
|
||||
(type $fn1 (func (param i32) (result i32)))
|
||||
(func $f1 (param $n i32) (result i32)
|
||||
(i32.sub (get_local $n) (i32.const 11)))
|
||||
(elem $t (i32.const 1) $f1))`);
|
||||
|
||||
let text =
|
||||
`(module
|
||||
(table $t0 ${x} 2 anyfunc)
|
||||
|
||||
(table $t1 ${y} 2 anyfunc)
|
||||
(type $fn1 (func (param i32) (result i32)))
|
||||
(func $f1 (param $n i32) (result i32)
|
||||
(i32.mul (get_local $n) (i32.const 13)))
|
||||
${init ? "(elem $t1 (i32.const 1) $f1)" : ""}
|
||||
|
||||
(func (export "f") (param $n i32) (result i32)
|
||||
(table.copy $t0 (i32.const 0) $t1 (i32.const 0) (i32.const 2))
|
||||
(call_indirect $t0 $fn1 (get_local $n) (i32.const 1))))`;
|
||||
var ins = wasmEvalText(text, {m: otherins.exports});
|
||||
|
||||
assertEq(ins.exports.f(arg), result);
|
||||
}
|
||||
|
||||
// - a table can be imported multiple times, and when it is, and one of them is grown,
|
||||
// they are all grown.
|
||||
// - test the (import "m" "t" (table ...)) syntax
|
||||
// - if table is grown from JS, wasm can observe the growth
|
||||
|
||||
var tbl = new WebAssembly.Table({element:"anyref", initial:1});
|
||||
var exp = {m: {t0: tbl, t1:tbl}};
|
||||
|
||||
var ins = wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(import $t0 "m" "t0" (table 1 anyref))
|
||||
(import $t1 "m" "t1" (table 1 anyref))
|
||||
(table $t2 (export "t2") 1 anyfunc)
|
||||
(func (export "f") (result i32)
|
||||
(table.grow $t0 (i32.const 1) (ref.null anyref)))
|
||||
(func (export "g") (result i32)
|
||||
(table.grow $t1 (i32.const 1) (ref.null anyref)))
|
||||
(func (export "size") (result i32)
|
||||
(table.size $t2)))`,
|
||||
exp);
|
||||
|
||||
assertEq(ins.exports.f(), 1);
|
||||
assertEq(ins.exports.g(), 2);
|
||||
assertEq(ins.exports.f(), 3);
|
||||
assertEq(ins.exports.g(), 4);
|
||||
assertEq(tbl.length, 5);
|
||||
ins.exports.t2.grow(3);
|
||||
assertEq(ins.exports.size(), 4);
|
||||
|
||||
// - table.init on tables other than table 0
|
||||
// - gc_feature_opt_in not required for table.init on table(anyfunc) even with multiple tables
|
||||
|
||||
var ins = wasmEvalText(
|
||||
`(module
|
||||
(table $t0 2 anyfunc)
|
||||
(table $t1 2 anyfunc)
|
||||
(elem passive $f0 $f1) ;; 0
|
||||
(type $ftype (func (param i32) (result i32)))
|
||||
(func $f0 (param i32) (result i32)
|
||||
(i32.mul (get_local 0) (i32.const 13)))
|
||||
(func $f1 (param i32) (result i32)
|
||||
(i32.sub (get_local 0) (i32.const 11)))
|
||||
(func (export "call") (param i32) (param i32) (result i32)
|
||||
(call_indirect $t1 $ftype (get_local 1) (get_local 0)))
|
||||
(func (export "init")
|
||||
(table.init $t1 0 (i32.const 0) (i32.const 0) (i32.const 2))))`);
|
||||
|
||||
ins.exports.init();
|
||||
assertEq(ins.exports.call(0, 10), 130);
|
||||
assertEq(ins.exports.call(1, 10), -1);
|
||||
|
||||
// - [white-box] if a multi-imported table of anyfunc is grown and the grown
|
||||
// part is properly initialized with functions then calls through both tables
|
||||
// in the grown area should succeed, ie, bounds checks should pass. this is
|
||||
// an interesting case because we cache the table bounds for the benefit of
|
||||
// call_indirect, so here we're testing that the caches are updated properly
|
||||
// even when a table is observed multiple times (also by multiple modules).
|
||||
// there's some extra hair here because a table of anyfunc can be grown only
|
||||
// from JS at the moment.
|
||||
// - also test that bounds checking continues to catch OOB calls
|
||||
|
||||
var tbl = new WebAssembly.Table({element:"anyfunc", initial:2});
|
||||
var exp = {m:{t0: tbl, t1: tbl}};
|
||||
var ins = wasmEvalText(
|
||||
`(module
|
||||
(import "m" "t0" (table $t0 2 anyfunc))
|
||||
(import "m" "t1" (table $t1 2 anyfunc))
|
||||
(type $ftype (func (param f64) (result f64)))
|
||||
(func (export "f") (param $n f64) (result f64)
|
||||
(f64.mul (get_local $n) (f64.const 3.25)))
|
||||
(func (export "do0") (param $i i32) (param $n f64) (result f64)
|
||||
(call_indirect $t0 $ftype (get_local $n) (get_local $i)))
|
||||
(func (export "do1") (param $i i32) (param $n f64) (result f64)
|
||||
(call_indirect $t1 $ftype (get_local $n) (get_local $i))))`,
|
||||
exp);
|
||||
var ins2 = wasmEvalText(
|
||||
`(module
|
||||
(import "m" "t0" (table $t0 2 anyfunc))
|
||||
(import "m" "t1" (table $t1 2 anyfunc))
|
||||
(type $ftype (func (param f64) (result f64)))
|
||||
(func (export "do0") (param $i i32) (param $n f64) (result f64)
|
||||
(call_indirect $t0 $ftype (get_local $n) (get_local $i)))
|
||||
(func (export "do1") (param $i i32) (param $n f64) (result f64)
|
||||
(call_indirect $t1 $ftype (get_local $n) (get_local $i))))`,
|
||||
exp);
|
||||
|
||||
assertEq(tbl.grow(10), 2);
|
||||
tbl.set(11, ins.exports.f);
|
||||
assertEq(ins.exports.do0(11, 2.0), 6.5);
|
||||
assertEq(ins.exports.do1(11, 4.0), 13.0);
|
||||
assertEq(ins2.exports.do0(11, 2.0), 6.5);
|
||||
assertEq(ins2.exports.do1(11, 4.0), 13.0);
|
||||
assertErrorMessage(() => ins.exports.do0(12, 2.0),
|
||||
WebAssembly.RuntimeError,
|
||||
/index out of bounds/);
|
||||
assertErrorMessage(() => ins2.exports.do0(12, 2.0),
|
||||
WebAssembly.RuntimeError,
|
||||
/index out of bounds/);
|
||||
assertErrorMessage(() => ins.exports.do1(12, 2.0),
|
||||
WebAssembly.RuntimeError,
|
||||
/index out of bounds/);
|
||||
assertErrorMessage(() => ins2.exports.do1(12, 2.0),
|
||||
WebAssembly.RuntimeError,
|
||||
/index out of bounds/);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Negative tests
|
||||
|
||||
// Table index (statically) out of bounds
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table $t0 2 anyref)
|
||||
(table $t1 2 anyref)
|
||||
(func $f (result anyref)
|
||||
(table.get 2 (i32.const 0))))`),
|
||||
WebAssembly.CompileError,
|
||||
/table index out of range for table.get/);
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table $t0 2 anyref)
|
||||
(table $t1 2 anyref)
|
||||
(func $f (param anyref)
|
||||
(table.set 2 (i32.const 0) (get_local 0))))`),
|
||||
WebAssembly.CompileError,
|
||||
/table index out of range for table.set/);
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table $t0 2 anyref)
|
||||
(table $t1 2 anyref)
|
||||
(func $f (param anyref)
|
||||
(table.copy 0 (i32.const 0) 2 (i32.const 0) (i32.const 2))))`),
|
||||
WebAssembly.CompileError,
|
||||
/table index out of range for table.copy/);
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table $t0 2 anyref)
|
||||
(table $t1 2 anyref)
|
||||
(func $f (param anyref)
|
||||
(table.copy 2 (i32.const 0) 0 (i32.const 0) (i32.const 2))))`),
|
||||
WebAssembly.CompileError,
|
||||
/table index out of range for table.copy/);
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table $t0 2 anyref)
|
||||
(table $t1 2 anyref)
|
||||
(func $f (result i32)
|
||||
(table.size 2)))`),
|
||||
WebAssembly.CompileError,
|
||||
/table index out of range for table.size/);
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
(table $t0 2 anyref)
|
||||
(table $t1 2 anyref)
|
||||
(func $f (result i32)
|
||||
(table.grow 2 (i32.const 1) (ref.null anyref))))`),
|
||||
WebAssembly.CompileError,
|
||||
/table index out of range for table.grow/);
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(
|
||||
`(module
|
||||
(table $t0 2 anyfunc)
|
||||
(elem passive) ;; 0
|
||||
(func $f (result i32)
|
||||
(table.init 2 0 (i32.const 0) (i32.const 0) (i32.const 0))))`),
|
||||
WebAssembly.CompileError,
|
||||
/table index out of range for table.init/);
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(
|
||||
`(module
|
||||
(table $t0 2 anyfunc)
|
||||
(elem 2 (i32.const 0)))`),
|
||||
WebAssembly.CompileError,
|
||||
/table index out of range for element segment/);
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(
|
||||
`(module
|
||||
(table $t0 2 anyfunc)
|
||||
(type $ft (func (param f64) (result i32)))
|
||||
(func $f (result i32)
|
||||
(call_indirect 2 $ft (f64.const 3.14) (i32.const 0))))`),
|
||||
WebAssembly.CompileError,
|
||||
/table index out of range for call_indirect/);
|
||||
|
||||
// Syntax errors when parsing text
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(
|
||||
`(module
|
||||
(table $t0 2 anyfunc)
|
||||
(elem 0 passive (i32.const 0)))`),
|
||||
SyntaxError,
|
||||
/passive segment must not have a table/);
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(
|
||||
`(module
|
||||
(table $t0 2 anyfunc)
|
||||
(elem passive) ;; 0
|
||||
(func $f (result i32)
|
||||
(table.init $t0 (i32.const 0) (i32.const 0) (i32.const 0))))`), // no segment
|
||||
SyntaxError,
|
||||
/expected element segment reference/);
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(
|
||||
`(module
|
||||
(table $t0 2 anyfunc)
|
||||
(table $t1 2 anyfunc)
|
||||
(func $f
|
||||
(table.copy 0 (i32.const 0) (i32.const 0) (i32.const 2))))`), // target without source
|
||||
SyntaxError,
|
||||
/source is required if target is specified/);
|
||||
|
||||
assertErrorMessage(() => wasmEvalText(
|
||||
`(module
|
||||
(table $t0 2 anyfunc)
|
||||
(table $t1 2 anyfunc)
|
||||
(func $f
|
||||
(table.copy (i32.const 0) 0 (i32.const 0) (i32.const 2))))`), // source without target
|
||||
SyntaxError,
|
||||
/parsing wasm text/);
|
||||
|
49
js/src/jit-test/tests/wasm/gc/tables-stress.js
Normal file
49
js/src/jit-test/tests/wasm/gc/tables-stress.js
Normal file
@ -0,0 +1,49 @@
|
||||
// |jit-test| skip-if: !wasmGeneralizedTables()
|
||||
|
||||
for ( let prefix of ['', '(table $prefix 0 32 anyfunc)']) {
|
||||
let mod = new WebAssembly.Module(wasmTextToBinary(
|
||||
`(module
|
||||
(gc_feature_opt_in 2)
|
||||
${prefix}
|
||||
(table $tbl 0 anyref)
|
||||
(import $item "m" "item" (func (result anyref)))
|
||||
(func (export "run") (param $numiters i32)
|
||||
(local $i i32)
|
||||
(local $j i32)
|
||||
(local $last i32)
|
||||
(local $iters i32)
|
||||
(local $tmp anyref)
|
||||
(set_local $last (table.grow $tbl (i32.const 1) (ref.null anyref)))
|
||||
(table.set $tbl (get_local $last) (call $item))
|
||||
(loop $iter_continue
|
||||
(set_local $i (i32.const 0))
|
||||
(set_local $j (get_local $last))
|
||||
(block $l_break
|
||||
(loop $l_continue
|
||||
(br_if $l_break (i32.ge_s (get_local $j) (get_local $i)))
|
||||
(set_local $tmp (table.get $tbl (get_local $i)))
|
||||
(if (i32.eqz (i32.rem_s (get_local $i) (i32.const 3)))
|
||||
(set_local $tmp (call $item)))
|
||||
(table.set $tbl (get_local $i) (table.get $tbl (get_local $j)))
|
||||
(table.set $tbl (get_local $j) (get_local $tmp))
|
||||
(set_local $i (i32.add (get_local $i) (i32.const 1)))
|
||||
(set_local $j (i32.sub (get_local $j) (i32.const 1)))
|
||||
(br $l_continue))
|
||||
(set_local $iters (i32.add (get_local $iters) (i32.const 1)))
|
||||
(br_if $iter_continue (i32.lt_s (get_local $iters) (get_local $numiters)))))))`));
|
||||
|
||||
for (let [mode,freq] of [[14,100], // Compact every 100 allocations
|
||||
[2,12], // Collect every 12 allocations
|
||||
[7,100], // Minor gc every 100 allocations
|
||||
[15,100]]) // Verify heap integrity
|
||||
{
|
||||
if (this.gczeal)
|
||||
this.gczeal(mode,freq);
|
||||
let k = 0;
|
||||
let ins = new WebAssembly.Instance(mod, {m:{item:() => { return { x: k++ } }}}).exports;
|
||||
for ( let i=0; i < 1000; i++ )
|
||||
ins.run(1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -453,8 +453,6 @@ wasmFailValidateText('(module (export "a" table))', /exported table index out of
|
||||
|
||||
wasmFailValidateText('(module (import "a" "b" (memory 1 1)) (memory 1 1))', /already have default memory/);
|
||||
wasmFailValidateText('(module (import "a" "b" (memory 1 1)) (import "x" "y" (memory 2 2)))', /already have default memory/);
|
||||
wasmFailValidateText('(module (import "a" "b" (table 1 1 anyfunc)) (table 1 1 anyfunc))', /already have default table/);
|
||||
wasmFailValidateText('(module (import "a" "b" (table 1 1 anyfunc)) (import "x" "y" (table 2 2 anyfunc)))', /already have default table/);
|
||||
|
||||
// Data segments on imports
|
||||
|
||||
|
@ -207,16 +207,16 @@ tab_test("table.drop 3", "",
|
||||
|
||||
// init with no table
|
||||
tab_test("(table.init 1 (i32.const 12) (i32.const 1) (i32.const 1))", "",
|
||||
WebAssembly.CompileError, /can't table.init without a table/,
|
||||
WebAssembly.CompileError, /table index out of range/,
|
||||
false);
|
||||
|
||||
// drop with elem seg ix out of range
|
||||
tab_test("table.drop 4", "",
|
||||
WebAssembly.CompileError, /table.drop index out of range/);
|
||||
WebAssembly.CompileError, /element segment index out of range for table.drop/);
|
||||
|
||||
// init with elem seg ix out of range
|
||||
tab_test("(table.init 4 (i32.const 12) (i32.const 1) (i32.const 1))", "",
|
||||
WebAssembly.CompileError, /table.init index out of range/);
|
||||
WebAssembly.CompileError, /table.init segment index out of range/);
|
||||
|
||||
// drop with elem seg ix indicating an active segment
|
||||
tab_test("table.drop 2", "",
|
||||
|
@ -371,7 +371,7 @@ function checkMiscPrefixed(opcode, expect_failure) {
|
||||
checkMiscPrefixed([0x0a, 0x00], false); // memory.copy, flags=0
|
||||
checkMiscPrefixed([0x0b, 0x00], false); // memory.fill, flags=0
|
||||
checkMiscPrefixed([0x0b, 0x80, 0x00], false); // memory.fill, flags=0 (long encoding)
|
||||
checkMiscPrefixed([0x0f], true); // table.copy+1, which is currently unassigned
|
||||
checkMiscPrefixed([0x13], true); // table.size+1, which is currently unassigned
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Verification cases for memory.copy/fill arguments
|
||||
|
@ -188,15 +188,6 @@ assert_trap(() => call($14, "call", [3]));
|
||||
// imports.wast:284
|
||||
assert_trap(() => call($14, "call", [100]));
|
||||
|
||||
// imports.wast:287
|
||||
assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x02\x8d\x80\x80\x80\x00\x02\x00\x00\x01\x70\x00\x0a\x00\x00\x01\x70\x00\x0a");
|
||||
|
||||
// imports.wast:291
|
||||
assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x02\x87\x80\x80\x80\x00\x01\x00\x00\x01\x70\x00\x0a\x04\x84\x80\x80\x80\x00\x01\x70\x00\x0a");
|
||||
|
||||
// imports.wast:295
|
||||
assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x04\x87\x80\x80\x80\x00\x02\x70\x00\x0a\x70\x00\x0a");
|
||||
|
||||
// imports.wast:300
|
||||
let $15 = instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x02\x97\x80\x80\x80\x00\x01\x04\x74\x65\x73\x74\x0c\x74\x61\x62\x6c\x65\x2d\x31\x30\x2d\x69\x6e\x66\x01\x70\x00\x0a");
|
||||
|
||||
|
@ -266,5 +266,5 @@ assertEq(tbl.get(0).foo, 42);
|
||||
// Should fail because the table index is invalid
|
||||
assertErrorMessage(() => new WebAssembly.Module(makeIt(0x02, 0x01)),
|
||||
WebAssembly.CompileError,
|
||||
/table index must be zero/);
|
||||
/table index out of range/);
|
||||
}
|
||||
|
@ -7346,8 +7346,7 @@ CodeGenerator::emitWasmCallBase(MWasmCall* mir, bool needsBoundsCheck)
|
||||
case wasm::CalleeDesc::AsmJSTable:
|
||||
case wasm::CalleeDesc::WasmTable:
|
||||
masm.wasmCallIndirect(desc, callee, needsBoundsCheck);
|
||||
reloadRegs = switchRealm =
|
||||
(callee.which() == wasm::CalleeDesc::WasmTable && callee.wasmTableIsExternal());
|
||||
reloadRegs = switchRealm = callee.which() == wasm::CalleeDesc::WasmTable;
|
||||
break;
|
||||
case wasm::CalleeDesc::Builtin:
|
||||
masm.call(desc, callee.builtin());
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
#include "jit/IonAnalysis.h"
|
||||
|
||||
#include <utility> // for ::std::pair
|
||||
|
||||
#include "jit/AliasAnalysis.h"
|
||||
#include "jit/BaselineInspector.h"
|
||||
#include "jit/BaselineJIT.h"
|
||||
@ -29,11 +31,116 @@ using namespace js::jit;
|
||||
|
||||
using mozilla::DebugOnly;
|
||||
|
||||
typedef Vector<MPhi*, 16, SystemAllocPolicy> MPhiVector;
|
||||
// Stack used by FlagPhiInputsAsHavingRemovedUses. It stores the Phi instruction
|
||||
// pointer and the MUseIterator which should be visited next.
|
||||
using MPhiUseIteratorStack = Vector<std::pair<MPhi*, MUseIterator>, 16, SystemAllocPolicy>;
|
||||
|
||||
// Look for Phi uses with a depth-first search. If any uses are found the stack
|
||||
// of MPhi instructions is returned in the |worklist| argument.
|
||||
static bool
|
||||
DepthFirstSearchUse(MIRGenerator* mir, MPhiUseIteratorStack& worklist, MPhi* phi)
|
||||
{
|
||||
// Push a Phi and the next use to iterate over in the worklist.
|
||||
auto push = [&worklist](MPhi* phi, MUseIterator use) -> bool {
|
||||
phi->setInWorklist();
|
||||
return worklist.append(std::make_pair(phi, use));
|
||||
};
|
||||
|
||||
#ifdef DEBUG
|
||||
// Used to assert that when we have no uses, we at least visited all the
|
||||
// transitive uses.
|
||||
size_t refUseCount = phi->useCount();
|
||||
size_t useCount = 0;
|
||||
#endif
|
||||
MOZ_ASSERT(worklist.empty());
|
||||
if (!push(phi, phi->usesBegin())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (!worklist.empty()) {
|
||||
// Resume iterating over the last phi-use pair added by the next loop.
|
||||
auto pair = worklist.popCopy();
|
||||
MPhi* producer = pair.first;
|
||||
MUseIterator use = pair.second;
|
||||
MUseIterator end(producer->usesEnd());
|
||||
producer->setNotInWorklist();
|
||||
|
||||
// Keep going down the tree of uses, skipping (continue)
|
||||
// non-observable/unused cases and Phi which are already listed in the
|
||||
// worklist. Stop (return) as soon as one use is found.
|
||||
while (use != end) {
|
||||
MNode* consumer = (*use)->consumer();
|
||||
MUseIterator it = use;
|
||||
use++;
|
||||
#ifdef DEBUG
|
||||
useCount++;
|
||||
#endif
|
||||
if (mir->shouldCancel("FlagPhiInputsAsHavingRemovedUses inner loop")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (consumer->isResumePoint()) {
|
||||
MResumePoint* rp = consumer->toResumePoint();
|
||||
// Observable operands are similar to potential uses.
|
||||
if (rp->isObservableOperand(*it)) {
|
||||
return push(producer, use);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
MDefinition* cdef = consumer->toDefinition();
|
||||
if (!cdef->isPhi()) {
|
||||
// The producer is explicitly used by a definition.
|
||||
return push(producer, use);
|
||||
}
|
||||
|
||||
MPhi* cphi = cdef->toPhi();
|
||||
if (cphi->getUsageAnalysis() == PhiUsage::Used || cphi->isUseRemoved()) {
|
||||
// The information got cached on the Phi the last time it
|
||||
// got visited, or when flagging operands of removed
|
||||
// instructions.
|
||||
return push(producer, use);
|
||||
}
|
||||
|
||||
if (cphi->isInWorklist() || cphi == producer) {
|
||||
// We are already iterating over the uses of this Phi
|
||||
// instruction. Skip it.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cphi->getUsageAnalysis() == PhiUsage::Unused) {
|
||||
// The instruction already got visited and is known to have
|
||||
// no uses. Skip it.
|
||||
continue;
|
||||
}
|
||||
|
||||
// We found another Phi instruction, move the use iterator to
|
||||
// the next use push it to the worklist stack. Then, continue
|
||||
// with a depth search.
|
||||
if (!push(producer, use)) {
|
||||
return false;
|
||||
}
|
||||
producer = cphi;
|
||||
use = producer->usesBegin();
|
||||
end = producer->usesEnd();
|
||||
#ifdef DEBUG
|
||||
refUseCount += producer->useCount();
|
||||
#endif
|
||||
}
|
||||
|
||||
// When unused, we cannot bubble up this information without iterating
|
||||
// over the rest of the previous Phi instruction consumers.
|
||||
MOZ_ASSERT(use == end);
|
||||
producer->setUsageAnalysis(PhiUsage::Unused);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(useCount == refUseCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
FlagPhiInputsAsHavingRemovedUses(MIRGenerator* mir, MBasicBlock* block, MBasicBlock* succ,
|
||||
MPhiVector& worklist)
|
||||
MPhiUseIteratorStack& worklist)
|
||||
{
|
||||
// When removing an edge between 2 blocks, we might remove the ability of
|
||||
// later phases to figure out that the uses of a Phi should be considered as
|
||||
@ -94,12 +201,11 @@ FlagPhiInputsAsHavingRemovedUses(MIRGenerator* mir, MBasicBlock* block, MBasicBl
|
||||
// later compilation phase might optimize them out. The problem is that a
|
||||
// bailout will use this value and give it back to baseline, which will then
|
||||
// use the OptimizedOut magic value in a computation.
|
||||
|
||||
// Conservative upper limit for the number of Phi instructions which are
|
||||
// visited while looking for uses.
|
||||
const size_t conservativeUsesLimit = 128;
|
||||
|
||||
MOZ_ASSERT(worklist.empty());
|
||||
//
|
||||
// Unfortunately, we cannot be too conservative about flagging Phi inputs as
|
||||
// having removed uses, as this would prevent many optimizations from being
|
||||
// used. Thus, the following code is in charge of flagging Phi instructions
|
||||
// as Unused or Used, and setting UseRemoved accordingly.
|
||||
size_t predIndex = succ->getPredecessorIndex(block);
|
||||
MPhiIterator end = succ->phisEnd();
|
||||
MPhiIterator it = succ->phisBegin();
|
||||
@ -117,82 +223,36 @@ FlagPhiInputsAsHavingRemovedUses(MIRGenerator* mir, MBasicBlock* block, MBasicBl
|
||||
continue;
|
||||
}
|
||||
|
||||
phi->setInWorklist();
|
||||
if (!worklist.append(phi)) {
|
||||
// If the Phi is either Used or Unused, set the UseRemoved flag
|
||||
// accordingly.
|
||||
if (phi->getUsageAnalysis() == PhiUsage::Used || phi->isUseRemoved()) {
|
||||
def->setUseRemoved();
|
||||
continue;
|
||||
} else if (phi->getUsageAnalysis() == PhiUsage::Unused) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We do not know if the Phi was Used or Unused, iterate over all uses
|
||||
// with a depth-search of uses. Returns the matching stack in the
|
||||
// worklist as soon as one use is found.
|
||||
MOZ_ASSERT(worklist.empty());
|
||||
if (!DepthFirstSearchUse(mir, worklist, phi)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fill the work list with all the Phi nodes uses until we reach either:
|
||||
// - A resume point which uses the Phi as an observable operand.
|
||||
// - An explicit use of the Phi instruction.
|
||||
// - An implicit use of the Phi instruction.
|
||||
bool isUsed = false;
|
||||
for (size_t idx = 0; !isUsed && idx < worklist.length(); idx++) {
|
||||
phi = worklist[idx];
|
||||
|
||||
if (mir->shouldCancel("FlagPhiInputsAsHavingRemovedUses inner loop 1")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (phi->isUseRemoved() || phi->isImplicitlyUsed()) {
|
||||
// The phi is implicitly used.
|
||||
isUsed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
MUseIterator usesEnd(phi->usesEnd());
|
||||
for (MUseIterator use(phi->usesBegin()); use != usesEnd; use++) {
|
||||
MNode* consumer = (*use)->consumer();
|
||||
|
||||
if (mir->shouldCancel("FlagPhiInputsAsHavingRemovedUses inner loop 2")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (consumer->isResumePoint()) {
|
||||
MResumePoint* rp = consumer->toResumePoint();
|
||||
if (rp->isObservableOperand(*use)) {
|
||||
// The phi is observable via a resume point operand.
|
||||
isUsed = true;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
MDefinition* cdef = consumer->toDefinition();
|
||||
if (!cdef->isPhi()) {
|
||||
// The phi is explicitly used.
|
||||
isUsed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
phi = cdef->toPhi();
|
||||
if (phi->isInWorklist()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
phi->setInWorklist();
|
||||
if (!worklist.append(phi)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Use a conservative upper bound to avoid iterating too many times
|
||||
// on very large graphs.
|
||||
if (idx >= conservativeUsesLimit) {
|
||||
isUsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isUsed) {
|
||||
MOZ_ASSERT_IF(worklist.empty(), phi->getUsageAnalysis() == PhiUsage::Unused);
|
||||
if (!worklist.empty()) {
|
||||
// One of the Phis is used, set Used flags on all the Phis which are
|
||||
// in the use chain.
|
||||
def->setUseRemoved();
|
||||
do {
|
||||
auto pair = worklist.popCopy();
|
||||
MPhi* producer = pair.first;
|
||||
producer->setUsageAnalysis(PhiUsage::Used);
|
||||
producer->setNotInWorklist();
|
||||
} while (!worklist.empty());
|
||||
}
|
||||
|
||||
// Remove all the InWorklist flags.
|
||||
while (!worklist.empty()) {
|
||||
phi = worklist.popCopy();
|
||||
phi->setNotInWorklist();
|
||||
}
|
||||
MOZ_ASSERT(phi->getUsageAnalysis() != PhiUsage::Unknown);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -246,7 +306,7 @@ FlagAllOperandsAsHavingRemovedUses(MIRGenerator* mir, MBasicBlock* block)
|
||||
}
|
||||
|
||||
// Flag Phi inputs of the successors has having removed uses.
|
||||
MPhiVector worklist;
|
||||
MPhiUseIteratorStack worklist;
|
||||
for (size_t i = 0, e = block->numSuccessors(); i < e; i++) {
|
||||
if (mir->shouldCancel("FlagAllOperandsAsHavingRemovedUses loop 3")) {
|
||||
return false;
|
||||
|
@ -2327,6 +2327,31 @@ MPhi::congruentTo(const MDefinition* ins) const
|
||||
return congruentIfOperandsEqual(ins);
|
||||
}
|
||||
|
||||
bool
|
||||
MPhi::updateForReplacement(MDefinition* def)
|
||||
{
|
||||
// This function is called to fix the current Phi flags using it as a
|
||||
// replacement of the other Phi instruction |def|.
|
||||
//
|
||||
// When dealing with usage analysis, any Use will replace all other values,
|
||||
// such as Unused and Unknown. Unless both are Unused, the merge would be
|
||||
// Unknown.
|
||||
MPhi* other = def->toPhi();
|
||||
if (usageAnalysis_ == PhiUsage::Used || other->usageAnalysis_ == PhiUsage::Used) {
|
||||
usageAnalysis_ = PhiUsage::Used;
|
||||
} else if (usageAnalysis_ != other->usageAnalysis_) {
|
||||
// this == unused && other == unknown
|
||||
// or this == unknown && other == unused
|
||||
usageAnalysis_ = PhiUsage::Unknown;
|
||||
} else {
|
||||
// this == unused && other == unused
|
||||
// or this == unknown && other = unknown
|
||||
MOZ_ASSERT(usageAnalysis_ == PhiUsage::Unused || usageAnalysis_ == PhiUsage::Unknown);
|
||||
MOZ_ASSERT(usageAnalysis_ == other->usageAnalysis_);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline TemporaryTypeSet*
|
||||
MakeMIRTypeSet(TempAllocator& alloc, MIRType type)
|
||||
{
|
||||
|
@ -6826,6 +6826,15 @@ class MArrowNewTarget
|
||||
}
|
||||
};
|
||||
|
||||
// This is a 3 state flag used by FlagPhiInputsAsHavingRemovedUses to record and
|
||||
// propagate the information about the consumers of a Phi instruction. This is
|
||||
// then used to set UseRemoved flags on the inputs of such Phi instructions.
|
||||
enum class PhiUsage : uint8_t {
|
||||
Unknown,
|
||||
Unused,
|
||||
Used
|
||||
};
|
||||
|
||||
class MPhi final
|
||||
: public MDefinition,
|
||||
public InlineListNode<MPhi>,
|
||||
@ -6840,6 +6849,9 @@ class MPhi final
|
||||
bool isIterator_;
|
||||
bool canProduceFloat32_;
|
||||
bool canConsumeFloat32_;
|
||||
// Record the state of the data flow before any mutation made to the control
|
||||
// flow, such that removing branches is properly accounted for.
|
||||
PhiUsage usageAnalysis_;
|
||||
|
||||
#if DEBUG
|
||||
bool specialized_;
|
||||
@ -6871,7 +6883,8 @@ class MPhi final
|
||||
triedToSpecialize_(false),
|
||||
isIterator_(false),
|
||||
canProduceFloat32_(false),
|
||||
canConsumeFloat32_(false)
|
||||
canConsumeFloat32_(false),
|
||||
usageAnalysis_(PhiUsage::Unknown)
|
||||
#if DEBUG
|
||||
, specialized_(false)
|
||||
#endif
|
||||
@ -6978,6 +6991,7 @@ class MPhi final
|
||||
MDefinition* foldsFilterTypeSet();
|
||||
|
||||
bool congruentTo(const MDefinition* ins) const override;
|
||||
bool updateForReplacement(MDefinition* def) override;
|
||||
|
||||
bool isIterator() const {
|
||||
return isIterator_;
|
||||
@ -7012,6 +7026,13 @@ class MPhi final
|
||||
TruncateKind operandTruncateKind(size_t index) const override;
|
||||
bool needTruncation(TruncateKind kind) override;
|
||||
void truncate() override;
|
||||
|
||||
PhiUsage getUsageAnalysis() const { return usageAnalysis_; }
|
||||
void setUsageAnalysis(PhiUsage pu) {
|
||||
MOZ_ASSERT(usageAnalysis_ == PhiUsage::Unknown);
|
||||
usageAnalysis_ = pu;
|
||||
MOZ_ASSERT(usageAnalysis_ != PhiUsage::Unknown);
|
||||
}
|
||||
};
|
||||
|
||||
// The goal of a Beta node is to split a def at a conditionally taken
|
||||
|
@ -3603,11 +3603,24 @@ MacroAssembler::wasmCallIndirect(const wasm::CallSiteDesc& desc, const wasm::Cal
|
||||
Register scratch = WasmTableCallScratchReg0;
|
||||
Register index = WasmTableCallIndexReg;
|
||||
|
||||
// Optimization opportunity: when offsetof(FunctionTableElem, code) == 0, as
|
||||
// it is at present, we can probably generate better code here by folding
|
||||
// the address computation into the load.
|
||||
|
||||
static_assert(sizeof(wasm::FunctionTableElem) == 8 || sizeof(wasm::FunctionTableElem) == 16,
|
||||
"elements of function tables are two words");
|
||||
|
||||
if (callee.which() == wasm::CalleeDesc::AsmJSTable) {
|
||||
// asm.js tables require no signature check, have had their index masked
|
||||
// into range and thus need no bounds check and cannot be external.
|
||||
loadWasmGlobalPtr(callee.tableBaseGlobalDataOffset(), scratch);
|
||||
loadPtr(BaseIndex(scratch, index, ScalePointer), scratch);
|
||||
// asm.js tables require no signature check, and have had their index
|
||||
// masked into range and thus need no bounds check.
|
||||
loadWasmGlobalPtr(callee.tableFunctionBaseGlobalDataOffset(), scratch);
|
||||
if (sizeof(wasm::FunctionTableElem) == 8) {
|
||||
computeEffectiveAddress(BaseIndex(scratch, index, TimesEight), scratch);
|
||||
} else {
|
||||
lshift32(Imm32(4), index);
|
||||
addPtr(index, scratch);
|
||||
}
|
||||
loadPtr(Address(scratch, offsetof(wasm::FunctionTableElem, code)), scratch);
|
||||
call(desc, scratch);
|
||||
return;
|
||||
}
|
||||
@ -3640,39 +3653,28 @@ MacroAssembler::wasmCallIndirect(const wasm::CallSiteDesc& desc, const wasm::Cal
|
||||
}
|
||||
|
||||
// Load the base pointer of the table.
|
||||
loadWasmGlobalPtr(callee.tableBaseGlobalDataOffset(), scratch);
|
||||
loadWasmGlobalPtr(callee.tableFunctionBaseGlobalDataOffset(), scratch);
|
||||
|
||||
// Load the callee from the table.
|
||||
if (callee.wasmTableIsExternal()) {
|
||||
static_assert(sizeof(wasm::ExternalTableElem) == 8 || sizeof(wasm::ExternalTableElem) == 16,
|
||||
"elements of external tables are two words");
|
||||
if (sizeof(wasm::ExternalTableElem) == 8) {
|
||||
computeEffectiveAddress(BaseIndex(scratch, index, TimesEight), scratch);
|
||||
} else {
|
||||
lshift32(Imm32(4), index);
|
||||
addPtr(index, scratch);
|
||||
}
|
||||
|
||||
loadPtr(Address(scratch, offsetof(wasm::ExternalTableElem, tls)), WasmTlsReg);
|
||||
|
||||
Label nonNull;
|
||||
branchTest32(Assembler::NonZero, WasmTlsReg, WasmTlsReg, &nonNull);
|
||||
wasmTrap(wasm::Trap::IndirectCallToNull, trapOffset);
|
||||
bind(&nonNull);
|
||||
|
||||
loadWasmPinnedRegsFromTls();
|
||||
switchToWasmTlsRealm(index, WasmTableCallScratchReg1);
|
||||
|
||||
loadPtr(Address(scratch, offsetof(wasm::ExternalTableElem, code)), scratch);
|
||||
if (sizeof(wasm::FunctionTableElem) == 8) {
|
||||
computeEffectiveAddress(BaseIndex(scratch, index, TimesEight), scratch);
|
||||
} else {
|
||||
loadPtr(BaseIndex(scratch, index, ScalePointer), scratch);
|
||||
|
||||
Label nonNull;
|
||||
branchTest32(Assembler::NonZero, scratch, scratch, &nonNull);
|
||||
wasmTrap(wasm::Trap::IndirectCallToNull, trapOffset);
|
||||
bind(&nonNull);
|
||||
lshift32(Imm32(4), index);
|
||||
addPtr(index, scratch);
|
||||
}
|
||||
|
||||
loadPtr(Address(scratch, offsetof(wasm::FunctionTableElem, tls)), WasmTlsReg);
|
||||
|
||||
Label nonNull;
|
||||
branchTest32(Assembler::NonZero, WasmTlsReg, WasmTlsReg, &nonNull);
|
||||
wasmTrap(wasm::Trap::IndirectCallToNull, trapOffset);
|
||||
bind(&nonNull);
|
||||
|
||||
loadWasmPinnedRegsFromTls();
|
||||
switchToWasmTlsRealm(index, WasmTableCallScratchReg1);
|
||||
|
||||
loadPtr(Address(scratch, offsetof(wasm::FunctionTableElem, code)), scratch);
|
||||
|
||||
call(desc, scratch);
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ if CONFIG['NIGHTLY_BUILD']:
|
||||
DEFINES['ENABLE_WASM_BULKMEM_OPS'] = True
|
||||
DEFINES['ENABLE_WASM_THREAD_OPS'] = True
|
||||
DEFINES['ENABLE_WASM_GC'] = True
|
||||
DEFINES['ENABLE_WASM_GENERALIZED_TABLES'] = True
|
||||
DEFINES['WASM_PRIVATE_REFTYPES'] = True
|
||||
|
||||
# Some huge-mapping optimization instead of bounds checks on supported
|
||||
|
@ -384,8 +384,9 @@ MSG_DEF(JSMSG_WASM_IMP_SHARED_BANNED, 0, JSEXN_WASMLINKERROR, "imported shared
|
||||
MSG_DEF(JSMSG_WASM_BAD_FIT, 2, JSEXN_WASMLINKERROR, "{0} segment does not fit in {1}")
|
||||
MSG_DEF(JSMSG_WASM_BAD_I64_LINK, 0, JSEXN_WASMLINKERROR, "cannot pass i64 to or from JS")
|
||||
MSG_DEF(JSMSG_WASM_NO_SHMEM_LINK, 0, JSEXN_WASMLINKERROR, "shared memory is disabled")
|
||||
MSG_DEF(JSMSG_WASM_BAD_MUT_LINK, 0, JSEXN_WASMLINKERROR, "imported global mutability mismatch")
|
||||
MSG_DEF(JSMSG_WASM_BAD_TYPE_LINK, 0, JSEXN_WASMLINKERROR, "imported global type mismatch")
|
||||
MSG_DEF(JSMSG_WASM_BAD_GLOB_MUT_LINK, 0, JSEXN_WASMLINKERROR, "imported global mutability mismatch")
|
||||
MSG_DEF(JSMSG_WASM_BAD_GLOB_TYPE_LINK, 0, JSEXN_WASMLINKERROR, "imported global type mismatch")
|
||||
MSG_DEF(JSMSG_WASM_BAD_TBL_TYPE_LINK, 0, JSEXN_WASMLINKERROR, "imported table type mismatch")
|
||||
MSG_DEF(JSMSG_WASM_IND_CALL_TO_NULL, 0, JSEXN_WASMRUNTIMEERROR, "indirect call to null")
|
||||
MSG_DEF(JSMSG_WASM_IND_CALL_BAD_SIG, 0, JSEXN_WASMRUNTIMEERROR, "indirect call signature mismatch")
|
||||
MSG_DEF(JSMSG_WASM_UNREACHABLE, 0, JSEXN_WASMRUNTIMEERROR, "unreachable executed")
|
||||
@ -400,12 +401,14 @@ MSG_DEF(JSMSG_WASM_INVALID_PASSIVE_ELEM_SEG, 0, JSEXN_WASMRUNTIMEERROR, "use of
|
||||
MSG_DEF(JSMSG_WASM_DEREF_NULL, 0, JSEXN_WASMRUNTIMEERROR, "dereferencing null pointer")
|
||||
MSG_DEF(JSMSG_WASM_BAD_RANGE , 2, JSEXN_RANGEERR, "bad {0} {1}")
|
||||
MSG_DEF(JSMSG_WASM_BAD_GROW, 1, JSEXN_RANGEERR, "failed to grow {0}")
|
||||
MSG_DEF(JSMSG_WASM_TABLE_OUT_OF_BOUNDS, 0, JSEXN_RANGEERR, "table index out of bounds")
|
||||
MSG_DEF(JSMSG_WASM_BAD_UINT32, 2, JSEXN_TYPEERR, "bad {0} {1}")
|
||||
MSG_DEF(JSMSG_WASM_BAD_BUF_ARG, 0, JSEXN_TYPEERR, "first argument must be an ArrayBuffer or typed array object")
|
||||
MSG_DEF(JSMSG_WASM_BAD_MOD_ARG, 0, JSEXN_TYPEERR, "first argument must be a WebAssembly.Module")
|
||||
MSG_DEF(JSMSG_WASM_BAD_BUF_MOD_ARG, 0, JSEXN_TYPEERR, "first argument must be a WebAssembly.Module, ArrayBuffer or typed array object")
|
||||
MSG_DEF(JSMSG_WASM_BAD_DESC_ARG, 1, JSEXN_TYPEERR, "first argument must be a {0} descriptor")
|
||||
MSG_DEF(JSMSG_WASM_BAD_ELEMENT, 0, JSEXN_TYPEERR, "\"element\" property of table descriptor must be \"anyfunc\"")
|
||||
MSG_DEF(JSMSG_WASM_BAD_ELEMENT_GENERALIZED, 0, JSEXN_TYPEERR, "\"element\" property of table descriptor must be \"anyfunc\" or \"anyref\"")
|
||||
MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG, 0, JSEXN_TYPEERR, "second argument must be an object")
|
||||
MSG_DEF(JSMSG_WASM_BAD_IMPORT_FIELD, 1, JSEXN_TYPEERR, "import object field '{0}' is not an Object")
|
||||
MSG_DEF(JSMSG_WASM_BAD_TABLE_VALUE, 0, JSEXN_TYPEERR, "can only assign WebAssembly exported functions to Table")
|
||||
|
@ -6654,9 +6654,8 @@ TryInstantiate(JSContext* cx, CallArgs args, const Module& module, const AsmJSMe
|
||||
}
|
||||
|
||||
Rooted<WasmGlobalObjectVector> globalObjs(cx);
|
||||
|
||||
RootedWasmTableObject table(cx);
|
||||
if (!module.instantiate(cx, funcs, table, memory, valImports, globalObjs.get(), nullptr,
|
||||
Rooted<WasmTableObjectVector> tables(cx);
|
||||
if (!module.instantiate(cx, funcs, tables.get(), memory, valImports, globalObjs.get(), nullptr,
|
||||
instanceObj))
|
||||
return false;
|
||||
|
||||
|
@ -95,6 +95,9 @@ class AstRef
|
||||
AstName name() const {
|
||||
return name_;
|
||||
}
|
||||
bool isIndex() const {
|
||||
return index_ != AstNoIndex;
|
||||
}
|
||||
size_t index() const {
|
||||
MOZ_ASSERT(index_ != AstNoIndex);
|
||||
return index_;
|
||||
@ -496,6 +499,12 @@ enum class AstExprKind
|
||||
MemFill,
|
||||
MemOrTableInit,
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
TableGet,
|
||||
TableGrow,
|
||||
TableSet,
|
||||
TableSize,
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GC
|
||||
StructNew,
|
||||
StructGet,
|
||||
@ -740,15 +749,22 @@ class AstCall : public AstExpr
|
||||
|
||||
class AstCallIndirect : public AstExpr
|
||||
{
|
||||
AstRef targetTable_;
|
||||
AstRef funcType_;
|
||||
AstExprVector args_;
|
||||
AstExpr* index_;
|
||||
|
||||
public:
|
||||
static const AstExprKind Kind = AstExprKind::CallIndirect;
|
||||
AstCallIndirect(AstRef funcType, AstExprType type, AstExprVector&& args, AstExpr* index)
|
||||
: AstExpr(Kind, type), funcType_(funcType), args_(std::move(args)), index_(index)
|
||||
AstCallIndirect(AstRef targetTable, AstRef funcType, AstExprType type, AstExprVector&& args,
|
||||
AstExpr* index)
|
||||
: AstExpr(Kind, type),
|
||||
targetTable_(targetTable),
|
||||
funcType_(funcType),
|
||||
args_(std::move(args)),
|
||||
index_(index)
|
||||
{}
|
||||
AstRef& targetTable() { return targetTable_; }
|
||||
AstRef& funcType() { return funcType_; }
|
||||
const AstExprVector& args() const { return args_; }
|
||||
AstExpr* index() const { return index_; }
|
||||
@ -973,22 +989,29 @@ class AstWake : public AstExpr
|
||||
class AstMemOrTableCopy : public AstExpr
|
||||
{
|
||||
bool isMem_;
|
||||
AstRef destTable_;
|
||||
AstExpr* dest_;
|
||||
AstRef srcTable_;
|
||||
AstExpr* src_;
|
||||
AstExpr* len_;
|
||||
|
||||
public:
|
||||
static const AstExprKind Kind = AstExprKind::MemOrTableCopy;
|
||||
explicit AstMemOrTableCopy(bool isMem, AstExpr* dest, AstExpr* src, AstExpr* len)
|
||||
explicit AstMemOrTableCopy(bool isMem, AstRef destTable, AstExpr* dest, AstRef srcTable,
|
||||
AstExpr* src, AstExpr* len)
|
||||
: AstExpr(Kind, ExprType::Void),
|
||||
isMem_(isMem),
|
||||
destTable_(destTable),
|
||||
dest_(dest),
|
||||
srcTable_(srcTable),
|
||||
src_(src),
|
||||
len_(len)
|
||||
{}
|
||||
|
||||
bool isMem() const { return isMem_; }
|
||||
AstRef& destTable() { return destTable_; }
|
||||
AstExpr& dest() const { return *dest_; }
|
||||
AstRef& srcTable() { return srcTable_; }
|
||||
AstExpr& src() const { return *src_; }
|
||||
AstExpr& len() const { return *len_; }
|
||||
};
|
||||
@ -1034,16 +1057,19 @@ class AstMemOrTableInit : public AstExpr
|
||||
{
|
||||
bool isMem_;
|
||||
uint32_t segIndex_;
|
||||
AstRef targetTable_;
|
||||
AstExpr* dst_;
|
||||
AstExpr* src_;
|
||||
AstExpr* len_;
|
||||
|
||||
public:
|
||||
static const AstExprKind Kind = AstExprKind::MemOrTableInit;
|
||||
explicit AstMemOrTableInit(bool isMem, uint32_t segIndex, AstExpr* dst, AstExpr* src, AstExpr* len)
|
||||
explicit AstMemOrTableInit(bool isMem, uint32_t segIndex, AstRef targetTable, AstExpr* dst,
|
||||
AstExpr* src, AstExpr* len)
|
||||
: AstExpr(Kind, ExprType::Void),
|
||||
isMem_(isMem),
|
||||
segIndex_(segIndex),
|
||||
targetTable_(targetTable),
|
||||
dst_(dst),
|
||||
src_(src),
|
||||
len_(len)
|
||||
@ -1051,12 +1077,86 @@ class AstMemOrTableInit : public AstExpr
|
||||
|
||||
bool isMem() const { return isMem_; }
|
||||
uint32_t segIndex() const { return segIndex_; }
|
||||
AstRef& targetTable() { return targetTable_; }
|
||||
AstExpr& dst() const { return *dst_; }
|
||||
AstExpr& src() const { return *src_; }
|
||||
AstExpr& len() const { return *len_; }
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
class AstTableGet : public AstExpr
|
||||
{
|
||||
AstRef targetTable_;
|
||||
AstExpr* index_;
|
||||
|
||||
public:
|
||||
static const AstExprKind Kind = AstExprKind::TableGet;
|
||||
explicit AstTableGet(AstRef targetTable, AstExpr* index)
|
||||
: AstExpr(Kind, ExprType::AnyRef),
|
||||
targetTable_(targetTable),
|
||||
index_(index)
|
||||
{}
|
||||
|
||||
AstRef& targetTable() { return targetTable_; }
|
||||
AstExpr& index() const { return *index_; }
|
||||
};
|
||||
|
||||
class AstTableGrow : public AstExpr
|
||||
{
|
||||
AstRef targetTable_;
|
||||
AstExpr* delta_;
|
||||
AstExpr* initValue_;
|
||||
|
||||
public:
|
||||
static const AstExprKind Kind = AstExprKind::TableGrow;
|
||||
AstTableGrow(AstRef targetTable, AstExpr* delta, AstExpr* initValue)
|
||||
: AstExpr(Kind, ExprType::I32),
|
||||
targetTable_(targetTable),
|
||||
delta_(delta),
|
||||
initValue_(initValue)
|
||||
{}
|
||||
|
||||
AstRef& targetTable() { return targetTable_; }
|
||||
AstExpr& delta() const { return *delta_; }
|
||||
AstExpr& initValue() const { return *initValue_; }
|
||||
};
|
||||
|
||||
class AstTableSet : public AstExpr
|
||||
{
|
||||
AstRef targetTable_;
|
||||
AstExpr* index_;
|
||||
AstExpr* value_;
|
||||
|
||||
public:
|
||||
static const AstExprKind Kind = AstExprKind::TableSet;
|
||||
AstTableSet(AstRef targetTable, AstExpr* index, AstExpr* value)
|
||||
: AstExpr(Kind, ExprType::Void),
|
||||
targetTable_(targetTable),
|
||||
index_(index),
|
||||
value_(value)
|
||||
{}
|
||||
|
||||
AstRef& targetTable() { return targetTable_; }
|
||||
AstExpr& index() const { return *index_; }
|
||||
AstExpr& value() const { return *value_; }
|
||||
};
|
||||
|
||||
class AstTableSize : public AstExpr
|
||||
{
|
||||
AstRef targetTable_;
|
||||
|
||||
public:
|
||||
static const AstExprKind Kind = AstExprKind::TableSize;
|
||||
explicit AstTableSize(AstRef targetTable)
|
||||
: AstExpr(Kind, ExprType::I32),
|
||||
targetTable_(targetTable)
|
||||
{}
|
||||
|
||||
AstRef& targetTable() { return targetTable_; }
|
||||
};
|
||||
#endif // ENABLE_WASM_GENERALIZED_TABLES
|
||||
|
||||
#ifdef ENABLE_WASM_GC
|
||||
class AstStructNew : public AstExpr
|
||||
{
|
||||
@ -1239,6 +1339,7 @@ class AstImport : public AstNode
|
||||
|
||||
AstRef funcType_;
|
||||
Limits limits_;
|
||||
TableKind tableKind_;
|
||||
AstGlobal global_;
|
||||
|
||||
public:
|
||||
@ -1248,6 +1349,12 @@ class AstImport : public AstNode
|
||||
AstImport(AstName name, AstName module, AstName field, DefinitionKind kind,
|
||||
const Limits& limits)
|
||||
: name_(name), module_(module), field_(field), kind_(kind), limits_(limits)
|
||||
{
|
||||
MOZ_ASSERT(kind != DefinitionKind::Table, "A table must have a kind");
|
||||
}
|
||||
AstImport(AstName name, AstName module, AstName field, const Limits& limits, TableKind tableKind)
|
||||
: name_(name), module_(module), field_(field), kind_(DefinitionKind::Table), limits_(limits),
|
||||
tableKind_(tableKind)
|
||||
{}
|
||||
AstImport(AstName name, AstName module, AstName field, const AstGlobal& global)
|
||||
: name_(name), module_(module), field_(field), kind_(DefinitionKind::Global), global_(global)
|
||||
@ -1258,6 +1365,10 @@ class AstImport : public AstNode
|
||||
AstName field() const { return field_; }
|
||||
|
||||
DefinitionKind kind() const { return kind_; }
|
||||
TableKind tableKind() const {
|
||||
MOZ_ASSERT(kind_ == DefinitionKind::Table);
|
||||
return tableKind_;
|
||||
}
|
||||
AstRef& funcType() {
|
||||
MOZ_ASSERT(kind_ == DefinitionKind::Function);
|
||||
return funcType_;
|
||||
@ -1313,15 +1424,19 @@ typedef AstVector<AstDataSegment*> AstDataSegmentVector;
|
||||
|
||||
class AstElemSegment : public AstNode
|
||||
{
|
||||
AstRef targetTable_;
|
||||
AstExpr* offsetIfActive_;
|
||||
AstRefVector elems_;
|
||||
|
||||
public:
|
||||
AstElemSegment(AstExpr* offsetIfActive, AstRefVector&& elems)
|
||||
: offsetIfActive_(offsetIfActive),
|
||||
AstElemSegment(AstRef targetTable, AstExpr* offsetIfActive, AstRefVector&& elems)
|
||||
: targetTable_(targetTable),
|
||||
offsetIfActive_(offsetIfActive),
|
||||
elems_(std::move(elems))
|
||||
{}
|
||||
|
||||
AstRef targetTable() const { return targetTable_; }
|
||||
AstRef& targetTableRef() { return targetTable_; }
|
||||
AstExpr* offsetIfActive() const { return offsetIfActive_; }
|
||||
AstRefVector& elems() { return elems_; }
|
||||
const AstRefVector& elems() const { return elems_; }
|
||||
@ -1343,19 +1458,34 @@ class AstStartFunc : public AstNode
|
||||
}
|
||||
};
|
||||
|
||||
struct AstResizable
|
||||
struct AstMemory
|
||||
{
|
||||
AstName name;
|
||||
Limits limits;
|
||||
bool imported;
|
||||
|
||||
AstResizable(const Limits& limits, bool imported, AstName name = AstName())
|
||||
AstMemory(const Limits& limits, bool imported, AstName name = AstName())
|
||||
: name(name),
|
||||
limits(limits),
|
||||
imported(imported)
|
||||
{}
|
||||
};
|
||||
|
||||
struct AstTable
|
||||
{
|
||||
AstName name;
|
||||
Limits limits;
|
||||
TableKind tableKind;
|
||||
bool imported;
|
||||
|
||||
AstTable(const Limits& limits, TableKind tableKind, bool imported, AstName name = AstName())
|
||||
: name(name),
|
||||
limits(limits),
|
||||
tableKind(tableKind),
|
||||
imported(imported)
|
||||
{}
|
||||
};
|
||||
|
||||
class AstModule : public AstNode
|
||||
{
|
||||
public:
|
||||
@ -1364,7 +1494,8 @@ class AstModule : public AstNode
|
||||
typedef AstVector<AstExport*> ExportVector;
|
||||
typedef AstVector<AstTypeDef*> TypeDefVector;
|
||||
typedef AstVector<AstName> NameVector;
|
||||
typedef AstVector<AstResizable> AstResizableVector;
|
||||
typedef AstVector<AstMemory> AstMemoryVector;
|
||||
typedef AstVector<AstTable> AstTableVector;
|
||||
|
||||
private:
|
||||
typedef AstHashMap<AstFuncType*, uint32_t, AstFuncType> FuncTypeMap;
|
||||
@ -1374,8 +1505,8 @@ class AstModule : public AstNode
|
||||
FuncTypeMap funcTypeMap_;
|
||||
ImportVector imports_;
|
||||
NameVector funcImportNames_;
|
||||
AstResizableVector tables_;
|
||||
AstResizableVector memories_;
|
||||
AstTableVector tables_;
|
||||
AstMemoryVector memories_;
|
||||
#ifdef ENABLE_WASM_GC
|
||||
uint32_t gcFeatureOptIn_;
|
||||
#endif
|
||||
@ -1408,12 +1539,12 @@ class AstModule : public AstNode
|
||||
numGlobalImports_(0)
|
||||
{}
|
||||
bool addMemory(AstName name, const Limits& memory) {
|
||||
return memories_.append(AstResizable(memory, false, name));
|
||||
return memories_.append(AstMemory(memory, false, name));
|
||||
}
|
||||
bool hasMemory() const {
|
||||
return !!memories_.length();
|
||||
}
|
||||
const AstResizableVector& memories() const {
|
||||
const AstMemoryVector& memories() const {
|
||||
return memories_;
|
||||
}
|
||||
#ifdef ENABLE_WASM_GC
|
||||
@ -1425,13 +1556,13 @@ class AstModule : public AstNode
|
||||
return gcFeatureOptIn_;
|
||||
}
|
||||
#endif
|
||||
bool addTable(AstName name, const Limits& table) {
|
||||
return tables_.append(AstResizable(table, false, name));
|
||||
bool addTable(AstName name, const Limits& table, TableKind tableKind) {
|
||||
return tables_.append(AstTable(table, tableKind, false, name));
|
||||
}
|
||||
bool hasTable() const {
|
||||
return !!tables_.length();
|
||||
}
|
||||
const AstResizableVector& tables() const {
|
||||
const AstTableVector& tables() const {
|
||||
return tables_;
|
||||
}
|
||||
bool append(AstDataSegment* seg) {
|
||||
@ -1508,12 +1639,12 @@ class AstModule : public AstNode
|
||||
}
|
||||
break;
|
||||
case DefinitionKind::Table:
|
||||
if (!tables_.append(AstResizable(imp->limits(), true))) {
|
||||
if (!tables_.append(AstTable(imp->limits(), imp->tableKind(), true))) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case DefinitionKind::Memory:
|
||||
if (!memories_.append(AstResizable(imp->limits(), true))) {
|
||||
if (!memories_.append(AstMemory(imp->limits(), true))) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
@ -1931,11 +1931,13 @@ class BaseCompiler final : public BaseCompilerInterface
|
||||
MIRTypeVector SigPI_;
|
||||
MIRTypeVector SigPL_;
|
||||
MIRTypeVector SigPII_;
|
||||
MIRTypeVector SigPIPI_;
|
||||
MIRTypeVector SigPIII_;
|
||||
MIRTypeVector SigPIIL_;
|
||||
MIRTypeVector SigPIIP_;
|
||||
MIRTypeVector SigPILL_;
|
||||
MIRTypeVector SigPIIII_;
|
||||
MIRTypeVector SigPIIIII_;
|
||||
NonAssertingLabel returnLabel_;
|
||||
|
||||
LatentOp latentOp_; // Latent operation for branch (seen next)
|
||||
@ -3873,13 +3875,13 @@ class BaseCompiler final : public BaseCompilerInterface
|
||||
|
||||
// Precondition: sync()
|
||||
|
||||
void callIndirect(uint32_t funcTypeIndex, const Stk& indexVal, const FunctionCall& call)
|
||||
void callIndirect(uint32_t funcTypeIndex, uint32_t tableIndex, const Stk& indexVal,
|
||||
const FunctionCall& call)
|
||||
{
|
||||
const FuncTypeWithId& funcType = env_.types[funcTypeIndex].funcType();
|
||||
MOZ_ASSERT(funcType.id.kind() != FuncTypeIdDescKind::None);
|
||||
|
||||
MOZ_ASSERT(env_.tables.length() == 1);
|
||||
const TableDesc& table = env_.tables[0];
|
||||
const TableDesc& table = env_.tables[tableIndex];
|
||||
|
||||
loadI32(indexVal, RegI32(WasmTableCallIndexReg));
|
||||
|
||||
@ -6169,6 +6171,10 @@ class BaseCompiler final : public BaseCompilerInterface
|
||||
MOZ_MUST_USE bool emitMemFill();
|
||||
MOZ_MUST_USE bool emitMemOrTableInit(bool isMem);
|
||||
#endif
|
||||
MOZ_MUST_USE bool emitTableGet();
|
||||
MOZ_MUST_USE bool emitTableGrow();
|
||||
MOZ_MUST_USE bool emitTableSet();
|
||||
MOZ_MUST_USE bool emitTableSize();
|
||||
MOZ_MUST_USE bool emitStructNew();
|
||||
MOZ_MUST_USE bool emitStructGet();
|
||||
MOZ_MUST_USE bool emitStructSet();
|
||||
@ -8181,9 +8187,10 @@ BaseCompiler::emitCallIndirect()
|
||||
uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
|
||||
|
||||
uint32_t funcTypeIndex;
|
||||
uint32_t tableIndex;
|
||||
Nothing callee_;
|
||||
BaseOpIter::ValueVector args_;
|
||||
if (!iter_.readCallIndirect(&funcTypeIndex, &callee_, &args_)) {
|
||||
if (!iter_.readCallIndirect(&funcTypeIndex, &tableIndex, &callee_, &args_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -8213,7 +8220,7 @@ BaseCompiler::emitCallIndirect()
|
||||
return false;
|
||||
}
|
||||
|
||||
callIndirect(funcTypeIndex, callee, baselineCall);
|
||||
callIndirect(funcTypeIndex, tableIndex, callee, baselineCall);
|
||||
|
||||
endCall(baselineCall, stackSpace);
|
||||
|
||||
@ -9273,6 +9280,7 @@ BaseCompiler::emitGrowMemory()
|
||||
return true;
|
||||
}
|
||||
|
||||
// infallible
|
||||
emitInstanceCall(lineOrBytecode, SigPI_, ExprType::I32, SymbolicAddress::GrowMemory);
|
||||
return true;
|
||||
}
|
||||
@ -9290,6 +9298,7 @@ BaseCompiler::emitCurrentMemory()
|
||||
return true;
|
||||
}
|
||||
|
||||
// infallible
|
||||
emitInstanceCall(lineOrBytecode, SigP_, ExprType::I32, SymbolicAddress::CurrentMemory);
|
||||
return true;
|
||||
}
|
||||
@ -9615,6 +9624,7 @@ BaseCompiler::emitWait(ValType type, uint32_t byteSize)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns -1 on trap, otherwise nonnegative result.
|
||||
switch (type.code()) {
|
||||
case ValType::I32:
|
||||
emitInstanceCall(lineOrBytecode, SigPIIL_, ExprType::I32, SymbolicAddress::WaitI32);
|
||||
@ -9649,6 +9659,7 @@ BaseCompiler::emitWake()
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns -1 on trap, otherwise nonnegative result.
|
||||
emitInstanceCall(lineOrBytecode, SigPII_, ExprType::I32, SymbolicAddress::Wake);
|
||||
|
||||
Label ok;
|
||||
@ -9665,8 +9676,12 @@ BaseCompiler::emitMemOrTableCopy(bool isMem)
|
||||
{
|
||||
uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
|
||||
|
||||
uint32_t dstMemOrTableIndex = 0;
|
||||
uint32_t srcMemOrTableIndex = 0;
|
||||
Nothing nothing;
|
||||
if (!iter_.readMemOrTableCopy(isMem, ¬hing, ¬hing, ¬hing)) {
|
||||
if (!iter_.readMemOrTableCopy(isMem, &dstMemOrTableIndex, ¬hing, &srcMemOrTableIndex,
|
||||
¬hing, ¬hing))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -9674,9 +9689,16 @@ BaseCompiler::emitMemOrTableCopy(bool isMem)
|
||||
return true;
|
||||
}
|
||||
|
||||
SymbolicAddress callee = isMem ? SymbolicAddress::MemCopy
|
||||
: SymbolicAddress::TableCopy;
|
||||
emitInstanceCall(lineOrBytecode, SigPIII_, ExprType::Void, callee);
|
||||
// Returns -1 on trap, otherwise 0.
|
||||
if (isMem) {
|
||||
MOZ_ASSERT(srcMemOrTableIndex == 0);
|
||||
MOZ_ASSERT(dstMemOrTableIndex == 0);
|
||||
emitInstanceCall(lineOrBytecode, SigPIII_, ExprType::Void, SymbolicAddress::MemCopy);
|
||||
} else {
|
||||
pushI32(dstMemOrTableIndex);
|
||||
pushI32(srcMemOrTableIndex);
|
||||
emitInstanceCall(lineOrBytecode, SigPIIIII_, ExprType::Void, SymbolicAddress::TableCopy);
|
||||
}
|
||||
|
||||
Label ok;
|
||||
masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
|
||||
@ -9701,6 +9723,8 @@ BaseCompiler::emitMemOrTableDrop(bool isMem)
|
||||
}
|
||||
|
||||
// Despite the cast to int32_t, the callee regards the value as unsigned.
|
||||
//
|
||||
// Returns -1 on trap, otherwise 0.
|
||||
pushI32(int32_t(segIndex));
|
||||
SymbolicAddress callee = isMem ? SymbolicAddress::MemDrop
|
||||
: SymbolicAddress::TableDrop;
|
||||
@ -9728,6 +9752,7 @@ BaseCompiler::emitMemFill()
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns -1 on trap, otherwise 0.
|
||||
emitInstanceCall(lineOrBytecode, SigPIII_, ExprType::Void, SymbolicAddress::MemFill);
|
||||
|
||||
Label ok;
|
||||
@ -9744,8 +9769,9 @@ BaseCompiler::emitMemOrTableInit(bool isMem)
|
||||
uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
|
||||
|
||||
uint32_t segIndex = 0;
|
||||
uint32_t dstTableIndex = 0;
|
||||
Nothing nothing;
|
||||
if (!iter_.readMemOrTableInit(isMem, &segIndex, ¬hing, ¬hing, ¬hing)) {
|
||||
if (!iter_.readMemOrTableInit(isMem, &segIndex, &dstTableIndex, ¬hing, ¬hing, ¬hing)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -9753,10 +9779,14 @@ BaseCompiler::emitMemOrTableInit(bool isMem)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns -1 on trap, otherwise 0.
|
||||
pushI32(int32_t(segIndex));
|
||||
SymbolicAddress callee = isMem ? SymbolicAddress::MemInit
|
||||
: SymbolicAddress::TableInit;
|
||||
emitInstanceCall(lineOrBytecode, SigPIIII_, ExprType::Void, callee);
|
||||
if (isMem) {
|
||||
emitInstanceCall(lineOrBytecode, SigPIIII_, ExprType::Void, SymbolicAddress::MemInit);
|
||||
} else {
|
||||
pushI32(dstTableIndex);
|
||||
emitInstanceCall(lineOrBytecode, SigPIIIII_, ExprType::Void, SymbolicAddress::TableInit);
|
||||
}
|
||||
|
||||
Label ok;
|
||||
masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
|
||||
@ -9767,6 +9797,101 @@ BaseCompiler::emitMemOrTableInit(bool isMem)
|
||||
}
|
||||
#endif
|
||||
|
||||
MOZ_MUST_USE
|
||||
bool
|
||||
BaseCompiler::emitTableGet()
|
||||
{
|
||||
uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
|
||||
Nothing index;
|
||||
uint32_t tableIndex;
|
||||
if (!iter_.readTableGet(&tableIndex, &index)) {
|
||||
return false;
|
||||
}
|
||||
if (deadCode_) {
|
||||
return true;
|
||||
}
|
||||
// get(index:u32, table:u32) -> anyref
|
||||
//
|
||||
// Returns (void*)-1 for error, this will not be confused with a real ref
|
||||
// value.
|
||||
pushI32(tableIndex);
|
||||
emitInstanceCall(lineOrBytecode, SigPII_, ExprType::AnyRef, SymbolicAddress::TableGet);
|
||||
Label noTrap;
|
||||
masm.branchPtr(Assembler::NotEqual, ReturnReg, Imm32(-1), &noTrap);
|
||||
trap(Trap::ThrowReported);
|
||||
masm.bind(&noTrap);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE
|
||||
bool
|
||||
BaseCompiler::emitTableGrow()
|
||||
{
|
||||
uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
|
||||
Nothing delta;
|
||||
Nothing initValue;
|
||||
uint32_t tableIndex;
|
||||
if (!iter_.readTableGrow(&tableIndex, &delta, &initValue)) {
|
||||
return false;
|
||||
}
|
||||
if (deadCode_) {
|
||||
return true;
|
||||
}
|
||||
// grow(delta:u32, initValue:anyref, table:u32) -> u32
|
||||
//
|
||||
// infallible.
|
||||
pushI32(tableIndex);
|
||||
emitInstanceCall(lineOrBytecode, SigPIPI_, ExprType::I32, SymbolicAddress::TableGrow);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE
|
||||
bool
|
||||
BaseCompiler::emitTableSet()
|
||||
{
|
||||
uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
|
||||
Nothing index, value;
|
||||
uint32_t tableIndex;
|
||||
if (!iter_.readTableSet(&tableIndex, &index, &value)) {
|
||||
return false;
|
||||
}
|
||||
if (deadCode_) {
|
||||
return true;
|
||||
}
|
||||
// set(index:u32, value:ref, table:u32) -> i32
|
||||
//
|
||||
// Returns -1 on range error, otherwise 0 (which is then ignored).
|
||||
pushI32(tableIndex);
|
||||
emitInstanceCall(lineOrBytecode, SigPIPI_, ExprType::Void, SymbolicAddress::TableSet);
|
||||
Label noTrap;
|
||||
masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &noTrap);
|
||||
trap(Trap::ThrowReported);
|
||||
masm.bind(&noTrap);
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE
|
||||
bool
|
||||
BaseCompiler::emitTableSize()
|
||||
{
|
||||
uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
|
||||
uint32_t tableIndex;
|
||||
if (!iter_.readTableSize(&tableIndex)) {
|
||||
return false;
|
||||
}
|
||||
if (deadCode_) {
|
||||
return true;
|
||||
}
|
||||
// size(table:u32) -> u32
|
||||
//
|
||||
// infallible.
|
||||
pushI32(tableIndex);
|
||||
emitInstanceCall(lineOrBytecode, SigPI_, ExprType::I32, SymbolicAddress::TableSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BaseCompiler::emitStructNew()
|
||||
{
|
||||
@ -9784,6 +9909,8 @@ BaseCompiler::emitStructNew()
|
||||
|
||||
// Allocate zeroed storage. The parameter to StructNew is an index into a
|
||||
// descriptor table that the instance has.
|
||||
//
|
||||
// Returns null on OOM.
|
||||
|
||||
const StructType& structType = env_.types[typeIndex].structType();
|
||||
|
||||
@ -10101,7 +10228,8 @@ BaseCompiler::emitStructNarrow()
|
||||
bool mustUnboxAnyref = inputType == ValType::AnyRef;
|
||||
|
||||
// Dynamic downcast (ref T) -> (ref U), leaves rp or null
|
||||
|
||||
//
|
||||
// Infallible.
|
||||
const StructType& outputStruct = env_.types[outputType.refTypeIndex()].structType();
|
||||
|
||||
pushI32(mustUnboxAnyref);
|
||||
@ -10763,6 +10891,16 @@ BaseCompiler::emitBody()
|
||||
case uint16_t(MiscOp::TableInit):
|
||||
CHECK_NEXT(emitMemOrTableInit(/*isMem=*/false));
|
||||
#endif // ENABLE_WASM_BULKMEM_OPS
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
case uint16_t(MiscOp::TableGet):
|
||||
CHECK_NEXT(emitTableGet());
|
||||
case uint16_t(MiscOp::TableGrow):
|
||||
CHECK_NEXT(emitTableGrow());
|
||||
case uint16_t(MiscOp::TableSet):
|
||||
CHECK_NEXT(emitTableSet());
|
||||
case uint16_t(MiscOp::TableSize):
|
||||
CHECK_NEXT(emitTableSize());
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GC
|
||||
case uint16_t(MiscOp::StructNew):
|
||||
if (env_.gcTypesEnabled() == HasGcTypes::False) {
|
||||
@ -11040,6 +11178,11 @@ BaseCompiler::init()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!SigPIPI_.append(MIRType::Pointer) || !SigPIPI_.append(MIRType::Int32) ||
|
||||
!SigPIPI_.append(MIRType::Pointer) || !SigPIPI_.append(MIRType::Int32))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!SigPIII_.append(MIRType::Pointer) || !SigPIII_.append(MIRType::Int32) ||
|
||||
!SigPIII_.append(MIRType::Int32) || !SigPIII_.append(MIRType::Int32))
|
||||
{
|
||||
@ -11066,6 +11209,12 @@ BaseCompiler::init()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!SigPIIIII_.append(MIRType::Pointer) || !SigPIIIII_.append(MIRType::Int32) ||
|
||||
!SigPIIIII_.append(MIRType::Int32) || !SigPIIIII_.append(MIRType::Int32) ||
|
||||
!SigPIIIII_.append(MIRType::Int32) || !SigPIIIII_.append(MIRType::Int32))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fr.setupLocals(locals_, funcType().args(), env_.debugEnabled(), &localInfo_)) {
|
||||
return false;
|
||||
|
@ -680,14 +680,26 @@ AddressOf(SymbolicAddress imm, ABIFunctionType* abiType)
|
||||
*abiType = Args_General5;
|
||||
return FuncCast(Instance::memInit, *abiType);
|
||||
case SymbolicAddress::TableCopy:
|
||||
*abiType = Args_General4;
|
||||
*abiType = Args_General6;
|
||||
return FuncCast(Instance::tableCopy, *abiType);
|
||||
case SymbolicAddress::TableDrop:
|
||||
*abiType = Args_General2;
|
||||
return FuncCast(Instance::tableDrop, *abiType);
|
||||
case SymbolicAddress::TableInit:
|
||||
*abiType = Args_General5;
|
||||
*abiType = Args_General6;
|
||||
return FuncCast(Instance::tableInit, *abiType);
|
||||
case SymbolicAddress::TableGet:
|
||||
*abiType = Args_General3;
|
||||
return FuncCast(Instance::tableGet, *abiType);
|
||||
case SymbolicAddress::TableGrow:
|
||||
*abiType = Args_General4;
|
||||
return FuncCast(Instance::tableGrow, *abiType);
|
||||
case SymbolicAddress::TableSet:
|
||||
*abiType = Args_General4;
|
||||
return FuncCast(Instance::tableSet, *abiType);
|
||||
case SymbolicAddress::TableSize:
|
||||
*abiType = Args_General2;
|
||||
return FuncCast(Instance::tableSize, *abiType);
|
||||
case SymbolicAddress::PostBarrier:
|
||||
*abiType = Args_General2;
|
||||
return FuncCast(Instance::postBarrier, *abiType);
|
||||
@ -777,7 +789,11 @@ wasm::NeedsBuiltinThunk(SymbolicAddress sym)
|
||||
case SymbolicAddress::MemInit:
|
||||
case SymbolicAddress::TableCopy:
|
||||
case SymbolicAddress::TableDrop:
|
||||
case SymbolicAddress::TableGet:
|
||||
case SymbolicAddress::TableGrow:
|
||||
case SymbolicAddress::TableInit:
|
||||
case SymbolicAddress::TableSet:
|
||||
case SymbolicAddress::TableSize:
|
||||
case SymbolicAddress::PostBarrier:
|
||||
case SymbolicAddress::StructNew:
|
||||
case SymbolicAddress::StructNarrow:
|
||||
|
@ -141,7 +141,8 @@ enum class MemoryTableFlags
|
||||
{
|
||||
Default = 0x0,
|
||||
HasMaximum = 0x1,
|
||||
IsShared = 0x2
|
||||
IsShared = 0x2,
|
||||
HasTableIndex = 0x4, // UNOFFICIAL. There will be a separate flag for memory.
|
||||
};
|
||||
|
||||
enum class MemoryMasks
|
||||
@ -400,6 +401,12 @@ enum class MiscOp
|
||||
TableDrop = 0x0d,
|
||||
TableCopy = 0x0e,
|
||||
|
||||
// Generalized tables (reftypes proposal). Note, these are unofficial.
|
||||
TableGrow = 0x0f,
|
||||
TableGet = 0x10,
|
||||
TableSet = 0x11,
|
||||
TableSize = 0x12,
|
||||
|
||||
// Structure operations. Note, these are unofficial.
|
||||
StructNew = 0x50,
|
||||
StructGet = 0x51,
|
||||
@ -582,6 +589,7 @@ enum class FieldFlags {
|
||||
|
||||
static const unsigned MaxTypes = 1000000;
|
||||
static const unsigned MaxFuncs = 1000000;
|
||||
static const unsigned MaxTables = 100000; // TODO: get this into the shared limits spec
|
||||
static const unsigned MaxImports = 100000;
|
||||
static const unsigned MaxExports = 100000;
|
||||
static const unsigned MaxGlobals = 1000000;
|
||||
|
@ -421,7 +421,20 @@ table_tlsOffset(const TableDesc* table)
|
||||
bool
|
||||
table_isExternal(const TableDesc* table)
|
||||
{
|
||||
return table->external;
|
||||
// The external field was removed because it did not make sense in a
|
||||
// multi-table world in the presence of table.copy - all function tables now
|
||||
// use FunctionTableElem values, carrying both the code pointer and the
|
||||
// instance.
|
||||
//
|
||||
// If you meant to ask whether the function is represented as
|
||||
// 'FunctionTableElem' (what used to be called ExternalTableElem) then you
|
||||
// want to see if
|
||||
// table->kind==TableKind::AnyFunction || table->kind==TableKind::TypedFunction.
|
||||
//
|
||||
// If you meant to ask whether the table needs a JSObject representation,
|
||||
// then you want to see if table->importedOrExported is true.
|
||||
|
||||
MOZ_CRASH("FIXME. This field has been removed.");
|
||||
}
|
||||
|
||||
// Sig
|
||||
|
@ -1399,8 +1399,16 @@ ThunkedNativeToDescription(SymbolicAddress func)
|
||||
return "call to native table.copy function";
|
||||
case SymbolicAddress::TableDrop:
|
||||
return "call to native table.drop function";
|
||||
case SymbolicAddress::TableGet:
|
||||
return "call to native table.get function";
|
||||
case SymbolicAddress::TableGrow:
|
||||
return "call to native table.grow function";
|
||||
case SymbolicAddress::TableInit:
|
||||
return "call to native table.init function";
|
||||
case SymbolicAddress::TableSet:
|
||||
return "call to native table.set function";
|
||||
case SymbolicAddress::TableSize:
|
||||
return "call to native table.size function";
|
||||
case SymbolicAddress::PostBarrier:
|
||||
return "call to native GC postbarrier (in wasm)";
|
||||
case SymbolicAddress::StructNew:
|
||||
|
@ -301,10 +301,10 @@ ModuleGenerator::init(Metadata* maybeAsmJSMetadata)
|
||||
}
|
||||
|
||||
// Accumulate all exported functions, whether by explicit export or
|
||||
// implicitly by being an element of an external (imported or exported)
|
||||
// table or by being the start function. The FuncExportVector stored in
|
||||
// Metadata needs to be sorted (to allow O(log(n)) lookup at runtime) and
|
||||
// deduplicated, so use an intermediate vector to sort and de-duplicate.
|
||||
// implicitly by being an element of a function table or by being the start
|
||||
// function. The FuncExportVector stored in Metadata needs to be sorted (to
|
||||
// allow O(log(n)) lookup at runtime) and deduplicated, so use an
|
||||
// intermediate vector to sort and de-duplicate.
|
||||
|
||||
static_assert((uint64_t(MaxFuncs) << 1) < uint64_t(UINT32_MAX), "bit packing won't work");
|
||||
|
||||
@ -329,13 +329,21 @@ ModuleGenerator::init(Metadata* maybeAsmJSMetadata)
|
||||
}
|
||||
|
||||
for (const ElemSegment* seg : env_->elemSegments) {
|
||||
if (env_->tables[seg->tableIndex].external) {
|
||||
TableKind kind = !seg->active() ? TableKind::AnyFunction : env_->tables[seg->tableIndex].kind;
|
||||
switch (kind) {
|
||||
case TableKind::AnyFunction:
|
||||
if (!exportedFuncs.reserve(exportedFuncs.length() + seg->length())) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t funcIndex : seg->elemFuncIndices) {
|
||||
exportedFuncs.infallibleEmplaceBack(funcIndex, false);
|
||||
}
|
||||
break;
|
||||
case TableKind::TypedFunction:
|
||||
// asm.js functions are not exported.
|
||||
break;
|
||||
case TableKind::AnyRef:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,7 +235,7 @@ Instance::callImport(JSContext* cx, uint32_t funcImportIndex, unsigned argc, con
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
/* static */ int32_t /* 0 to signal trap; 1 to signal OK */
|
||||
Instance::callImport_void(Instance* instance, int32_t funcImportIndex, int32_t argc, uint64_t* argv)
|
||||
{
|
||||
JSContext* cx = TlsContext.get();
|
||||
@ -243,7 +243,7 @@ Instance::callImport_void(Instance* instance, int32_t funcImportIndex, int32_t a
|
||||
return instance->callImport(cx, funcImportIndex, argc, argv, &rval);
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
/* static */ int32_t /* 0 to signal trap; 1 to signal OK */
|
||||
Instance::callImport_i32(Instance* instance, int32_t funcImportIndex, int32_t argc, uint64_t* argv)
|
||||
{
|
||||
JSContext* cx = TlsContext.get();
|
||||
@ -255,7 +255,7 @@ Instance::callImport_i32(Instance* instance, int32_t funcImportIndex, int32_t ar
|
||||
return ToInt32(cx, rval, (int32_t*)argv);
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
/* static */ int32_t /* 0 to signal trap; 1 to signal OK */
|
||||
Instance::callImport_i64(Instance* instance, int32_t funcImportIndex, int32_t argc, uint64_t* argv)
|
||||
{
|
||||
JSContext* cx = TlsContext.get();
|
||||
@ -263,7 +263,7 @@ Instance::callImport_i64(Instance* instance, int32_t funcImportIndex, int32_t ar
|
||||
return false;
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
/* static */ int32_t /* 0 to signal trap; 1 to signal OK */
|
||||
Instance::callImport_f64(Instance* instance, int32_t funcImportIndex, int32_t argc, uint64_t* argv)
|
||||
{
|
||||
JSContext* cx = TlsContext.get();
|
||||
@ -291,7 +291,7 @@ ToRef(JSContext* cx, HandleValue val, void* addr)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
/* static */ int32_t /* 0 to signal trap; 1 to signal OK */
|
||||
Instance::callImport_ref(Instance* instance, int32_t funcImportIndex, int32_t argc, uint64_t* argv)
|
||||
{
|
||||
JSContext* cx = TlsContext.get();
|
||||
@ -302,7 +302,7 @@ Instance::callImport_ref(Instance* instance, int32_t funcImportIndex, int32_t ar
|
||||
return ToRef(cx, rval, argv);
|
||||
}
|
||||
|
||||
/* static */ uint32_t
|
||||
/* static */ uint32_t /* infallible */
|
||||
Instance::growMemory_i32(Instance* instance, uint32_t delta)
|
||||
{
|
||||
MOZ_ASSERT(!instance->isAsmJS());
|
||||
@ -319,7 +319,7 @@ Instance::growMemory_i32(Instance* instance, uint32_t delta)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* static */ uint32_t
|
||||
/* static */ uint32_t /* infallible */
|
||||
Instance::currentMemory_i32(Instance* instance)
|
||||
{
|
||||
// This invariant must hold when running Wasm code. Assert it here so we can
|
||||
@ -361,19 +361,19 @@ PerformWait(Instance* instance, uint32_t byteOffset, T value, int64_t timeout_ns
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
/* static */ int32_t /* -1 to signal trap; nonnegative result for ok */
|
||||
Instance::wait_i32(Instance* instance, uint32_t byteOffset, int32_t value, int64_t timeout_ns)
|
||||
{
|
||||
return PerformWait<int32_t>(instance, byteOffset, value, timeout_ns);
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
/* static */ int32_t /* -1 to signal trap; nonnegative result for ok */
|
||||
Instance::wait_i64(Instance* instance, uint32_t byteOffset, int64_t value, int64_t timeout_ns)
|
||||
{
|
||||
return PerformWait<int64_t>(instance, byteOffset, value, timeout_ns);
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
/* static */ int32_t /* -1 to signal trap; nonnegative for ok */
|
||||
Instance::wake(Instance* instance, uint32_t byteOffset, int32_t count)
|
||||
{
|
||||
JSContext* cx = TlsContext.get();
|
||||
@ -402,7 +402,7 @@ Instance::wake(Instance* instance, uint32_t byteOffset, int32_t count)
|
||||
return int32_t(woken);
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
/* static */ int32_t /* -1 to signal trap; 0 for ok */
|
||||
Instance::memCopy(Instance* instance, uint32_t dstByteOffset, uint32_t srcByteOffset, uint32_t len)
|
||||
{
|
||||
WasmMemoryObject* mem = instance->memory();
|
||||
@ -435,7 +435,7 @@ Instance::memCopy(Instance* instance, uint32_t dstByteOffset, uint32_t srcByteOf
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
/* static */ int32_t /* -1 to signal trap; 0 for ok */
|
||||
Instance::memDrop(Instance* instance, uint32_t segIndex)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(size_t(segIndex) < instance->passiveDataSegments_.length(),
|
||||
@ -455,7 +455,7 @@ Instance::memDrop(Instance* instance, uint32_t segIndex)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
/* static */ int32_t /* -1 to signal trap; 0 for ok */
|
||||
Instance::memFill(Instance* instance, uint32_t byteOffset, uint32_t value, uint32_t len)
|
||||
{
|
||||
WasmMemoryObject* mem = instance->memory();
|
||||
@ -484,7 +484,7 @@ Instance::memFill(Instance* instance, uint32_t byteOffset, uint32_t value, uint3
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
/* static */ int32_t /* -1 to signal trap; 0 for ok */
|
||||
Instance::memInit(Instance* instance, uint32_t dstOffset, uint32_t srcOffset,
|
||||
uint32_t len, uint32_t segIndex)
|
||||
{
|
||||
@ -538,16 +538,20 @@ Instance::memInit(Instance* instance, uint32_t dstOffset, uint32_t srcOffset,
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
Instance::tableCopy(Instance* instance, uint32_t dstOffset, uint32_t srcOffset, uint32_t len)
|
||||
/* static */ int32_t /* -1 to signal trap; 0 for ok */
|
||||
Instance::tableCopy(Instance* instance, uint32_t dstOffset, uint32_t srcOffset, uint32_t len,
|
||||
uint32_t dstTableIndex, uint32_t srcTableIndex)
|
||||
{
|
||||
const SharedTable& table = instance->tables()[0];
|
||||
uint32_t tableLen = table->length();
|
||||
const SharedTable& srcTable = instance->tables()[srcTableIndex];
|
||||
uint32_t srcTableLen = srcTable->length();
|
||||
|
||||
const SharedTable& dstTable = instance->tables()[dstTableIndex];
|
||||
uint32_t dstTableLen = dstTable->length();
|
||||
|
||||
if (len == 0) {
|
||||
// Even though the number of items to copy is zero, we must check
|
||||
// for valid offsets.
|
||||
if (dstOffset < tableLen && srcOffset < tableLen) {
|
||||
if (dstOffset < dstTableLen && srcOffset < srcTableLen) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
@ -557,18 +561,20 @@ Instance::tableCopy(Instance* instance, uint32_t dstOffset, uint32_t srcOffset,
|
||||
CheckedU32 highestSrcOffset = CheckedU32(srcOffset) + lenMinus1;
|
||||
if (highestDstOffset.isValid() &&
|
||||
highestSrcOffset.isValid() &&
|
||||
highestDstOffset.value() < tableLen &&
|
||||
highestSrcOffset.value() < tableLen)
|
||||
highestDstOffset.value() < dstTableLen &&
|
||||
highestSrcOffset.value() < srcTableLen)
|
||||
{
|
||||
// Actually do the copy, taking care to handle overlapping cases
|
||||
// correctly.
|
||||
if (dstOffset > srcOffset) {
|
||||
if (&srcTable == &dstTable && dstOffset > srcOffset) {
|
||||
for (uint32_t i = len; i > 0; i--) {
|
||||
table->copy(dstOffset + (i - 1), srcOffset + (i - 1));
|
||||
dstTable->copy(*srcTable, dstOffset + (i - 1), srcOffset + (i - 1));
|
||||
}
|
||||
} else if (dstOffset < srcOffset) {
|
||||
} else if (&srcTable == &dstTable && dstOffset == srcOffset) {
|
||||
// No-op
|
||||
} else {
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
table->copy(dstOffset + i, srcOffset + i);
|
||||
dstTable->copy(*srcTable, dstOffset + i, srcOffset + i);
|
||||
}
|
||||
}
|
||||
|
||||
@ -580,7 +586,7 @@ Instance::tableCopy(Instance* instance, uint32_t dstOffset, uint32_t srcOffset,
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
/* static */ int32_t /* -1 to signal trap; 0 for ok */
|
||||
Instance::tableDrop(Instance* instance, uint32_t segIndex)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(size_t(segIndex) < instance->passiveElemSegments_.length(),
|
||||
@ -601,9 +607,10 @@ Instance::tableDrop(Instance* instance, uint32_t segIndex)
|
||||
}
|
||||
|
||||
void
|
||||
Instance::initElems(const ElemSegment& seg, uint32_t dstOffset, uint32_t srcOffset, uint32_t len)
|
||||
Instance::initElems(uint32_t tableIndex, const ElemSegment& seg, uint32_t dstOffset,
|
||||
uint32_t srcOffset, uint32_t len)
|
||||
{
|
||||
Table& table = *tables_[seg.tableIndex];
|
||||
Table& table = *tables_[tableIndex];
|
||||
MOZ_ASSERT(dstOffset <= table.length());
|
||||
MOZ_ASSERT(len <= table.length() - dstOffset);
|
||||
|
||||
@ -635,18 +642,18 @@ Instance::initElems(const ElemSegment& seg, uint32_t dstOffset, uint32_t srcOffs
|
||||
const CodeRange& calleeCodeRange =
|
||||
calleeInstanceObj->getExportedFunctionCodeRange(fun, calleeTier);
|
||||
void* code = calleeInstance.codeBase(calleeTier) + calleeCodeRange.funcTableEntry();
|
||||
table.set(dstOffset + i, code, &calleeInstance);
|
||||
table.setAnyFunc(dstOffset + i, code, &calleeInstance);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
void* code = codeBaseTier + codeRanges[funcToCodeRange[funcIndex]].funcTableEntry();
|
||||
table.set(dstOffset + i, code, this);
|
||||
table.setAnyFunc(dstOffset + i, code, this);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
/* static */ int32_t /* -1 to signal trap; 0 for ok */
|
||||
Instance::tableInit(Instance* instance, uint32_t dstOffset, uint32_t srcOffset,
|
||||
uint32_t len, uint32_t segIndex)
|
||||
uint32_t len, uint32_t segIndex, uint32_t tableIndex)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(size_t(segIndex) < instance->passiveElemSegments_.length(),
|
||||
"ensured by validation");
|
||||
@ -659,7 +666,11 @@ Instance::tableInit(Instance* instance, uint32_t dstOffset, uint32_t srcOffset,
|
||||
|
||||
const ElemSegment& seg = *instance->passiveElemSegments_[segIndex];
|
||||
MOZ_RELEASE_ASSERT(!seg.active());
|
||||
const Table& table = *instance->tables()[0];
|
||||
const Table& table = *instance->tables()[tableIndex];
|
||||
|
||||
// Element segments cannot currently contain arbitrary values, and anyref
|
||||
// tables cannot be initialized from segments.
|
||||
MOZ_ASSERT(table.kind() == TableKind::AnyFunction);
|
||||
|
||||
// We are proposing to copy
|
||||
//
|
||||
@ -682,7 +693,7 @@ Instance::tableInit(Instance* instance, uint32_t dstOffset, uint32_t srcOffset,
|
||||
highestDstOffset.value() < table.length() &&
|
||||
highestSrcOffset.value() < seg.length())
|
||||
{
|
||||
instance->initElems(seg, dstOffset, srcOffset, len);
|
||||
instance->initElems(tableIndex, seg, dstOffset, srcOffset, len);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -691,7 +702,55 @@ Instance::tableInit(Instance* instance, uint32_t dstOffset, uint32_t srcOffset,
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
/* static */ void* /* (void*)-1 to signal trap; other pointer value for ok */
|
||||
Instance::tableGet(Instance* instance, uint32_t index, uint32_t tableIndex)
|
||||
{
|
||||
const Table& table = *instance->tables()[tableIndex];
|
||||
MOZ_RELEASE_ASSERT(table.kind() == TableKind::AnyRef);
|
||||
if (index >= table.length()) {
|
||||
JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr, JSMSG_WASM_TABLE_OUT_OF_BOUNDS);
|
||||
return (void*)-1;
|
||||
}
|
||||
return table.getAnyRef(index);
|
||||
}
|
||||
|
||||
/* static */ uint32_t /* infallible */
|
||||
Instance::tableGrow(Instance* instance, uint32_t delta, void* initValue, uint32_t tableIndex)
|
||||
{
|
||||
RootedObject obj(TlsContext.get(), (JSObject*)initValue);
|
||||
Table& table = *instance->tables()[tableIndex];
|
||||
MOZ_RELEASE_ASSERT(table.kind() == TableKind::AnyRef);
|
||||
|
||||
uint32_t oldSize = table.grow(delta, TlsContext.get());
|
||||
if (oldSize != uint32_t(-1) && initValue != nullptr) {
|
||||
for (uint32_t i = 0; i < delta; i++) {
|
||||
table.setAnyRef(oldSize + i, obj.get());
|
||||
}
|
||||
}
|
||||
return oldSize;
|
||||
}
|
||||
|
||||
/* static */ int32_t /* -1 to signal trap; 0 for ok */
|
||||
Instance::tableSet(Instance* instance, uint32_t index, void* value, uint32_t tableIndex)
|
||||
{
|
||||
Table& table = *instance->tables()[tableIndex];
|
||||
MOZ_RELEASE_ASSERT(table.kind() == TableKind::AnyRef);
|
||||
if (index >= table.length()) {
|
||||
JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr, JSMSG_WASM_TABLE_OUT_OF_BOUNDS);
|
||||
return -1;
|
||||
}
|
||||
table.setAnyRef(index, (JSObject*)value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* static */ uint32_t /* infallible */
|
||||
Instance::tableSize(Instance* instance, uint32_t tableIndex)
|
||||
{
|
||||
Table& table = *instance->tables()[tableIndex];
|
||||
return table.length();
|
||||
}
|
||||
|
||||
/* static */ void /* infallible */
|
||||
Instance::postBarrier(Instance* instance, gc::Cell** location)
|
||||
{
|
||||
MOZ_ASSERT(location);
|
||||
@ -704,7 +763,7 @@ Instance::postBarrier(Instance* instance, gc::Cell** location)
|
||||
// When we fail to allocate we return a nullptr; the wasm side must check this
|
||||
// and propagate it as an error.
|
||||
|
||||
/* static */ void*
|
||||
/* static */ void* /* null on OOM, otherwise a pointer */
|
||||
Instance::structNew(Instance* instance, uint32_t typeIndex)
|
||||
{
|
||||
JSContext* cx = TlsContext.get();
|
||||
@ -712,7 +771,7 @@ Instance::structNew(Instance* instance, uint32_t typeIndex)
|
||||
return TypedObject::createZeroed(cx, typeDescr);
|
||||
}
|
||||
|
||||
/* static */ void*
|
||||
/* static */ void* /* infallible */
|
||||
Instance::structNarrow(Instance* instance, uint32_t mustUnboxAnyref, uint32_t outputTypeIndex,
|
||||
void* maybeNullPtr)
|
||||
{
|
||||
@ -847,7 +906,7 @@ Instance::Instance(JSContext* cx,
|
||||
const TableDesc& td = metadata().tables[i];
|
||||
TableTls& table = tableTls(td);
|
||||
table.length = tables_[i]->length();
|
||||
table.base = tables_[i]->base();
|
||||
table.functionBase = tables_[i]->functionBase();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < metadata().globals.length(); i++) {
|
||||
@ -1246,13 +1305,30 @@ Instance::onMovingGrowMemory(uint8_t* prevMemoryBase)
|
||||
}
|
||||
|
||||
void
|
||||
Instance::onMovingGrowTable()
|
||||
Instance::onMovingGrowTable(const Table* theTable)
|
||||
{
|
||||
MOZ_ASSERT(!isAsmJS());
|
||||
MOZ_ASSERT(tables_.length() == 1);
|
||||
TableTls& table = tableTls(metadata().tables[0]);
|
||||
table.length = tables_[0]->length();
|
||||
table.base = tables_[0]->base();
|
||||
|
||||
// `theTable` has grown and we must update cached data for it. Importantly,
|
||||
// we can have cached those data in more than one location: we'll have
|
||||
// cached them once for each time the table was imported into this instance.
|
||||
//
|
||||
// When an instance is registered as an observer of a table it is only
|
||||
// registered once, regardless of how many times the table was imported.
|
||||
// Thus when a table is grown, onMovingGrowTable() is only invoked once for
|
||||
// the table.
|
||||
//
|
||||
// Ergo we must go through the entire list of tables in the instance here
|
||||
// and check for the table in all the cached-data slots; we can't exit after
|
||||
// the first hit.
|
||||
|
||||
for (uint32_t i = 0; i < tables_.length(); i++) {
|
||||
if (tables_[i] == theTable) {
|
||||
TableTls& table = tableTls(metadata().tables[i]);
|
||||
table.length = tables_[i]->length();
|
||||
table.functionBase = tables_[i]->functionBase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -150,12 +150,13 @@ class Instance
|
||||
// Called by Wasm(Memory|Table)Object when a moving resize occurs:
|
||||
|
||||
void onMovingGrowMemory(uint8_t* prevMemoryBase);
|
||||
void onMovingGrowTable();
|
||||
void onMovingGrowTable(const Table* theTable);
|
||||
|
||||
// Called to apply a single ElemSegment at a given offset, assuming
|
||||
// that all bounds validation has already been performed.
|
||||
|
||||
void initElems(const ElemSegment& seg, uint32_t dstOffset, uint32_t srcOffset, uint32_t len);
|
||||
void initElems(uint32_t tableIndex, const ElemSegment& seg, uint32_t dstOffset,
|
||||
uint32_t srcOffset, uint32_t len);
|
||||
|
||||
// Debugger support:
|
||||
|
||||
@ -188,10 +189,15 @@ class Instance
|
||||
static int32_t memFill(Instance* instance, uint32_t byteOffset, uint32_t value, uint32_t len);
|
||||
static int32_t memInit(Instance* instance, uint32_t dstOffset,
|
||||
uint32_t srcOffset, uint32_t len, uint32_t segIndex);
|
||||
static int32_t tableCopy(Instance* instance, uint32_t dstOffset, uint32_t srcOffset, uint32_t len);
|
||||
static int32_t tableCopy(Instance* instance, uint32_t dstOffset, uint32_t srcOffset, uint32_t len,
|
||||
uint32_t dstTableIndex, uint32_t srcTableIndex);
|
||||
static int32_t tableDrop(Instance* instance, uint32_t segIndex);
|
||||
static void* tableGet(Instance* instance, uint32_t index, uint32_t tableIndex);
|
||||
static uint32_t tableGrow(Instance* instance, uint32_t delta, void* initValue, uint32_t tableIndex);
|
||||
static int32_t tableSet(Instance* instance, uint32_t index, void* value, uint32_t tableIndex);
|
||||
static uint32_t tableSize(Instance* instance, uint32_t tableIndex);
|
||||
static int32_t tableInit(Instance* instance, uint32_t dstOffset,
|
||||
uint32_t srcOffset, uint32_t len, uint32_t segIndex);
|
||||
uint32_t srcOffset, uint32_t len, uint32_t segIndex, uint32_t tableIndex);
|
||||
static void postBarrier(Instance* instance, gc::Cell** location);
|
||||
static void* structNew(Instance* instance, uint32_t typeIndex);
|
||||
static void* structNarrow(Instance* instance, uint32_t mustUnboxAnyref, uint32_t outputTypeIndex,
|
||||
|
@ -1067,8 +1067,8 @@ class FunctionCompiler
|
||||
return true;
|
||||
}
|
||||
|
||||
bool callIndirect(uint32_t funcTypeIndex, MDefinition* index, const CallCompileState& call,
|
||||
MDefinition** def)
|
||||
bool callIndirect(uint32_t funcTypeIndex, uint32_t tableIndex, MDefinition* index,
|
||||
const CallCompileState& call, MDefinition** def)
|
||||
{
|
||||
if (inDeadCode()) {
|
||||
*def = nullptr;
|
||||
@ -1079,10 +1079,10 @@ class FunctionCompiler
|
||||
|
||||
CalleeDesc callee;
|
||||
if (env_.isAsmJS()) {
|
||||
MOZ_ASSERT(tableIndex == 0);
|
||||
MOZ_ASSERT(funcType.id.kind() == FuncTypeIdDescKind::None);
|
||||
const TableDesc& table = env_.tables[env_.asmJSSigToTableIndex[funcTypeIndex]];
|
||||
MOZ_ASSERT(IsPowerOfTwo(table.limits.initial));
|
||||
MOZ_ASSERT(!table.external);
|
||||
|
||||
MConstant* mask = MConstant::New(alloc(), Int32Value(table.limits.initial - 1));
|
||||
curBlock_->add(mask);
|
||||
@ -1093,8 +1093,7 @@ class FunctionCompiler
|
||||
callee = CalleeDesc::asmJSTable(table);
|
||||
} else {
|
||||
MOZ_ASSERT(funcType.id.kind() != FuncTypeIdDescKind::None);
|
||||
MOZ_ASSERT(env_.tables.length() == 1);
|
||||
const TableDesc& table = env_.tables[0];
|
||||
const TableDesc& table = env_.tables[tableIndex];
|
||||
callee = CalleeDesc::wasmTable(table, funcType.id);
|
||||
}
|
||||
|
||||
@ -2104,14 +2103,16 @@ EmitCallIndirect(FunctionCompiler& f, bool oldStyle)
|
||||
uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
|
||||
|
||||
uint32_t funcTypeIndex;
|
||||
uint32_t tableIndex;
|
||||
MDefinition* callee;
|
||||
DefVector args;
|
||||
if (oldStyle) {
|
||||
tableIndex = 0;
|
||||
if (!f.iter().readOldCallIndirect(&funcTypeIndex, &callee, &args)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!f.iter().readCallIndirect(&funcTypeIndex, &callee, &args)) {
|
||||
if (!f.iter().readCallIndirect(&funcTypeIndex, &tableIndex, &callee, &args)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -2128,7 +2129,7 @@ EmitCallIndirect(FunctionCompiler& f, bool oldStyle)
|
||||
}
|
||||
|
||||
MDefinition* def;
|
||||
if (!f.callIndirect(funcTypeIndex, callee, call, &def)) {
|
||||
if (!f.callIndirect(funcTypeIndex, tableIndex, callee, call, &def)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2986,7 +2987,9 @@ static bool
|
||||
EmitMemOrTableCopy(FunctionCompiler& f, bool isMem)
|
||||
{
|
||||
MDefinition* dst, *src, *len;
|
||||
if (!f.iter().readMemOrTableCopy(isMem, &dst, &src, &len)) {
|
||||
uint32_t dstTableIndex;
|
||||
uint32_t srcTableIndex;
|
||||
if (!f.iter().readMemOrTableCopy(isMem, &dstTableIndex, &dst, &srcTableIndex, &src, &len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -3014,7 +3017,22 @@ EmitMemOrTableCopy(FunctionCompiler& f, bool isMem)
|
||||
if (!f.passArg(len, ValType::I32, &args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isMem) {
|
||||
MDefinition* dti = f.constant(Int32Value(dstTableIndex), MIRType::Int32);
|
||||
if (!dti) {
|
||||
return false;
|
||||
}
|
||||
if (!f.passArg(dti, ValType::I32, &args)) {
|
||||
return false;
|
||||
}
|
||||
MDefinition* sti = f.constant(Int32Value(srcTableIndex), MIRType::Int32);
|
||||
if (!sti) {
|
||||
return false;
|
||||
}
|
||||
if (!f.passArg(sti, ValType::I32, &args)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!f.finishCall(&args)) {
|
||||
return false;
|
||||
}
|
||||
@ -3131,9 +3149,9 @@ EmitMemFill(FunctionCompiler& f)
|
||||
static bool
|
||||
EmitMemOrTableInit(FunctionCompiler& f, bool isMem)
|
||||
{
|
||||
uint32_t segIndexVal = 0;
|
||||
uint32_t segIndexVal = 0, dstTableIndex = 0;
|
||||
MDefinition* dstOff, *srcOff, *len;
|
||||
if (!f.iter().readMemOrTableInit(isMem, &segIndexVal, &dstOff, &srcOff, &len)) {
|
||||
if (!f.iter().readMemOrTableInit(isMem, &segIndexVal, &dstTableIndex, &dstOff, &srcOff, &len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -3166,7 +3184,15 @@ EmitMemOrTableInit(FunctionCompiler& f, bool isMem)
|
||||
if (!f.passArg(segIndex, ValType::I32, &args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isMem) {
|
||||
MDefinition* dti = f.constant(Int32Value(dstTableIndex), MIRType::Int32);
|
||||
if (!dti) {
|
||||
return false;
|
||||
}
|
||||
if (!f.passArg(dti, ValType::I32, &args)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!f.finishCall(&args)) {
|
||||
return false;
|
||||
}
|
||||
@ -3186,6 +3212,97 @@ EmitMemOrTableInit(FunctionCompiler& f, bool isMem)
|
||||
}
|
||||
#endif // ENABLE_WASM_BULKMEM_OPS
|
||||
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
// About these implementations: table.{get,grow,set} on table(anyfunc) is
|
||||
// rejected by the verifier, while table.{get,grow,set} on table(anyref)
|
||||
// requires gc_feature_opt_in and will always be handled by the baseline
|
||||
// compiler; we should never get here in that case.
|
||||
//
|
||||
// table.size must however be handled properly here.
|
||||
|
||||
static bool
|
||||
EmitTableGet(FunctionCompiler& f)
|
||||
{
|
||||
uint32_t tableIndex;
|
||||
MDefinition* index;
|
||||
if (!f.iter().readTableGet(&tableIndex, &index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_CRASH("Should not happen"); // See above
|
||||
}
|
||||
|
||||
static bool
|
||||
EmitTableGrow(FunctionCompiler& f)
|
||||
{
|
||||
uint32_t tableIndex;
|
||||
MDefinition* delta;
|
||||
MDefinition* initValue;
|
||||
if (!f.iter().readTableGrow(&tableIndex, &delta, &initValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_CRASH("Should not happen"); // See above
|
||||
}
|
||||
|
||||
static bool
|
||||
EmitTableSet(FunctionCompiler& f)
|
||||
{
|
||||
uint32_t tableIndex;
|
||||
MDefinition* index;
|
||||
MDefinition* value;
|
||||
if (!f.iter().readTableSet(&tableIndex, &index, &value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_CRASH("Should not happen"); // See above
|
||||
}
|
||||
|
||||
static bool
|
||||
EmitTableSize(FunctionCompiler& f)
|
||||
{
|
||||
uint32_t tableIndex;
|
||||
if (!f.iter().readTableSize(&tableIndex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (f.inDeadCode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
|
||||
|
||||
CallCompileState args(f, lineOrBytecode);
|
||||
if (!f.startCall(&args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!f.passInstance(&args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MDefinition* tableIndexArg = f.constant(Int32Value(tableIndex), MIRType::Int32);
|
||||
if (!tableIndexArg) {
|
||||
return false;
|
||||
}
|
||||
if (!f.passArg(tableIndexArg, ValType::I32, &args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!f.finishCall(&args)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MDefinition* ret;
|
||||
if (!f.builtinInstanceMethodCall(SymbolicAddress::TableSize, args, ValType::I32, &ret)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
f.iter().setResult(ret);
|
||||
return true;
|
||||
}
|
||||
#endif // ENABLE_WASM_GENERALIZED_TABLES
|
||||
|
||||
static bool
|
||||
EmitBodyExprs(FunctionCompiler& f)
|
||||
{
|
||||
@ -3626,6 +3743,16 @@ EmitBodyExprs(FunctionCompiler& f)
|
||||
case uint16_t(MiscOp::TableInit):
|
||||
CHECK(EmitMemOrTableInit(f, /*isMem=*/false));
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
case uint16_t(MiscOp::TableGet):
|
||||
CHECK(EmitTableGet(f));
|
||||
case uint16_t(MiscOp::TableGrow):
|
||||
CHECK(EmitTableGrow(f));
|
||||
case uint16_t(MiscOp::TableSet):
|
||||
CHECK(EmitTableSet(f));
|
||||
case uint16_t(MiscOp::TableSize):
|
||||
CHECK(EmitTableSize(f));
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GC
|
||||
case uint16_t(MiscOp::StructNew):
|
||||
case uint16_t(MiscOp::StructGet):
|
||||
|
@ -240,7 +240,7 @@ GetImports(JSContext* cx,
|
||||
const Module& module,
|
||||
HandleObject importObj,
|
||||
MutableHandle<FunctionVector> funcImports,
|
||||
MutableHandleWasmTableObject tableImport,
|
||||
WasmTableObjectVector& tableImports,
|
||||
MutableHandleWasmMemoryObject memoryImport,
|
||||
WasmGlobalObjectVector& globalObjs,
|
||||
MutableHandleValVector globalImportValues)
|
||||
@ -254,6 +254,8 @@ GetImports(JSContext* cx,
|
||||
|
||||
uint32_t globalIndex = 0;
|
||||
const GlobalDescVector& globals = metadata.globals;
|
||||
uint32_t tableIndex = 0;
|
||||
const TableDescVector& tables = metadata.tables;
|
||||
for (const Import& import : imports) {
|
||||
RootedValue v(cx);
|
||||
if (!GetProperty(cx, importObj, import.module.get(), &v)) {
|
||||
@ -284,12 +286,20 @@ GetImports(JSContext* cx,
|
||||
break;
|
||||
}
|
||||
case DefinitionKind::Table: {
|
||||
const uint32_t index = tableIndex++;
|
||||
if (!v.isObject() || !v.toObject().is<WasmTableObject>()) {
|
||||
return ThrowBadImportType(cx, import.field.get(), "Table");
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!tableImport);
|
||||
tableImport.set(&v.toObject().as<WasmTableObject>());
|
||||
RootedWasmTableObject obj(cx, &v.toObject().as<WasmTableObject>());
|
||||
if (obj->table().kind() != tables[index].kind) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_TBL_TYPE_LINK);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tableImports.append(obj)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DefinitionKind::Memory: {
|
||||
@ -311,11 +321,11 @@ GetImports(JSContext* cx,
|
||||
RootedWasmGlobalObject obj(cx, &v.toObject().as<WasmGlobalObject>());
|
||||
|
||||
if (obj->isMutable() != global.isMutable()) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_MUT_LINK);
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GLOB_MUT_LINK);
|
||||
return false;
|
||||
}
|
||||
if (obj->type() != global.type()) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_TYPE_LINK);
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GLOB_TYPE_LINK);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -343,7 +353,7 @@ GetImports(JSContext* cx,
|
||||
}
|
||||
|
||||
if (global.isMutable()) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_MUT_LINK);
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GLOB_MUT_LINK);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -429,16 +439,16 @@ wasm::Eval(JSContext* cx, Handle<TypedArrayObject*> code, HandleObject importObj
|
||||
}
|
||||
|
||||
Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
|
||||
RootedWasmTableObject table(cx);
|
||||
Rooted<WasmTableObjectVector> tables(cx);
|
||||
RootedWasmMemoryObject memory(cx);
|
||||
Rooted<WasmGlobalObjectVector> globalObjs(cx);
|
||||
|
||||
RootedValVector globals(cx);
|
||||
if (!GetImports(cx, *module, importObj, &funcs, &table, &memory, globalObjs.get(), &globals)) {
|
||||
if (!GetImports(cx, *module, importObj, &funcs, tables.get(), &memory, globalObjs.get(), &globals)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return module->instantiate(cx, funcs, table, memory, globals, globalObjs.get(), nullptr,
|
||||
return module->instantiate(cx, funcs, tables.get(), memory, globals, globalObjs.get(), nullptr,
|
||||
instanceObj);
|
||||
}
|
||||
|
||||
@ -1335,16 +1345,16 @@ Instantiate(JSContext* cx, const Module& module, HandleObject importObj,
|
||||
RootedObject instanceProto(cx, &cx->global()->getPrototype(JSProto_WasmInstance).toObject());
|
||||
|
||||
Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
|
||||
RootedWasmTableObject table(cx);
|
||||
Rooted<WasmTableObjectVector> tables(cx);
|
||||
RootedWasmMemoryObject memory(cx);
|
||||
Rooted<WasmGlobalObjectVector> globalObjs(cx);
|
||||
|
||||
RootedValVector globals(cx);
|
||||
if (!GetImports(cx, module, importObj, &funcs, &table, &memory, globalObjs.get(), &globals)) {
|
||||
if (!GetImports(cx, module, importObj, &funcs, tables.get(), &memory, globalObjs.get(), &globals)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return module.instantiate(cx, funcs, table, memory, globals, globalObjs.get(), instanceProto,
|
||||
return module.instantiate(cx, funcs, tables.get(), memory, globals, globalObjs.get(), instanceProto,
|
||||
instanceObj);
|
||||
}
|
||||
|
||||
@ -2065,7 +2075,7 @@ WasmTableObject::trace(JSTracer* trc, JSObject* obj)
|
||||
}
|
||||
|
||||
/* static */ WasmTableObject*
|
||||
WasmTableObject::create(JSContext* cx, const Limits& limits)
|
||||
WasmTableObject::create(JSContext* cx, const Limits& limits, TableKind tableKind)
|
||||
{
|
||||
RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmTable).toObject());
|
||||
|
||||
@ -2077,11 +2087,7 @@ WasmTableObject::create(JSContext* cx, const Limits& limits)
|
||||
|
||||
MOZ_ASSERT(obj->isNewborn());
|
||||
|
||||
TableDesc td(TableKind::AnyFunction, limits);
|
||||
td.external = true;
|
||||
#ifdef WASM_PRIVATE_REFTYPES
|
||||
td.importedOrExported = true;
|
||||
#endif
|
||||
TableDesc td(tableKind, limits, /*importedOrExported=*/true);
|
||||
|
||||
SharedTable table = Table::create(cx, td, obj);
|
||||
if (!table) {
|
||||
@ -2135,8 +2141,23 @@ WasmTableObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!StringEqualsAscii(elementLinearStr, "anyfunc")) {
|
||||
TableKind tableKind;
|
||||
if (StringEqualsAscii(elementLinearStr, "anyfunc")) {
|
||||
tableKind = TableKind::AnyFunction;
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
} else if (StringEqualsAscii(elementLinearStr, "anyref")) {
|
||||
if (!cx->options().wasmGc()) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT);
|
||||
return false;
|
||||
}
|
||||
tableKind = TableKind::AnyRef;
|
||||
#endif
|
||||
} else {
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT_GENERALIZED);
|
||||
#else
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2147,7 +2168,7 @@ WasmTableObject::construct(JSContext* cx, unsigned argc, Value* vp)
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedWasmTableObject table(cx, WasmTableObject::create(cx, limits));
|
||||
RootedWasmTableObject table(cx, WasmTableObject::create(cx, limits, tableKind));
|
||||
if (!table) {
|
||||
return false;
|
||||
}
|
||||
@ -2212,22 +2233,34 @@ WasmTableObject::getImpl(JSContext* cx, const CallArgs& args)
|
||||
return false;
|
||||
}
|
||||
|
||||
ExternalTableElem& elem = table.externalArray()[index];
|
||||
if (!elem.code) {
|
||||
args.rval().setNull();
|
||||
return true;
|
||||
switch (table.kind()) {
|
||||
case TableKind::AnyFunction: {
|
||||
const FunctionTableElem& elem = table.getAnyFunc(index);
|
||||
if (!elem.code) {
|
||||
args.rval().setNull();
|
||||
return true;
|
||||
}
|
||||
|
||||
Instance& instance = *elem.tls->instance;
|
||||
const CodeRange& codeRange = *instance.code().lookupFuncRange(elem.code);
|
||||
|
||||
RootedWasmInstanceObject instanceObj(cx, instance.object());
|
||||
RootedFunction fun(cx);
|
||||
if (!instanceObj->getExportedFunction(cx, instanceObj, codeRange.funcIndex(), &fun)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*fun);
|
||||
break;
|
||||
}
|
||||
case TableKind::AnyRef: {
|
||||
args.rval().setObjectOrNull(table.getAnyRef(index));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
MOZ_CRASH("Unexpected table kind");
|
||||
}
|
||||
}
|
||||
|
||||
Instance& instance = *elem.tls->instance;
|
||||
const CodeRange& codeRange = *instance.code().lookupFuncRange(elem.code);
|
||||
|
||||
RootedWasmInstanceObject instanceObj(cx, instance.object());
|
||||
RootedFunction fun(cx);
|
||||
if (!instanceObj->getExportedFunction(cx, instanceObj, codeRange.funcIndex(), &fun)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*fun);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2253,30 +2286,50 @@ WasmTableObject::setImpl(JSContext* cx, const CallArgs& args)
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedFunction value(cx);
|
||||
if (!IsExportedFunction(args[1], &value) && !args[1].isNull()) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_TABLE_VALUE);
|
||||
return false;
|
||||
}
|
||||
switch (table.kind()) {
|
||||
case TableKind::AnyFunction: {
|
||||
RootedFunction value(cx);
|
||||
if (!IsExportedFunction(args[1], &value) && !args[1].isNull()) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_TABLE_VALUE);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
RootedWasmInstanceObject instanceObj(cx, ExportedFunctionToInstanceObject(value));
|
||||
uint32_t funcIndex = ExportedFunctionToFuncIndex(value);
|
||||
if (value) {
|
||||
RootedWasmInstanceObject instanceObj(cx, ExportedFunctionToInstanceObject(value));
|
||||
uint32_t funcIndex = ExportedFunctionToFuncIndex(value);
|
||||
|
||||
#ifdef DEBUG
|
||||
RootedFunction f(cx);
|
||||
MOZ_ASSERT(instanceObj->getExportedFunction(cx, instanceObj, funcIndex, &f));
|
||||
MOZ_ASSERT(value == f);
|
||||
RootedFunction f(cx);
|
||||
MOZ_ASSERT(instanceObj->getExportedFunction(cx, instanceObj, funcIndex, &f));
|
||||
MOZ_ASSERT(value == f);
|
||||
#endif
|
||||
|
||||
Instance& instance = instanceObj->instance();
|
||||
Tier tier = instance.code().bestTier();
|
||||
const MetadataTier& metadata = instance.metadata(tier);
|
||||
const CodeRange& codeRange = metadata.codeRange(metadata.lookupFuncExport(funcIndex));
|
||||
void* code = instance.codeBase(tier) + codeRange.funcTableEntry();
|
||||
table.set(index, code, &instance);
|
||||
} else {
|
||||
table.setNull(index);
|
||||
Instance& instance = instanceObj->instance();
|
||||
Tier tier = instance.code().bestTier();
|
||||
const MetadataTier& metadata = instance.metadata(tier);
|
||||
const CodeRange& codeRange = metadata.codeRange(metadata.lookupFuncExport(funcIndex));
|
||||
void* code = instance.codeBase(tier) + codeRange.funcTableEntry();
|
||||
table.setAnyFunc(index, code, &instance);
|
||||
} else {
|
||||
table.setNull(index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TableKind::AnyRef: {
|
||||
if (args[1].isNull()) {
|
||||
table.setNull(index);
|
||||
} else {
|
||||
RootedObject value(cx, ToObject(cx, args[1]));
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
table.setAnyRef(index, value.get());
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
MOZ_CRASH("Unexpected table kind");
|
||||
}
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
|
@ -359,7 +359,8 @@ class WasmTableObject : public NativeObject
|
||||
// Note that, after creation, a WasmTableObject's table() is not initialized
|
||||
// and must be initialized before use.
|
||||
|
||||
static WasmTableObject* create(JSContext* cx, const wasm::Limits& limits);
|
||||
static WasmTableObject* create(JSContext* cx, const wasm::Limits& limits,
|
||||
wasm::TableKind tableKind);
|
||||
wasm::Table& table() const;
|
||||
};
|
||||
|
||||
|
@ -631,7 +631,7 @@ Module::initSegments(JSContext* cx,
|
||||
for (const ElemSegment* seg : elemSegments_) {
|
||||
if (seg->active()) {
|
||||
uint32_t offset = EvaluateInitExpr(globalImportValues, seg->offset());
|
||||
instance.initElems(*seg, offset, 0, seg->length());
|
||||
instance.initElems(seg->tableIndex, *seg, offset, 0, seg->length());
|
||||
}
|
||||
}
|
||||
|
||||
@ -803,53 +803,85 @@ Module::instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) c
|
||||
}
|
||||
|
||||
bool
|
||||
Module::instantiateTable(JSContext* cx, MutableHandleWasmTableObject tableObj,
|
||||
SharedTableVector* tables) const
|
||||
Module::instantiateImportedTable(JSContext* cx, const TableDesc& td, Handle<WasmTableObject*> tableObj,
|
||||
WasmTableObjectVector* tableObjs, SharedTableVector* tables) const
|
||||
{
|
||||
if (tableObj) {
|
||||
MOZ_ASSERT(!metadata().isAsmJS());
|
||||
MOZ_ASSERT(tableObj);
|
||||
MOZ_ASSERT(!metadata().isAsmJS());
|
||||
|
||||
MOZ_ASSERT(metadata().tables.length() == 1);
|
||||
const TableDesc& td = metadata().tables[0];
|
||||
MOZ_ASSERT(td.external);
|
||||
Table& table = tableObj->table();
|
||||
if (!CheckLimits(cx, td.limits.initial, td.limits.maximum, table.length(), table.maximum(),
|
||||
metadata().isAsmJS(), "Table"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Table& table = tableObj->table();
|
||||
if (!CheckLimits(cx, td.limits.initial, td.limits.maximum, table.length(), table.maximum(),
|
||||
metadata().isAsmJS(), "Table")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tables->append(&table)) {
|
||||
ReportOutOfMemory(cx);
|
||||
if (!tables->append(&table)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tableObjs->append(tableObj)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Module::instantiateLocalTable(JSContext* cx, const TableDesc& td, WasmTableObjectVector* tableObjs,
|
||||
SharedTableVector* tables) const
|
||||
{
|
||||
SharedTable table;
|
||||
Rooted<WasmTableObject*> tableObj(cx);
|
||||
if (td.importedOrExported) {
|
||||
tableObj.set(WasmTableObject::create(cx, td.limits, td.kind));
|
||||
if (!tableObj) {
|
||||
return false;
|
||||
}
|
||||
table = &tableObj->table();
|
||||
} else {
|
||||
for (const TableDesc& td : metadata().tables) {
|
||||
SharedTable table;
|
||||
if (td.external) {
|
||||
MOZ_ASSERT(!tableObj);
|
||||
MOZ_ASSERT(td.kind == TableKind::AnyFunction);
|
||||
|
||||
tableObj.set(WasmTableObject::create(cx, td.limits));
|
||||
if (!tableObj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
table = &tableObj->table();
|
||||
} else {
|
||||
table = Table::create(cx, td, /* HandleWasmTableObject = */ nullptr);
|
||||
if (!table) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tables->emplaceBack(table)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
table = Table::create(cx, td, /* HandleWasmTableObject = */ nullptr);
|
||||
if (!table) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Note, appending a null pointer for non-exported local tables.
|
||||
if (!tableObjs->append(tableObj.get())) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tables->emplaceBack(table)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Module::instantiateTables(JSContext* cx,
|
||||
WasmTableObjectVector& tableImports,
|
||||
MutableHandle<WasmTableObjectVector> tableObjs,
|
||||
SharedTableVector* tables) const
|
||||
{
|
||||
uint32_t tableIndex = 0;
|
||||
for (const TableDesc& td : metadata().tables) {
|
||||
if (tableIndex < tableImports.length()) {
|
||||
Rooted<WasmTableObject*> tableObj(cx, tableImports[tableIndex]);
|
||||
if (!instantiateImportedTable(cx, td, tableObj, &tableObjs.get(), tables)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!instantiateLocalTable(cx, td, &tableObjs.get(), tables)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
tableIndex++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1032,7 +1064,7 @@ static bool
|
||||
CreateExportObject(JSContext* cx,
|
||||
HandleWasmInstanceObject instanceObj,
|
||||
Handle<FunctionVector> funcImports,
|
||||
HandleWasmTableObject tableObj,
|
||||
const WasmTableObjectVector& tableObjs,
|
||||
HandleWasmMemoryObject memoryObj,
|
||||
const WasmGlobalObjectVector& globalObjs,
|
||||
const ExportVector& exports)
|
||||
@ -1074,7 +1106,7 @@ CreateExportObject(JSContext* cx,
|
||||
}
|
||||
break;
|
||||
case DefinitionKind::Table:
|
||||
val = ObjectValue(*tableObj);
|
||||
val = ObjectValue(*tableObjs[exp.tableIndex()]);
|
||||
break;
|
||||
case DefinitionKind::Memory:
|
||||
val = ObjectValue(*memoryObj);
|
||||
@ -1246,7 +1278,7 @@ Module::makeStructTypeDescrs(JSContext* cx,
|
||||
bool
|
||||
Module::instantiate(JSContext* cx,
|
||||
Handle<FunctionVector> funcImports,
|
||||
HandleWasmTableObject tableImport,
|
||||
WasmTableObjectVector& tableImports,
|
||||
HandleWasmMemoryObject memoryImport,
|
||||
HandleValVector globalImportValues,
|
||||
WasmGlobalObjectVector& globalObjs,
|
||||
@ -1264,9 +1296,12 @@ Module::instantiate(JSContext* cx,
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedWasmTableObject table(cx, tableImport);
|
||||
// Note that tableObjs is sparse: it will be null in slots that contain
|
||||
// tables that are neither exported nor imported.
|
||||
|
||||
Rooted<WasmTableObjectVector> tableObjs(cx);
|
||||
SharedTableVector tables;
|
||||
if (!instantiateTable(cx, &table, &tables)) {
|
||||
if (!instantiateTables(cx, tableImports, &tableObjs, &tables)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1323,7 +1358,7 @@ Module::instantiate(JSContext* cx,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CreateExportObject(cx, instance, funcImports, table, memory, globalObjs, exports_)) {
|
||||
if (!CreateExportObject(cx, instance, funcImports, tableObjs.get(), memory, globalObjs, exports_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -88,9 +88,18 @@ class Module : public JS::WasmModule
|
||||
|
||||
bool instantiateFunctions(JSContext* cx, Handle<FunctionVector> funcImports) const;
|
||||
bool instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) const;
|
||||
bool instantiateTable(JSContext* cx,
|
||||
MutableHandleWasmTableObject table,
|
||||
SharedTableVector* tables) const;
|
||||
bool instantiateImportedTable(JSContext* cx,
|
||||
const TableDesc& td,
|
||||
Handle<WasmTableObject*> table,
|
||||
WasmTableObjectVector* tableObjs,
|
||||
SharedTableVector* tables) const;
|
||||
bool instantiateLocalTable(JSContext* cx,
|
||||
const TableDesc& td,
|
||||
WasmTableObjectVector* tableObjs,
|
||||
SharedTableVector* tables) const;
|
||||
bool instantiateTables(JSContext* cx, WasmTableObjectVector& tableImports,
|
||||
MutableHandle<WasmTableObjectVector> tableObjs,
|
||||
SharedTableVector* tables) const;
|
||||
bool instantiateGlobals(JSContext* cx, HandleValVector globalImportValues,
|
||||
WasmGlobalObjectVector& globalObjs) const;
|
||||
bool initSegments(JSContext* cx,
|
||||
@ -145,7 +154,7 @@ class Module : public JS::WasmModule
|
||||
|
||||
bool instantiate(JSContext* cx,
|
||||
Handle<FunctionVector> funcImports,
|
||||
HandleWasmTableObject tableImport,
|
||||
WasmTableObjectVector& tableImport,
|
||||
HandleWasmMemoryObject memoryImport,
|
||||
HandleValVector globalImportValues,
|
||||
WasmGlobalObjectVector& globalObjs,
|
||||
|
@ -22,6 +22,14 @@ using namespace js;
|
||||
using namespace js::jit;
|
||||
using namespace js::wasm;
|
||||
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
// Actually we depend only on the reftypes proposal; this guard will change once
|
||||
// reftypes and GC are pried apart properly.
|
||||
# ifndef ENABLE_WASM_GC
|
||||
# error "Generalized tables require the GC feature"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
# ifdef ENABLE_WASM_GC
|
||||
@ -34,6 +42,11 @@ using namespace js::wasm;
|
||||
# else
|
||||
# define WASM_BULK_OP(code) break
|
||||
# endif
|
||||
# ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
# define WASM_TABLE_OP(code) return code
|
||||
# else
|
||||
# define WASM_TABLE_OP(code) break
|
||||
# endif
|
||||
# ifdef ENABLE_WASM_THREAD_OPS
|
||||
# define WASM_THREAD_OP(code) return code
|
||||
# else
|
||||
@ -284,6 +297,14 @@ wasm::Classify(OpBytes op)
|
||||
case MiscOp::MemInit:
|
||||
case MiscOp::TableInit:
|
||||
WASM_BULK_OP(OpKind::MemOrTableInit);
|
||||
case MiscOp::TableGet:
|
||||
WASM_TABLE_OP(OpKind::TableGet);
|
||||
case MiscOp::TableGrow:
|
||||
WASM_TABLE_OP(OpKind::TableGrow);
|
||||
case MiscOp::TableSet:
|
||||
WASM_TABLE_OP(OpKind::TableSet);
|
||||
case MiscOp::TableSize:
|
||||
WASM_TABLE_OP(OpKind::TableSize);
|
||||
case MiscOp::StructNew:
|
||||
WASM_GC_OP(OpKind::StructNew);
|
||||
case MiscOp::StructGet:
|
||||
@ -428,6 +449,7 @@ wasm::Classify(OpBytes op)
|
||||
|
||||
# undef WASM_GC_OP
|
||||
# undef WASM_BULK_OP
|
||||
# undef WASM_TABLE_OP
|
||||
# undef WASM_THREAD_OP
|
||||
|
||||
#endif
|
||||
|
@ -191,6 +191,10 @@ enum class OpKind {
|
||||
MemOrTableDrop,
|
||||
MemFill,
|
||||
MemOrTableInit,
|
||||
TableGet,
|
||||
TableGrow,
|
||||
TableSet,
|
||||
TableSize,
|
||||
RefNull,
|
||||
StructNew,
|
||||
StructGet,
|
||||
@ -542,7 +546,8 @@ class MOZ_STACK_CLASS OpIter : private Policy
|
||||
MOZ_MUST_USE bool readF64Const(double* f64);
|
||||
MOZ_MUST_USE bool readRefNull(ValType* type);
|
||||
MOZ_MUST_USE bool readCall(uint32_t* calleeIndex, ValueVector* argValues);
|
||||
MOZ_MUST_USE bool readCallIndirect(uint32_t* funcTypeIndex, Value* callee, ValueVector* argValues);
|
||||
MOZ_MUST_USE bool readCallIndirect(uint32_t* funcTypeIndex, uint32_t* tableIndex, Value* callee,
|
||||
ValueVector* argValues);
|
||||
MOZ_MUST_USE bool readOldCallDirect(uint32_t numFuncImports, uint32_t* funcIndex,
|
||||
ValueVector* argValues);
|
||||
MOZ_MUST_USE bool readOldCallIndirect(uint32_t* funcTypeIndex, Value* callee, ValueVector* argValues);
|
||||
@ -568,12 +573,16 @@ class MOZ_STACK_CLASS OpIter : private Policy
|
||||
uint32_t byteSize,
|
||||
Value* oldValue,
|
||||
Value* newValue);
|
||||
MOZ_MUST_USE bool readMemOrTableCopy(bool isMem,
|
||||
Value* dst, Value* src, Value* len);
|
||||
MOZ_MUST_USE bool readMemOrTableCopy(bool isMem, uint32_t* dstMemOrTableIndex, Value* dst,
|
||||
uint32_t* srcMemOrTableIndex, Value* src, Value* len);
|
||||
MOZ_MUST_USE bool readMemOrTableDrop(bool isMem, uint32_t* segIndex);
|
||||
MOZ_MUST_USE bool readMemFill(Value* start, Value* val, Value* len);
|
||||
MOZ_MUST_USE bool readMemOrTableInit(bool isMem, uint32_t* segIndex,
|
||||
Value* dst, Value* src, Value* len);
|
||||
uint32_t* dstTableIndex, Value* dst, Value* src, Value* len);
|
||||
MOZ_MUST_USE bool readTableGet(uint32_t* tableIndex, Value* index);
|
||||
MOZ_MUST_USE bool readTableGrow(uint32_t* tableIndex, Value* delta, Value* initValue);
|
||||
MOZ_MUST_USE bool readTableSet(uint32_t* tableIndex, Value* index, Value* value);
|
||||
MOZ_MUST_USE bool readTableSize(uint32_t* tableIndex);
|
||||
MOZ_MUST_USE bool readStructNew(uint32_t* typeIndex, ValueVector* argValues);
|
||||
MOZ_MUST_USE bool readStructGet(uint32_t* typeIndex, uint32_t* fieldIndex, Value* ptr);
|
||||
MOZ_MUST_USE bool readStructSet(uint32_t* typeIndex, uint32_t* fieldIndex, Value* ptr, Value* val);
|
||||
@ -1780,13 +1789,10 @@ OpIter<Policy>::readCall(uint32_t* funcTypeIndex, ValueVector* argValues)
|
||||
|
||||
template <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::readCallIndirect(uint32_t* funcTypeIndex, Value* callee, ValueVector* argValues)
|
||||
OpIter<Policy>::readCallIndirect(uint32_t* funcTypeIndex, uint32_t* tableIndex, Value* callee, ValueVector* argValues)
|
||||
{
|
||||
MOZ_ASSERT(Classify(op_) == OpKind::CallIndirect);
|
||||
|
||||
if (!env_.tables.length()) {
|
||||
return fail("can't call_indirect without a table");
|
||||
}
|
||||
MOZ_ASSERT(funcTypeIndex != tableIndex);
|
||||
|
||||
if (!readVarU32(funcTypeIndex)) {
|
||||
return fail("unable to read call_indirect signature index");
|
||||
@ -1801,10 +1807,26 @@ OpIter<Policy>::readCallIndirect(uint32_t* funcTypeIndex, Value* callee, ValueVe
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flags != uint8_t(MemoryTableFlags::Default)) {
|
||||
*tableIndex = 0;
|
||||
if (flags == uint8_t(MemoryTableFlags::HasTableIndex)) {
|
||||
if (!readVarU32(tableIndex))
|
||||
return false;
|
||||
} else if (flags != uint8_t(MemoryTableFlags::Default)) {
|
||||
return fail("unexpected flags");
|
||||
}
|
||||
|
||||
if (*tableIndex >= env_.tables.length()) {
|
||||
// Special case this for improved user experience.
|
||||
if (!env_.tables.length()) {
|
||||
return fail("can't call_indirect without a table");
|
||||
}
|
||||
return fail("table index out of range for call_indirect");
|
||||
}
|
||||
|
||||
if (env_.tables[*tableIndex].kind != TableKind::AnyFunction) {
|
||||
return fail("indirect calls must go through a table of 'anyfunc'");
|
||||
}
|
||||
|
||||
if (!popWithType(ValType::I32, callee)) {
|
||||
return false;
|
||||
}
|
||||
@ -1816,7 +1838,7 @@ OpIter<Policy>::readCallIndirect(uint32_t* funcTypeIndex, Value* callee, ValueVe
|
||||
const FuncType& funcType = env_.types[*funcTypeIndex].funcType();
|
||||
|
||||
#ifdef WASM_PRIVATE_REFTYPES
|
||||
if (env_.tables[0].importedOrExported && funcType.exposesRef()) {
|
||||
if (env_.tables[*tableIndex].importedOrExported && funcType.exposesRef()) {
|
||||
return fail("cannot expose reference type");
|
||||
}
|
||||
#endif
|
||||
@ -2041,28 +2063,42 @@ OpIter<Policy>::readAtomicCmpXchg(LinearMemoryAddress<Value>* addr, ValType resu
|
||||
|
||||
template <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::readMemOrTableCopy(bool isMem, Value* dst, Value* src, Value* len)
|
||||
OpIter<Policy>::readMemOrTableCopy(bool isMem, uint32_t* dstMemOrTableIndex, Value* dst,
|
||||
uint32_t* srcMemOrTableIndex, Value* src, Value* len)
|
||||
{
|
||||
MOZ_ASSERT(Classify(op_) == OpKind::MemOrTableCopy);
|
||||
MOZ_ASSERT(dstMemOrTableIndex != srcMemOrTableIndex);
|
||||
|
||||
*dstMemOrTableIndex = 0;
|
||||
*srcMemOrTableIndex = 0;
|
||||
|
||||
uint32_t memOrTableFlags;
|
||||
if (!readVarU32(&memOrTableFlags)) {
|
||||
return fail(isMem ? "unable to read memory flags" : "unable to read table flags");
|
||||
}
|
||||
if (!isMem && (memOrTableFlags & uint32_t(MemoryTableFlags::HasTableIndex))) {
|
||||
if (!readVarU32(dstMemOrTableIndex)) {
|
||||
return false;
|
||||
}
|
||||
if (!readVarU32(srcMemOrTableIndex)) {
|
||||
return false;
|
||||
}
|
||||
memOrTableFlags ^= uint32_t(MemoryTableFlags::HasTableIndex);
|
||||
}
|
||||
if (memOrTableFlags != uint32_t(MemoryTableFlags::Default)) {
|
||||
return fail(isMem ? "unrecognized memory flags" : "unrecognized table flags");
|
||||
}
|
||||
|
||||
if (isMem) {
|
||||
if (!env_.usesMemory()) {
|
||||
return fail("can't touch memory without memory");
|
||||
}
|
||||
} else {
|
||||
if (env_.tables.length() == 0) {
|
||||
return fail("can't table.copy without a table");
|
||||
if (*dstMemOrTableIndex >= env_.tables.length() || *srcMemOrTableIndex >= env_.tables.length()) {
|
||||
return fail("table index out of range for table.copy");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t memOrTableFlags;
|
||||
if (!readVarU32(&memOrTableFlags)) {
|
||||
return fail(isMem ? "unable to read memory flags" : "unable to read table flags");
|
||||
}
|
||||
if (memOrTableFlags != 0) {
|
||||
return fail(isMem ? "memory flags must be zero" : "table flags must be zero");
|
||||
}
|
||||
|
||||
if (!popWithType(ValType::I32, len)) {
|
||||
return false;
|
||||
}
|
||||
@ -2105,7 +2141,7 @@ OpIter<Policy>::readMemOrTableDrop(bool isMem, uint32_t* segIndex)
|
||||
dvs_.lock()->notifyDataSegmentIndex(*segIndex, d_.currentOffset());
|
||||
} else {
|
||||
if (*segIndex >= env_.elemSegments.length()) {
|
||||
return fail("table.drop index out of range");
|
||||
return fail("element segment index out of range for table.drop");
|
||||
}
|
||||
}
|
||||
|
||||
@ -2126,8 +2162,8 @@ OpIter<Policy>::readMemFill(Value* start, Value* val, Value* len)
|
||||
if (!readVarU32(&memoryFlags)) {
|
||||
return fail("unable to read memory flags");
|
||||
}
|
||||
if (memoryFlags != 0) {
|
||||
return fail("memory flags must be zero");
|
||||
if (memoryFlags != uint32_t(MemoryTableFlags::Default)) {
|
||||
return fail("unrecognized memory flags");
|
||||
}
|
||||
|
||||
if (!popWithType(ValType::I32, len)) {
|
||||
@ -2148,19 +2184,10 @@ OpIter<Policy>::readMemFill(Value* start, Value* val, Value* len)
|
||||
template <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::readMemOrTableInit(bool isMem, uint32_t* segIndex,
|
||||
Value* dst, Value* src, Value* len)
|
||||
uint32_t* dstTableIndex, Value* dst, Value* src, Value* len)
|
||||
{
|
||||
MOZ_ASSERT(Classify(op_) == OpKind::MemOrTableInit);
|
||||
|
||||
if (isMem) {
|
||||
if (!env_.usesMemory()) {
|
||||
return fail("can't touch memory without memory");
|
||||
}
|
||||
} else {
|
||||
if (env_.tables.length() == 0) {
|
||||
return fail("can't table.init without a table");
|
||||
}
|
||||
}
|
||||
MOZ_ASSERT(segIndex != dstTableIndex);
|
||||
|
||||
if (!popWithType(ValType::I32, len)) {
|
||||
return false;
|
||||
@ -2174,12 +2201,30 @@ OpIter<Policy>::readMemOrTableInit(bool isMem, uint32_t* segIndex,
|
||||
return false;
|
||||
}
|
||||
|
||||
*dstTableIndex = 0;
|
||||
|
||||
uint32_t memOrTableFlags;
|
||||
if (!readVarU32(&memOrTableFlags)) {
|
||||
return fail(isMem ? "unable to read memory flags" : "unable to read table flags");
|
||||
}
|
||||
if (memOrTableFlags != 0) {
|
||||
return fail(isMem ? "memory flags must be zero" : "table flags must be zero");
|
||||
if (!isMem && (memOrTableFlags & uint32_t(MemoryTableFlags::HasTableIndex))) {
|
||||
if (!readVarU32(dstTableIndex)) {
|
||||
return false;
|
||||
}
|
||||
memOrTableFlags ^= uint32_t(MemoryTableFlags::HasTableIndex);
|
||||
}
|
||||
if (memOrTableFlags != uint32_t(MemoryTableFlags::Default)) {
|
||||
return fail(isMem ? "unrecognized memory flags" : "unrecognized table flags");
|
||||
}
|
||||
|
||||
if (isMem) {
|
||||
if (!env_.usesMemory()) {
|
||||
return fail("can't touch memory without memory");
|
||||
}
|
||||
} else {
|
||||
if (*dstTableIndex >= env_.tables.length()) {
|
||||
return fail("table index out of range for table.init");
|
||||
}
|
||||
}
|
||||
|
||||
if (!readVarU32(segIndex)) {
|
||||
@ -2190,14 +2235,173 @@ OpIter<Policy>::readMemOrTableInit(bool isMem, uint32_t* segIndex,
|
||||
// Same comment as for readMemOrTableDrop.
|
||||
dvs_.lock()->notifyDataSegmentIndex(*segIndex, d_.currentOffset());
|
||||
} else {
|
||||
// Element segments must carry functions exclusively and anyfunc is not
|
||||
// yet a subtype of anyref.
|
||||
if (env_.tables[*dstTableIndex].kind != TableKind::AnyFunction) {
|
||||
return fail("only tables of 'anyfunc' may have element segments");
|
||||
}
|
||||
if (*segIndex >= env_.elemSegments.length()) {
|
||||
return fail("table.init index out of range");
|
||||
return fail("table.init segment index out of range");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::readTableGet(uint32_t* tableIndex, Value* index)
|
||||
{
|
||||
MOZ_ASSERT(Classify(op_) == OpKind::TableGet);
|
||||
|
||||
if (!popWithType(ValType::I32, index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*tableIndex = 0;
|
||||
|
||||
uint8_t tableFlags;
|
||||
if (!readFixedU8(&tableFlags)) {
|
||||
return fail("unable to read table flags");
|
||||
}
|
||||
if (tableFlags & uint8_t(MemoryTableFlags::HasTableIndex)) {
|
||||
if (!readVarU32(tableIndex)) {
|
||||
return false;
|
||||
}
|
||||
tableFlags ^= uint8_t(MemoryTableFlags::HasTableIndex);
|
||||
}
|
||||
if (tableFlags != uint8_t(MemoryTableFlags::Default)) {
|
||||
return fail("unrecognized table flags");
|
||||
}
|
||||
|
||||
if (*tableIndex >= env_.tables.length()) {
|
||||
return fail("table index out of range for table.get");
|
||||
}
|
||||
if (env_.tables[*tableIndex].kind != TableKind::AnyRef) {
|
||||
return fail("table.get only on tables of anyref");
|
||||
}
|
||||
if (env_.gcTypesEnabled() == HasGcTypes::False) {
|
||||
return fail("anyref support not enabled");
|
||||
}
|
||||
|
||||
infalliblePush(ValType::AnyRef);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::readTableGrow(uint32_t* tableIndex, Value* delta, Value* initValue)
|
||||
{
|
||||
MOZ_ASSERT(Classify(op_) == OpKind::TableGrow);
|
||||
|
||||
if (!popWithType(ValType::AnyRef, initValue)) {
|
||||
return false;
|
||||
}
|
||||
if (!popWithType(ValType::I32, delta)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*tableIndex = 0;
|
||||
|
||||
uint8_t tableFlags;
|
||||
if (!readFixedU8(&tableFlags)) {
|
||||
return fail("unable to read table flags");
|
||||
}
|
||||
if (tableFlags & uint8_t(MemoryTableFlags::HasTableIndex)) {
|
||||
if (!readVarU32(tableIndex)) {
|
||||
return false;
|
||||
}
|
||||
tableFlags ^= uint8_t(MemoryTableFlags::HasTableIndex);
|
||||
}
|
||||
if (tableFlags != uint8_t(MemoryTableFlags::Default)) {
|
||||
return fail("unrecognized table flags");
|
||||
}
|
||||
|
||||
if (*tableIndex >= env_.tables.length()) {
|
||||
return fail("table index out of range for table.grow");
|
||||
}
|
||||
if (env_.tables[*tableIndex].kind != TableKind::AnyRef) {
|
||||
return fail("table.grow only on tables of anyref");
|
||||
}
|
||||
if (env_.gcTypesEnabled() == HasGcTypes::False) {
|
||||
return fail("anyref support not enabled");
|
||||
}
|
||||
|
||||
infalliblePush(ValType::I32);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::readTableSet(uint32_t* tableIndex, Value* index, Value* value)
|
||||
{
|
||||
MOZ_ASSERT(Classify(op_) == OpKind::TableSet);
|
||||
|
||||
if (!popWithType(ValType::AnyRef, value)) {
|
||||
return false;
|
||||
}
|
||||
if (!popWithType(ValType::I32, index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*tableIndex = 0;
|
||||
|
||||
uint8_t tableFlags;
|
||||
if (!readFixedU8(&tableFlags)) {
|
||||
return fail("unable to read table flags");
|
||||
}
|
||||
if (tableFlags & uint8_t(MemoryTableFlags::HasTableIndex)) {
|
||||
if (!readVarU32(tableIndex)) {
|
||||
return false;
|
||||
}
|
||||
tableFlags ^= uint8_t(MemoryTableFlags::HasTableIndex);
|
||||
}
|
||||
if (tableFlags != uint8_t(MemoryTableFlags::Default)) {
|
||||
return fail("unrecognized table flags");
|
||||
}
|
||||
|
||||
if (*tableIndex >= env_.tables.length()) {
|
||||
return fail("table index out of range for table.set");
|
||||
}
|
||||
if (env_.tables[*tableIndex].kind != TableKind::AnyRef) {
|
||||
return fail("table.set only on tables of anyref");
|
||||
}
|
||||
if (env_.gcTypesEnabled() == HasGcTypes::False) {
|
||||
return fail("anyref support not enabled");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::readTableSize(uint32_t* tableIndex)
|
||||
{
|
||||
MOZ_ASSERT(Classify(op_) == OpKind::TableSize);
|
||||
|
||||
*tableIndex = 0;
|
||||
|
||||
uint8_t tableFlags;
|
||||
if (!readFixedU8(&tableFlags)) {
|
||||
return fail("unable to read table flags");
|
||||
}
|
||||
if (tableFlags & uint8_t(MemoryTableFlags::HasTableIndex)) {
|
||||
if (!readVarU32(tableIndex)) {
|
||||
return false;
|
||||
}
|
||||
tableFlags ^= uint8_t(MemoryTableFlags::HasTableIndex);
|
||||
}
|
||||
if (tableFlags != uint8_t(MemoryTableFlags::Default)) {
|
||||
return fail("unrecognized table flags");
|
||||
}
|
||||
|
||||
if (*tableIndex >= env_.tables.length()) {
|
||||
return fail("table index out of range for table.size");
|
||||
}
|
||||
|
||||
return push(ValType::I32);
|
||||
}
|
||||
|
||||
template <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::readStructTypeIndex(uint32_t* typeIndex)
|
||||
|
@ -30,33 +30,51 @@ using namespace js::wasm;
|
||||
using mozilla::CheckedInt;
|
||||
|
||||
Table::Table(JSContext* cx, const TableDesc& desc, HandleWasmTableObject maybeObject,
|
||||
UniqueByteArray array)
|
||||
UniqueAnyFuncArray functions)
|
||||
: maybeObject_(maybeObject),
|
||||
observers_(cx->zone()),
|
||||
array_(std::move(array)),
|
||||
functions_(std::move(functions)),
|
||||
kind_(desc.kind),
|
||||
length_(desc.limits.initial),
|
||||
maximum_(desc.limits.maximum),
|
||||
external_(desc.external)
|
||||
{}
|
||||
maximum_(desc.limits.maximum)
|
||||
{
|
||||
MOZ_ASSERT(kind_ != TableKind::AnyRef);
|
||||
}
|
||||
|
||||
Table::Table(JSContext* cx, const TableDesc& desc, HandleWasmTableObject maybeObject,
|
||||
TableAnyRefVector&& objects)
|
||||
: maybeObject_(maybeObject),
|
||||
observers_(cx->zone()),
|
||||
objects_(std::move(objects)),
|
||||
kind_(desc.kind),
|
||||
length_(desc.limits.initial),
|
||||
maximum_(desc.limits.maximum)
|
||||
{
|
||||
MOZ_ASSERT(kind_ == TableKind::AnyRef);
|
||||
}
|
||||
|
||||
/* static */ SharedTable
|
||||
Table::create(JSContext* cx, const TableDesc& desc, HandleWasmTableObject maybeObject)
|
||||
{
|
||||
// The raw element type of a Table depends on whether it is external: an
|
||||
// external table can contain functions from multiple instances and thus
|
||||
// must store an additional instance pointer in each element.
|
||||
UniqueByteArray array;
|
||||
if (desc.external) {
|
||||
array.reset((uint8_t*)cx->pod_calloc<ExternalTableElem>(desc.limits.initial));
|
||||
} else {
|
||||
array.reset((uint8_t*)cx->pod_calloc<void*>(desc.limits.initial));
|
||||
switch (desc.kind) {
|
||||
case TableKind::AnyFunction:
|
||||
case TableKind::TypedFunction: {
|
||||
UniqueAnyFuncArray functions(cx->pod_calloc<FunctionTableElem>(desc.limits.initial));
|
||||
if (!functions) {
|
||||
return nullptr;
|
||||
}
|
||||
return SharedTable(cx->new_<Table>(cx, desc, maybeObject, std::move(functions)));
|
||||
}
|
||||
case TableKind::AnyRef: {
|
||||
TableAnyRefVector objects;
|
||||
if (!objects.resize(desc.limits.initial)) {
|
||||
return nullptr;
|
||||
}
|
||||
return SharedTable(cx->new_<Table>(cx, desc, maybeObject, std::move(objects)));
|
||||
}
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
if (!array) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return SharedTable(cx->new_<Table>(cx, desc, maybeObject, std::move(array)));
|
||||
}
|
||||
|
||||
void
|
||||
@ -72,15 +90,29 @@ Table::tracePrivate(JSTracer* trc)
|
||||
TraceEdge(trc, &maybeObject_, "wasm table object");
|
||||
}
|
||||
|
||||
if (external_) {
|
||||
ExternalTableElem* array = externalArray();
|
||||
switch (kind_) {
|
||||
case TableKind::AnyFunction: {
|
||||
for (uint32_t i = 0; i < length_; i++) {
|
||||
if (array[i].tls) {
|
||||
array[i].tls->instance->trace(trc);
|
||||
if (functions_[i].tls) {
|
||||
functions_[i].tls->instance->trace(trc);
|
||||
} else {
|
||||
MOZ_ASSERT(!array[i].code);
|
||||
MOZ_ASSERT(!functions_[i].code);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TableKind::AnyRef: {
|
||||
objects_.trace(trc);
|
||||
break;
|
||||
}
|
||||
case TableKind::TypedFunction: {
|
||||
#ifdef DEBUG
|
||||
for (uint32_t i = 0; i < length_; i++) {
|
||||
MOZ_ASSERT(!functions_[i].tls);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,62 +131,97 @@ Table::trace(JSTracer* trc)
|
||||
}
|
||||
}
|
||||
|
||||
void**
|
||||
Table::internalArray() const
|
||||
uint8_t*
|
||||
Table::functionBase() const
|
||||
{
|
||||
MOZ_ASSERT(!external_);
|
||||
return (void**)array_.get();
|
||||
if (kind() == TableKind::AnyRef) {
|
||||
return nullptr;
|
||||
}
|
||||
return (uint8_t*)functions_.get();
|
||||
}
|
||||
|
||||
ExternalTableElem*
|
||||
Table::externalArray() const
|
||||
const FunctionTableElem&
|
||||
Table::getAnyFunc(uint32_t index) const
|
||||
{
|
||||
MOZ_ASSERT(external_);
|
||||
return (ExternalTableElem*)array_.get();
|
||||
MOZ_ASSERT(isFunction());
|
||||
return functions_[index];
|
||||
}
|
||||
|
||||
JSObject*
|
||||
Table::getAnyRef(uint32_t index) const
|
||||
{
|
||||
MOZ_ASSERT(!isFunction());
|
||||
return objects_[index];
|
||||
}
|
||||
|
||||
void
|
||||
Table::set(uint32_t index, void* code, const Instance* instance)
|
||||
Table::setAnyFunc(uint32_t index, void* code, const Instance* instance)
|
||||
{
|
||||
if (external_) {
|
||||
ExternalTableElem& elem = externalArray()[index];
|
||||
if (elem.tls) {
|
||||
JSObject::writeBarrierPre(elem.tls->instance->objectUnbarriered());
|
||||
}
|
||||
MOZ_ASSERT(isFunction());
|
||||
|
||||
FunctionTableElem& elem = functions_[index];
|
||||
if (elem.tls) {
|
||||
JSObject::writeBarrierPre(elem.tls->instance->objectUnbarriered());
|
||||
}
|
||||
|
||||
switch (kind_) {
|
||||
case TableKind::AnyFunction:
|
||||
elem.code = code;
|
||||
elem.tls = instance->tlsData();
|
||||
|
||||
MOZ_ASSERT(elem.tls->instance->objectUnbarriered()->isTenured(),
|
||||
"no writeBarrierPost (Table::set)");
|
||||
} else {
|
||||
internalArray()[index] = code;
|
||||
break;
|
||||
case TableKind::TypedFunction:
|
||||
elem.code = code;
|
||||
elem.tls = nullptr;
|
||||
break;
|
||||
case TableKind::AnyRef:
|
||||
MOZ_CRASH("Bad table type");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Table::setAnyRef(uint32_t index, JSObject* new_obj)
|
||||
{
|
||||
MOZ_ASSERT(!isFunction());
|
||||
objects_[index] = new_obj;
|
||||
}
|
||||
|
||||
void
|
||||
Table::setNull(uint32_t index)
|
||||
{
|
||||
// Only external tables can set elements to null after initialization.
|
||||
ExternalTableElem& elem = externalArray()[index];
|
||||
if (elem.tls) {
|
||||
JSObject::writeBarrierPre(elem.tls->instance->objectUnbarriered());
|
||||
}
|
||||
switch (kind_) {
|
||||
case TableKind::AnyFunction: {
|
||||
FunctionTableElem& elem = functions_[index];
|
||||
if (elem.tls) {
|
||||
JSObject::writeBarrierPre(elem.tls->instance->objectUnbarriered());
|
||||
}
|
||||
|
||||
elem.code = nullptr;
|
||||
elem.tls = nullptr;
|
||||
elem.code = nullptr;
|
||||
elem.tls = nullptr;
|
||||
break;
|
||||
}
|
||||
case TableKind::AnyRef: {
|
||||
objects_[index] = nullptr;
|
||||
break;
|
||||
}
|
||||
case TableKind::TypedFunction: {
|
||||
MOZ_CRASH("Should not happen");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Table::copy(uint32_t dstIndex, uint32_t srcIndex)
|
||||
Table::copy(const Table& srcTable, uint32_t dstIndex, uint32_t srcIndex)
|
||||
{
|
||||
if (external_) {
|
||||
ExternalTableElem& dst = externalArray()[dstIndex];
|
||||
switch (kind_) {
|
||||
case TableKind::AnyFunction: {
|
||||
FunctionTableElem& dst = functions_[dstIndex];
|
||||
if (dst.tls) {
|
||||
JSObject::writeBarrierPre(dst.tls->instance->objectUnbarriered());
|
||||
}
|
||||
|
||||
ExternalTableElem& src = externalArray()[srcIndex];
|
||||
FunctionTableElem& src = srcTable.functions_[srcIndex];
|
||||
dst.code = src.code;
|
||||
dst.tls = src.tls;
|
||||
|
||||
@ -165,8 +232,15 @@ Table::copy(uint32_t dstIndex, uint32_t srcIndex)
|
||||
} else {
|
||||
MOZ_ASSERT(!dst.code);
|
||||
}
|
||||
} else {
|
||||
internalArray()[dstIndex] = internalArray()[srcIndex];
|
||||
break;
|
||||
}
|
||||
case TableKind::AnyRef: {
|
||||
objects_[dstIndex] = srcTable.objects_[srcIndex];
|
||||
break;
|
||||
}
|
||||
case TableKind::TypedFunction: {
|
||||
MOZ_CRASH("Bad table type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,21 +269,38 @@ Table::grow(uint32_t delta, JSContext* cx)
|
||||
|
||||
JSRuntime* rt = cx->runtime(); // Use JSRuntime's MallocProvider to avoid throwing.
|
||||
|
||||
// Note that realloc does not release array_'s pointee (which is returned by
|
||||
// externalArray()) on failure which is exactly what we need here.
|
||||
ExternalTableElem* newArray = rt->pod_realloc(externalArray(), length_, newLength.value());
|
||||
if (!newArray) {
|
||||
return -1;
|
||||
}
|
||||
Unused << array_.release();
|
||||
array_.reset((uint8_t*)newArray);
|
||||
switch (kind_) {
|
||||
case TableKind::AnyFunction: {
|
||||
// Note that realloc does not release functions_'s pointee on failure
|
||||
// which is exactly what we need here.
|
||||
FunctionTableElem* newFunctions = rt->pod_realloc<FunctionTableElem>(functions_.get(),
|
||||
length_,
|
||||
newLength.value());
|
||||
if (!newFunctions) {
|
||||
return -1;
|
||||
}
|
||||
Unused << functions_.release();
|
||||
functions_.reset(newFunctions);
|
||||
|
||||
// Realloc does not zero the delta for us.
|
||||
PodZero(newFunctions + length_, delta);
|
||||
break;
|
||||
}
|
||||
case TableKind::AnyRef: {
|
||||
if (!objects_.resize(newLength.value())) {
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TableKind::TypedFunction: {
|
||||
MOZ_CRASH("Bad table type");
|
||||
}
|
||||
}
|
||||
|
||||
// Realloc does not zero the delta for us.
|
||||
PodZero(newArray + length_, delta);
|
||||
length_ = newLength.value();
|
||||
|
||||
for (InstanceSet::Range r = observers_.all(); !r.empty(); r.popFront()) {
|
||||
r.front()->instance().onMovingGrowTable();
|
||||
r.front()->instance().onMovingGrowTable(this);
|
||||
}
|
||||
|
||||
return oldLength;
|
||||
@ -226,7 +317,10 @@ Table::addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance)
|
||||
{
|
||||
MOZ_ASSERT(movingGrowable());
|
||||
|
||||
if (!observers_.putNew(instance)) {
|
||||
// A table can be imported multiple times into an instance, but we only
|
||||
// register the instance as an observer once.
|
||||
|
||||
if (!observers_.put(instance)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
@ -237,5 +331,8 @@ Table::addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance)
|
||||
size_t
|
||||
Table::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
|
||||
{
|
||||
return mallocSizeOf(array_.get());
|
||||
if (isFunction()) {
|
||||
return mallocSizeOf(functions_.get());
|
||||
}
|
||||
return objects_.sizeOfExcludingThis(mallocSizeOf);
|
||||
}
|
||||
|
@ -28,25 +28,34 @@ namespace wasm {
|
||||
// A Table is an indexable array of opaque values. Tables are first-class
|
||||
// stateful objects exposed to WebAssembly. asm.js also uses Tables to represent
|
||||
// its homogeneous function-pointer tables.
|
||||
//
|
||||
// A table of AnyFunction holds FunctionTableElems, which are (instance*,index)
|
||||
// pairs, where the instance must be traced.
|
||||
//
|
||||
// A table of AnyRef holds JSObject pointers, which must be traced.
|
||||
|
||||
typedef GCVector<JS::Heap<JSObject*>, 0, SystemAllocPolicy> TableAnyRefVector;
|
||||
|
||||
class Table : public ShareableBase<Table>
|
||||
{
|
||||
using InstanceSet = JS::WeakCache<GCHashSet<ReadBarrieredWasmInstanceObject,
|
||||
MovableCellHasher<ReadBarrieredWasmInstanceObject>,
|
||||
SystemAllocPolicy>>;
|
||||
using UniqueByteArray = UniquePtr<uint8_t[], JS::FreePolicy>;
|
||||
using UniqueAnyFuncArray = UniquePtr<FunctionTableElem[], JS::FreePolicy>;
|
||||
|
||||
ReadBarrieredWasmTableObject maybeObject_;
|
||||
InstanceSet observers_;
|
||||
UniqueByteArray array_;
|
||||
UniqueAnyFuncArray functions_; // either functions_ has data
|
||||
TableAnyRefVector objects_; // or objects_, but not both
|
||||
const TableKind kind_;
|
||||
uint32_t length_;
|
||||
const Maybe<uint32_t> maximum_;
|
||||
const bool external_;
|
||||
|
||||
template <class> friend struct js::MallocProvider;
|
||||
Table(JSContext* cx, const TableDesc& td, HandleWasmTableObject maybeObject,
|
||||
UniqueByteArray array);
|
||||
UniqueAnyFuncArray functions);
|
||||
Table(JSContext* cx, const TableDesc& td, HandleWasmTableObject maybeObject,
|
||||
TableAnyRefVector&& objects);
|
||||
|
||||
void tracePrivate(JSTracer* trc);
|
||||
friend class js::WasmTableObject;
|
||||
@ -56,22 +65,33 @@ class Table : public ShareableBase<Table>
|
||||
HandleWasmTableObject maybeObject);
|
||||
void trace(JSTracer* trc);
|
||||
|
||||
bool external() const { return external_; }
|
||||
TableKind kind() const { return kind_; }
|
||||
bool isTypedFunction() const { return kind_ == TableKind::TypedFunction; }
|
||||
bool isFunction() const {
|
||||
return kind_ == TableKind::AnyFunction || kind_ == TableKind::TypedFunction;
|
||||
}
|
||||
uint32_t length() const { return length_; }
|
||||
Maybe<uint32_t> maximum() const { return maximum_; }
|
||||
uint8_t* base() const { return array_.get(); }
|
||||
|
||||
// All table updates must go through set() or setNull().
|
||||
// Only for function values. Raw pointer to the table.
|
||||
uint8_t* functionBase() const;
|
||||
|
||||
// get/setAnyFunc is allowed only on table-of-anyfunc.
|
||||
// get/setAnyRef is allowed only on table-of-anyref.
|
||||
// setNull is allowed on either.
|
||||
const FunctionTableElem& getAnyFunc(uint32_t index) const;
|
||||
void setAnyFunc(uint32_t index, void* code, const Instance* instance);
|
||||
|
||||
JSObject* getAnyRef(uint32_t index) const;
|
||||
void setAnyRef(uint32_t index, JSObject* obj);
|
||||
|
||||
void** internalArray() const;
|
||||
ExternalTableElem* externalArray() const;
|
||||
void set(uint32_t index, void* code, const Instance* instance);
|
||||
void setNull(uint32_t index);
|
||||
|
||||
// Copy entry at |srcIndex| to |dstIndex|. Used by table.copy.
|
||||
void copy(uint32_t dstIndex, uint32_t srcIndex);
|
||||
// Copy entry from |srcTable| at |srcIndex| to this table at |dstIndex|.
|
||||
// Used by table.copy.
|
||||
void copy(const Table& srcTable, uint32_t dstIndex, uint32_t srcIndex);
|
||||
|
||||
// grow() returns (uint32_t)-1 if it could not grow.
|
||||
uint32_t grow(uint32_t delta, JSContext* cx);
|
||||
bool movingGrowable() const;
|
||||
bool addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance);
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/MathAlgorithms.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
|
||||
#include "jsnum.h"
|
||||
|
||||
@ -148,6 +149,12 @@ class WasmToken
|
||||
TableCopy,
|
||||
TableDrop,
|
||||
TableInit,
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
TableGet,
|
||||
TableGrow,
|
||||
TableSet,
|
||||
TableSize,
|
||||
#endif
|
||||
TeeLocal,
|
||||
TernaryOpcode,
|
||||
@ -374,6 +381,12 @@ class WasmToken
|
||||
case TableCopy:
|
||||
case TableDrop:
|
||||
case TableInit:
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
case TableGet:
|
||||
case TableGrow:
|
||||
case TableSet:
|
||||
case TableSize:
|
||||
#endif
|
||||
case TeeLocal:
|
||||
case TernaryOpcode:
|
||||
@ -993,8 +1006,11 @@ WasmTokenStream::next()
|
||||
return WasmToken(WasmToken::ValueType, ValType::AnyRef, begin, cur_);
|
||||
}
|
||||
#ifdef ENABLE_WASM_THREAD_OPS
|
||||
if (consume(u"atomic.wake")) {
|
||||
return WasmToken(WasmToken::Wake, ThreadOp::Wake, begin, cur_);
|
||||
if (consume(u"atomic.")) {
|
||||
if (consume(u"wake") || consume(u"notify")) {
|
||||
return WasmToken(WasmToken::Wake, ThreadOp::Wake, begin, cur_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
@ -2105,8 +2121,8 @@ WasmTokenStream::next()
|
||||
break;
|
||||
|
||||
case 't':
|
||||
#ifdef ENABLE_WASM_BULKMEM_OPS
|
||||
if (consume(u"table.")) {
|
||||
#ifdef ENABLE_WASM_BULKMEM_OPS
|
||||
if (consume(u"copy")) {
|
||||
return WasmToken(WasmToken::TableCopy, begin, cur_);
|
||||
}
|
||||
@ -2116,9 +2132,23 @@ WasmTokenStream::next()
|
||||
if (consume(u"init")) {
|
||||
return WasmToken(WasmToken::TableInit, begin, cur_);
|
||||
}
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
if (consume(u"get")) {
|
||||
return WasmToken(WasmToken::TableGet, begin, cur_);
|
||||
}
|
||||
if (consume(u"grow")) {
|
||||
return WasmToken(WasmToken::TableGrow, begin, cur_);
|
||||
}
|
||||
if (consume(u"set")) {
|
||||
return WasmToken(WasmToken::TableSet, begin, cur_);
|
||||
}
|
||||
if (consume(u"size")) {
|
||||
return WasmToken(WasmToken::TableSize, begin, cur_);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
if (consume(u"table")) {
|
||||
return WasmToken(WasmToken::Table, begin, cur_);
|
||||
}
|
||||
@ -2158,6 +2188,7 @@ struct WasmParseContext
|
||||
UniqueChars* error;
|
||||
DtoaState* dtoaState;
|
||||
uintptr_t stackLimit;
|
||||
uint32_t nextSym;
|
||||
|
||||
WasmParseContext(const char16_t* text, uintptr_t stackLimit, LifoAlloc& lifo,
|
||||
UniqueChars* error)
|
||||
@ -2165,12 +2196,29 @@ struct WasmParseContext
|
||||
lifo(lifo),
|
||||
error(error),
|
||||
dtoaState(NewDtoaState()),
|
||||
stackLimit(stackLimit)
|
||||
stackLimit(stackLimit),
|
||||
nextSym(0)
|
||||
{}
|
||||
|
||||
~WasmParseContext() {
|
||||
DestroyDtoaState(dtoaState);
|
||||
}
|
||||
|
||||
AstName gensym(const char* tag) {
|
||||
char buf[128];
|
||||
MOZ_ASSERT(strlen(tag) < sizeof(buf)-20);
|
||||
SprintfLiteral(buf, ".%s.%u", tag, nextSym);
|
||||
nextSym++;
|
||||
size_t k = strlen(buf)+1;
|
||||
char16_t* mem = (char16_t*)lifo.alloc(k * sizeof(char16_t));
|
||||
if (!mem) {
|
||||
return AstName();
|
||||
}
|
||||
for (size_t i = 0; i < k; i++) {
|
||||
mem[i] = buf[i];
|
||||
}
|
||||
return AstName(mem, k-1);
|
||||
}
|
||||
};
|
||||
|
||||
} // end anonymous namespace
|
||||
@ -2451,10 +2499,23 @@ ParseCall(WasmParseContext& c, bool inParens)
|
||||
static AstCallIndirect*
|
||||
ParseCallIndirect(WasmParseContext& c, bool inParens)
|
||||
{
|
||||
AstRef firstRef;
|
||||
AstRef secondRef;
|
||||
AstRef funcType;
|
||||
if (!c.ts.matchRef(&funcType, c.error)) {
|
||||
AstRef targetTable = AstRef(0);
|
||||
|
||||
// (call_indirect table signature arg ... index)
|
||||
// (call_indirect signature arg ... index)
|
||||
|
||||
if (!c.ts.matchRef(&firstRef, c.error)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (c.ts.getIfRef(&secondRef)) {
|
||||
targetTable = firstRef;
|
||||
funcType = secondRef;
|
||||
} else {
|
||||
funcType = firstRef;
|
||||
}
|
||||
|
||||
AstExprVector args(c.lifo);
|
||||
AstExpr* index;
|
||||
@ -2476,7 +2537,7 @@ ParseCallIndirect(WasmParseContext& c, bool inParens)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new(c.lifo) AstCallIndirect(funcType, ExprType::Void, std::move(args), index);
|
||||
return new(c.lifo) AstCallIndirect(targetTable, funcType, ExprType::Void, std::move(args), index);
|
||||
}
|
||||
|
||||
static uint_fast8_t
|
||||
@ -3526,11 +3587,31 @@ ParseGrowMemory(WasmParseContext& c, bool inParens)
|
||||
static AstMemOrTableCopy*
|
||||
ParseMemOrTableCopy(WasmParseContext& c, bool inParens, bool isMem)
|
||||
{
|
||||
// (table.copy dest-table dest src-table src len)
|
||||
// (table.copy dest src len)
|
||||
// (memory.copy dest src len)
|
||||
|
||||
AstRef targetMemOrTable = AstRef(0);
|
||||
bool requireSource = false;
|
||||
if (!isMem) {
|
||||
if (c.ts.getIfRef(&targetMemOrTable)) {
|
||||
requireSource = true;
|
||||
}
|
||||
}
|
||||
|
||||
AstExpr* dest = ParseExpr(c, inParens);
|
||||
if (!dest) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AstRef memOrTableSource = AstRef(0);
|
||||
if (requireSource) {
|
||||
if (!c.ts.getIfRef(&memOrTableSource)) {
|
||||
c.ts.generateError(c.ts.peek(), "source is required if target is specified", c.error);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AstExpr* src = ParseExpr(c, inParens);
|
||||
if (!src) {
|
||||
return nullptr;
|
||||
@ -3541,7 +3622,7 @@ ParseMemOrTableCopy(WasmParseContext& c, bool inParens, bool isMem)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new(c.lifo) AstMemOrTableCopy(isMem, dest, src, len);
|
||||
return new(c.lifo) AstMemOrTableCopy(isMem, targetMemOrTable, dest, memOrTableSource, src, len);
|
||||
}
|
||||
|
||||
static AstMemOrTableDrop*
|
||||
@ -3579,9 +3660,32 @@ ParseMemFill(WasmParseContext& c, bool inParens)
|
||||
static AstMemOrTableInit*
|
||||
ParseMemOrTableInit(WasmParseContext& c, bool inParens, bool isMem)
|
||||
{
|
||||
// (table.init table-index segment-index ...)
|
||||
// (table.init segment-index ...)
|
||||
// (memory.init segment-index ...)
|
||||
|
||||
AstRef targetMemOrTable = AstRef(0);
|
||||
uint32_t segIndex = 0;
|
||||
|
||||
WasmToken segIndexTok;
|
||||
if (!c.ts.getIf(WasmToken::Index, &segIndexTok)) {
|
||||
return nullptr;
|
||||
if (isMem) {
|
||||
if (!c.ts.getIf(WasmToken::Index, &segIndexTok)) {
|
||||
return nullptr;
|
||||
}
|
||||
segIndex = segIndexTok.index();
|
||||
} else {
|
||||
// Slightly hairy to parse this for tables because the element index "0"
|
||||
// could just as well be the table index "0".
|
||||
c.ts.getIfRef(&targetMemOrTable);
|
||||
if (c.ts.getIf(WasmToken::Index, &segIndexTok)) {
|
||||
segIndex = segIndexTok.index();
|
||||
} else if (targetMemOrTable.isIndex()) {
|
||||
segIndex = targetMemOrTable.index();
|
||||
targetMemOrTable = AstRef(0);
|
||||
} else {
|
||||
c.ts.generateError(c.ts.peek(), "expected element segment reference", c.error);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AstExpr* dst = ParseExpr(c, inParens);
|
||||
@ -3599,7 +3703,79 @@ ParseMemOrTableInit(WasmParseContext& c, bool inParens, bool isMem)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new(c.lifo) AstMemOrTableInit(isMem, segIndexTok.index(), dst, src, len);
|
||||
return new(c.lifo) AstMemOrTableInit(isMem, segIndex, targetMemOrTable, dst, src, len);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
static AstTableGet*
|
||||
ParseTableGet(WasmParseContext& c, bool inParens)
|
||||
{
|
||||
// (table.get table index)
|
||||
// (table.get index)
|
||||
|
||||
AstRef targetTable = AstRef(0);
|
||||
c.ts.getIfRef(&targetTable);
|
||||
|
||||
AstExpr* index = ParseExpr(c, inParens);
|
||||
if (!index) {
|
||||
return nullptr;
|
||||
}
|
||||
return new(c.lifo) AstTableGet(targetTable, index);
|
||||
}
|
||||
|
||||
static AstTableGrow*
|
||||
ParseTableGrow(WasmParseContext& c, bool inParens)
|
||||
{
|
||||
// (table.grow table delta)
|
||||
// (table.grow delta)
|
||||
|
||||
AstRef targetTable = AstRef(0);
|
||||
c.ts.getIfRef(&targetTable);
|
||||
|
||||
AstExpr* delta = ParseExpr(c, inParens);
|
||||
if (!delta) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AstExpr* initValue = ParseExpr(c, inParens);
|
||||
if (!initValue) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new(c.lifo) AstTableGrow(targetTable, delta, initValue);
|
||||
}
|
||||
|
||||
static AstTableSet*
|
||||
ParseTableSet(WasmParseContext& c, bool inParens)
|
||||
{
|
||||
// (table.set table index value)
|
||||
// (table.set index value)
|
||||
|
||||
AstRef targetTable = AstRef(0);
|
||||
c.ts.getIfRef(&targetTable);
|
||||
|
||||
AstExpr* index = ParseExpr(c, inParens);
|
||||
if (!index) {
|
||||
return nullptr;
|
||||
}
|
||||
AstExpr* value = ParseExpr(c, inParens);
|
||||
if (!value) {
|
||||
return nullptr;
|
||||
}
|
||||
return new(c.lifo) AstTableSet(targetTable, index, value);
|
||||
}
|
||||
|
||||
static AstTableSize*
|
||||
ParseTableSize(WasmParseContext& c, bool inParens)
|
||||
{
|
||||
// (table.size table)
|
||||
// (table.size)
|
||||
|
||||
AstRef targetTable = AstRef(0);
|
||||
c.ts.getIfRef(&targetTable);
|
||||
|
||||
return new(c.lifo) AstTableSize(targetTable);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -3818,6 +3994,16 @@ ParseExprBody(WasmParseContext& c, WasmToken token, bool inParens)
|
||||
case WasmToken::TableInit:
|
||||
return ParseMemOrTableInit(c, inParens, /*isMem=*/false);
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
case WasmToken::TableGet:
|
||||
return ParseTableGet(c, inParens);
|
||||
case WasmToken::TableGrow:
|
||||
return ParseTableGrow(c, inParens);
|
||||
case WasmToken::TableSet:
|
||||
return ParseTableSet(c, inParens);
|
||||
case WasmToken::TableSize:
|
||||
return ParseTableSize(c, inParens);
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GC
|
||||
case WasmToken::StructNew:
|
||||
return ParseStructNew(c, inParens);
|
||||
@ -4438,17 +4624,32 @@ ParseGlobalType(WasmParseContext& c, AstValType* type, bool* isMutable)
|
||||
}
|
||||
|
||||
static bool
|
||||
ParseElemType(WasmParseContext& c)
|
||||
ParseElemType(WasmParseContext& c, TableKind* tableKind)
|
||||
{
|
||||
// Only AnyFunc is allowed at the moment.
|
||||
return c.ts.match(WasmToken::AnyFunc, c.error);
|
||||
WasmToken token;
|
||||
if (c.ts.getIf(WasmToken::AnyFunc, &token)) {
|
||||
*tableKind = TableKind::AnyFunction;
|
||||
return true;
|
||||
}
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
if (c.ts.getIf(WasmToken::ValueType, &token) &&
|
||||
token.valueType() == ValType::AnyRef)
|
||||
{
|
||||
*tableKind = TableKind::AnyRef;
|
||||
return true;
|
||||
}
|
||||
c.ts.generateError(token, "'anyfunc' or 'anyref' required", c.error);
|
||||
#else
|
||||
c.ts.generateError(token, "'anyfunc' required", c.error);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
ParseTableSig(WasmParseContext& c, Limits* table)
|
||||
ParseTableSig(WasmParseContext& c, Limits* table, TableKind* tableKind)
|
||||
{
|
||||
return ParseLimits(c, table, Shareable::False) &&
|
||||
ParseElemType(c);
|
||||
ParseElemType(c, tableKind);
|
||||
}
|
||||
|
||||
static AstImport*
|
||||
@ -4489,15 +4690,17 @@ ParseImport(WasmParseContext& c, AstModule* module)
|
||||
name = c.ts.getIfName();
|
||||
}
|
||||
|
||||
TableKind tableKind;
|
||||
Limits table;
|
||||
if (!ParseTableSig(c, &table)) {
|
||||
if (!ParseTableSig(c, &table, &tableKind)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!c.ts.match(WasmToken::CloseParen, c.error)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(),
|
||||
DefinitionKind::Table, table);
|
||||
table, tableKind);
|
||||
}
|
||||
if (c.ts.getIf(WasmToken::Global)) {
|
||||
if (name.empty()) {
|
||||
@ -4657,13 +4860,14 @@ ParseTable(WasmParseContext& c, WasmToken token, AstModule* module)
|
||||
return false;
|
||||
}
|
||||
|
||||
TableKind tableKind;
|
||||
Limits table;
|
||||
if (!ParseTableSig(c, &table)) {
|
||||
if (!ParseTableSig(c, &table, &tableKind)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* import = new(c.lifo) AstImport(name, names.module.text(), names.field.text(),
|
||||
DefinitionKind::Table, table);
|
||||
table, tableKind);
|
||||
|
||||
return import && module->append(import);
|
||||
}
|
||||
@ -4684,15 +4888,17 @@ ParseTable(WasmParseContext& c, WasmToken token, AstModule* module)
|
||||
|
||||
// Either: min max? anyfunc
|
||||
if (c.ts.peek().kind() == WasmToken::Index) {
|
||||
TableKind tableKind;
|
||||
Limits table;
|
||||
if (!ParseTableSig(c, &table)) {
|
||||
if (!ParseTableSig(c, &table, &tableKind)) {
|
||||
return false;
|
||||
}
|
||||
return module->addTable(name, table);
|
||||
return module->addTable(name, table, tableKind);
|
||||
}
|
||||
|
||||
// Or: anyfunc (elem 1 2 ...)
|
||||
if (!ParseElemType(c)) {
|
||||
TableKind tableKind;
|
||||
if (!ParseElemType(c, &tableKind)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -4703,6 +4909,14 @@ ParseTable(WasmParseContext& c, WasmToken token, AstModule* module)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name.empty()) {
|
||||
// For inline elements we need a name, so synthesize one if there isn't
|
||||
// one already.
|
||||
name = c.gensym("elem");
|
||||
if (name.empty())
|
||||
return false;
|
||||
}
|
||||
|
||||
AstRefVector elems(c.lifo);
|
||||
|
||||
AstRef elem;
|
||||
@ -4721,7 +4935,9 @@ ParseTable(WasmParseContext& c, WasmToken token, AstModule* module)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!module->addTable(name, Limits(numElements, Some(numElements), Shareable::False))) {
|
||||
if (!module->addTable(name, Limits(numElements, Some(numElements), Shareable::False),
|
||||
tableKind))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -4730,22 +4946,30 @@ ParseTable(WasmParseContext& c, WasmToken token, AstModule* module)
|
||||
return false;
|
||||
}
|
||||
|
||||
AstElemSegment* segment = new(c.lifo) AstElemSegment(zero, std::move(elems));
|
||||
AstElemSegment* segment = new(c.lifo) AstElemSegment(AstRef(name), zero, std::move(elems));
|
||||
return segment && module->append(segment);
|
||||
}
|
||||
|
||||
static AstElemSegment*
|
||||
ParseElemSegment(WasmParseContext& c)
|
||||
{
|
||||
if (!MaybeParseOwnerIndex(c)) {
|
||||
return nullptr;
|
||||
}
|
||||
// (elem table-name init-expr ref ...)
|
||||
// (elem init-expr ref ...)
|
||||
// (elem passive ref ...)
|
||||
|
||||
AstRef targetTable = AstRef(0);
|
||||
bool hasTableName = c.ts.getIfRef(&targetTable);
|
||||
|
||||
AstExpr* offsetIfActive;
|
||||
if (!ParseInitializerExpressionOrPassive(c, &offsetIfActive)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (hasTableName && !offsetIfActive) {
|
||||
c.ts.generateError(c.ts.peek(), "passive segment must not have a table", c.error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AstRefVector elems(c.lifo);
|
||||
|
||||
AstRef elem;
|
||||
@ -4755,7 +4979,7 @@ ParseElemSegment(WasmParseContext& c)
|
||||
}
|
||||
}
|
||||
|
||||
return new(c.lifo) AstElemSegment(offsetIfActive, std::move(elems));
|
||||
return new(c.lifo) AstElemSegment(targetTable, offsetIfActive, std::move(elems));
|
||||
}
|
||||
|
||||
static bool
|
||||
@ -5221,6 +5445,10 @@ ResolveCallIndirect(Resolver& r, AstCallIndirect& c)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!r.resolveTable(c.targetTable())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -5448,7 +5676,9 @@ ResolveMemOrTableCopy(Resolver& r, AstMemOrTableCopy& s)
|
||||
{
|
||||
return ResolveExpr(r, s.dest()) &&
|
||||
ResolveExpr(r, s.src()) &&
|
||||
ResolveExpr(r, s.len());
|
||||
ResolveExpr(r, s.len()) &&
|
||||
r.resolveTable(s.destTable()) &&
|
||||
r.resolveTable(s.srcTable());
|
||||
}
|
||||
|
||||
static bool
|
||||
@ -5464,7 +5694,38 @@ ResolveMemOrTableInit(Resolver& r, AstMemOrTableInit& s)
|
||||
{
|
||||
return ResolveExpr(r, s.dst()) &&
|
||||
ResolveExpr(r, s.src()) &&
|
||||
ResolveExpr(r, s.len());
|
||||
ResolveExpr(r, s.len()) &&
|
||||
r.resolveTable(s.targetTable());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
static bool
|
||||
ResolveTableGet(Resolver& r, AstTableGet& s)
|
||||
{
|
||||
return ResolveExpr(r, s.index()) && r.resolveTable(s.targetTable());
|
||||
}
|
||||
|
||||
static bool
|
||||
ResolveTableGrow(Resolver& r, AstTableGrow& s)
|
||||
{
|
||||
return ResolveExpr(r, s.delta()) &&
|
||||
ResolveExpr(r, s.initValue()) &&
|
||||
r.resolveTable(s.targetTable());
|
||||
}
|
||||
|
||||
static bool
|
||||
ResolveTableSet(Resolver& r, AstTableSet& s)
|
||||
{
|
||||
return ResolveExpr(r, s.index()) &&
|
||||
ResolveExpr(r, s.value()) &&
|
||||
r.resolveTable(s.targetTable());
|
||||
}
|
||||
|
||||
static bool
|
||||
ResolveTableSize(Resolver& r, AstTableSize& s)
|
||||
{
|
||||
return r.resolveTable(s.targetTable());
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -5613,6 +5874,16 @@ ResolveExpr(Resolver& r, AstExpr& expr)
|
||||
case AstExprKind::MemOrTableInit:
|
||||
return ResolveMemOrTableInit(r, expr.as<AstMemOrTableInit>());
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
case AstExprKind::TableGet:
|
||||
return ResolveTableGet(r, expr.as<AstTableGet>());
|
||||
case AstExprKind::TableGrow:
|
||||
return ResolveTableGrow(r, expr.as<AstTableGrow>());
|
||||
case AstExprKind::TableSet:
|
||||
return ResolveTableSet(r, expr.as<AstTableSet>());
|
||||
case AstExprKind::TableSize:
|
||||
return ResolveTableSize(r, expr.as<AstTableSize>());
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GC
|
||||
case AstExprKind::StructNew:
|
||||
return ResolveStructNew(r, expr.as<AstStructNew>());
|
||||
@ -5674,6 +5945,14 @@ ResolveStruct(Resolver& r, AstStructType& s)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ResolveElemSegment(Resolver& r, AstElemSegment& seg)
|
||||
{
|
||||
if (!r.resolveTable(seg.targetTableRef()))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ResolveModule(LifoAlloc& lifo, AstModule* module, UniqueChars* error)
|
||||
{
|
||||
@ -5778,7 +6057,7 @@ ResolveModule(LifoAlloc& lifo, AstModule* module, UniqueChars* error)
|
||||
}
|
||||
}
|
||||
|
||||
for (const AstResizable& table : module->tables()) {
|
||||
for (const AstTable& table : module->tables()) {
|
||||
if (table.imported) {
|
||||
continue;
|
||||
}
|
||||
@ -5787,7 +6066,7 @@ ResolveModule(LifoAlloc& lifo, AstModule* module, UniqueChars* error)
|
||||
}
|
||||
}
|
||||
|
||||
for (const AstResizable& memory : module->memories()) {
|
||||
for (const AstMemory& memory : module->memories()) {
|
||||
if (memory.imported) {
|
||||
continue;
|
||||
}
|
||||
@ -5821,6 +6100,12 @@ ResolveModule(LifoAlloc& lifo, AstModule* module, UniqueChars* error)
|
||||
}
|
||||
}
|
||||
|
||||
for (AstElemSegment* seg : module->elemSegments()) {
|
||||
if (!ResolveElemSegment(r, *seg)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (AstFunc* func : module->funcs()) {
|
||||
if (!ResolveFunc(r, *func)) {
|
||||
return false;
|
||||
@ -5956,6 +6241,16 @@ EncodeCall(Encoder& e, AstCall& c)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
EncodeOneTableIndex(Encoder& e, uint32_t index)
|
||||
{
|
||||
if (index) {
|
||||
return e.writeVarU32(uint32_t(MemoryTableFlags::HasTableIndex)) &&
|
||||
e.writeVarU32(index);
|
||||
}
|
||||
return e.writeVarU32(uint32_t(MemoryTableFlags::Default));
|
||||
}
|
||||
|
||||
static bool
|
||||
EncodeCallIndirect(Encoder& e, AstCallIndirect& c)
|
||||
{
|
||||
@ -5975,11 +6270,7 @@ EncodeCallIndirect(Encoder& e, AstCallIndirect& c)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!e.writeVarU32(uint32_t(MemoryTableFlags::Default))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return EncodeOneTableIndex(e, c.targetTable().index());
|
||||
}
|
||||
|
||||
static bool
|
||||
@ -6293,11 +6584,20 @@ EncodeWake(Encoder& e, AstWake& s)
|
||||
static bool
|
||||
EncodeMemOrTableCopy(Encoder& e, AstMemOrTableCopy& s)
|
||||
{
|
||||
return EncodeExpr(e, s.dest()) &&
|
||||
EncodeExpr(e, s.src()) &&
|
||||
EncodeExpr(e, s.len()) &&
|
||||
e.writeOp(s.isMem() ? MiscOp::MemCopy : MiscOp::TableCopy) &&
|
||||
e.writeVarU32(uint32_t(MemoryTableFlags::Default));
|
||||
bool result = EncodeExpr(e, s.dest()) &&
|
||||
EncodeExpr(e, s.src()) &&
|
||||
EncodeExpr(e, s.len()) &&
|
||||
e.writeOp(s.isMem() ? MiscOp::MemCopy : MiscOp::TableCopy);
|
||||
if (s.destTable().index() == 0 && s.srcTable().index() == 0) {
|
||||
result = result &&
|
||||
e.writeVarU32(uint32_t(MemoryTableFlags::Default));
|
||||
} else {
|
||||
result = result &&
|
||||
e.writeVarU32(uint32_t(MemoryTableFlags::HasTableIndex)) &&
|
||||
e.writeVarU32(s.destTable().index()) &&
|
||||
e.writeVarU32(s.srcTable().index());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool
|
||||
@ -6324,11 +6624,46 @@ EncodeMemOrTableInit(Encoder& e, AstMemOrTableInit& s)
|
||||
EncodeExpr(e, s.src()) &&
|
||||
EncodeExpr(e, s.len()) &&
|
||||
e.writeOp(s.isMem() ? MiscOp::MemInit : MiscOp::TableInit) &&
|
||||
e.writeVarU32(uint32_t(MemoryTableFlags::Default)) &&
|
||||
EncodeOneTableIndex(e, s.targetTable().index()) &&
|
||||
e.writeVarU32(s.segIndex());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
static bool
|
||||
EncodeTableGet(Encoder& e, AstTableGet& s)
|
||||
{
|
||||
return EncodeExpr(e, s.index()) &&
|
||||
e.writeOp(MiscOp::TableGet) &&
|
||||
EncodeOneTableIndex(e, s.targetTable().index());
|
||||
}
|
||||
|
||||
static bool
|
||||
EncodeTableGrow(Encoder& e, AstTableGrow& s)
|
||||
{
|
||||
return EncodeExpr(e, s.delta()) &&
|
||||
EncodeExpr(e, s.initValue()) &&
|
||||
e.writeOp(MiscOp::TableGrow) &&
|
||||
EncodeOneTableIndex(e, s.targetTable().index());
|
||||
}
|
||||
|
||||
static bool
|
||||
EncodeTableSet(Encoder& e, AstTableSet& s)
|
||||
{
|
||||
return EncodeExpr(e, s.index()) &&
|
||||
EncodeExpr(e, s.value()) &&
|
||||
e.writeOp(MiscOp::TableSet) &&
|
||||
EncodeOneTableIndex(e, s.targetTable().index());
|
||||
}
|
||||
|
||||
static bool
|
||||
EncodeTableSize(Encoder& e, AstTableSize& s)
|
||||
{
|
||||
return e.writeOp(MiscOp::TableSize) &&
|
||||
EncodeOneTableIndex(e, s.targetTable().index());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_WASM_GC
|
||||
static bool
|
||||
EncodeStructNew(Encoder& e, AstStructNew& s)
|
||||
@ -6497,6 +6832,16 @@ EncodeExpr(Encoder& e, AstExpr& expr)
|
||||
case AstExprKind::MemOrTableInit:
|
||||
return EncodeMemOrTableInit(e, expr.as<AstMemOrTableInit>());
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
case AstExprKind::TableGet:
|
||||
return EncodeTableGet(e, expr.as<AstTableGet>());
|
||||
case AstExprKind::TableGrow:
|
||||
return EncodeTableGrow(e, expr.as<AstTableGrow>());
|
||||
case AstExprKind::TableSet:
|
||||
return EncodeTableSet(e, expr.as<AstTableSet>());
|
||||
case AstExprKind::TableSize:
|
||||
return EncodeTableSize(e, expr.as<AstTableSize>());
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GC
|
||||
case AstExprKind::StructNew:
|
||||
return EncodeStructNew(e, expr.as<AstStructNew>());
|
||||
@ -6670,10 +7015,21 @@ EncodeLimits(Encoder& e, const Limits& limits)
|
||||
}
|
||||
|
||||
static bool
|
||||
EncodeTableLimits(Encoder& e, const Limits& limits)
|
||||
EncodeTableLimits(Encoder& e, const Limits& limits, TableKind tableKind)
|
||||
{
|
||||
if (!e.writeVarU32(uint32_t(TypeCode::AnyFunc))) {
|
||||
return false;
|
||||
switch (tableKind) {
|
||||
case TableKind::AnyFunction:
|
||||
if (!e.writeVarU32(uint32_t(TypeCode::AnyFunc))) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case TableKind::AnyRef:
|
||||
if (!e.writeVarU32(uint32_t(TypeCode::AnyRef))) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH("Unexpected table kind");
|
||||
}
|
||||
|
||||
return EncodeLimits(e, limits);
|
||||
@ -6714,7 +7070,7 @@ EncodeImport(Encoder& e, AstImport& imp)
|
||||
}
|
||||
break;
|
||||
case DefinitionKind::Table:
|
||||
if (!EncodeTableLimits(e, imp.limits())) {
|
||||
if (!EncodeTableLimits(e, imp.limits(), imp.tableKind())) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
@ -6758,7 +7114,7 @@ static bool
|
||||
EncodeMemorySection(Encoder& e, AstModule& module)
|
||||
{
|
||||
size_t numOwnMemories = 0;
|
||||
for (const AstResizable& memory : module.memories()) {
|
||||
for (const AstMemory& memory : module.memories()) {
|
||||
if (!memory.imported) {
|
||||
numOwnMemories++;
|
||||
}
|
||||
@ -6777,7 +7133,7 @@ EncodeMemorySection(Encoder& e, AstModule& module)
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const AstResizable& memory : module.memories()) {
|
||||
for (const AstMemory& memory : module.memories()) {
|
||||
if (memory.imported) {
|
||||
continue;
|
||||
}
|
||||
@ -6870,7 +7226,7 @@ static bool
|
||||
EncodeTableSection(Encoder& e, AstModule& module)
|
||||
{
|
||||
size_t numOwnTables = 0;
|
||||
for (const AstResizable& table : module.tables()) {
|
||||
for (const AstTable& table : module.tables()) {
|
||||
if (!table.imported) {
|
||||
numOwnTables++;
|
||||
}
|
||||
@ -6889,11 +7245,11 @@ EncodeTableSection(Encoder& e, AstModule& module)
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const AstResizable& table : module.tables()) {
|
||||
for (const AstTable& table : module.tables()) {
|
||||
if (table.imported) {
|
||||
continue;
|
||||
}
|
||||
if (!EncodeTableLimits(e, table.limits)) {
|
||||
if (!EncodeTableLimits(e, table.limits, table.tableKind)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -6989,14 +7345,22 @@ EncodeCodeSection(Encoder& e, Uint32Vector* offsets, AstModule& module)
|
||||
}
|
||||
|
||||
static bool
|
||||
EncodeDestinationOffsetOrFlags(Encoder& e, AstExpr* offsetIfActive)
|
||||
EncodeDestinationOffsetOrFlags(Encoder& e, uint32_t index, AstExpr* offsetIfActive)
|
||||
{
|
||||
if (offsetIfActive) {
|
||||
// In the MVP, the following VarU32 is the table or linear memory
|
||||
// index and it must be zero. In the bulk-mem-ops proposal, it is
|
||||
// repurposed as a flag field.
|
||||
if (!e.writeVarU32(uint32_t(InitializerKind::Active))) {
|
||||
return false;
|
||||
// In the MVP, the following VarU32 is the table or linear memory index
|
||||
// and it must be zero. In the bulk-mem-ops proposal, it is repurposed
|
||||
// as a flag field, and if the index is not zero it must be present.
|
||||
if (index) {
|
||||
if (!e.writeVarU32(uint32_t(InitializerKind::ActiveWithIndex)) ||
|
||||
!e.writeVarU32(index))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!e.writeVarU32(uint32_t(InitializerKind::Active))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!EncodeExpr(e, *offsetIfActive)) {
|
||||
return false;
|
||||
@ -7016,7 +7380,7 @@ EncodeDestinationOffsetOrFlags(Encoder& e, AstExpr* offsetIfActive)
|
||||
static bool
|
||||
EncodeDataSegment(Encoder& e, const AstDataSegment& segment)
|
||||
{
|
||||
if (!EncodeDestinationOffsetOrFlags(e, segment.offsetIfActive())) {
|
||||
if (!EncodeDestinationOffsetOrFlags(e, 0, segment.offsetIfActive())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -7072,7 +7436,7 @@ EncodeDataSection(Encoder& e, AstModule& module)
|
||||
static bool
|
||||
EncodeElemSegment(Encoder& e, AstElemSegment& segment)
|
||||
{
|
||||
if (!EncodeDestinationOffsetOrFlags(e, segment.offsetIfActive())) {
|
||||
if (!EncodeDestinationOffsetOrFlags(e, segment.targetTable().index(), segment.offsetIfActive())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -427,6 +427,13 @@ Export::globalIndex() const
|
||||
return pod.index_;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
Export::tableIndex() const
|
||||
{
|
||||
MOZ_ASSERT(pod.kind_ == DefinitionKind::Table);
|
||||
return pod.index_;
|
||||
}
|
||||
|
||||
size_t
|
||||
Export::serializedSize() const
|
||||
{
|
||||
|
@ -66,6 +66,7 @@ typedef Handle<WasmInstanceObject*> HandleWasmInstanceObject;
|
||||
typedef MutableHandle<WasmInstanceObject*> MutableHandleWasmInstanceObject;
|
||||
|
||||
class WasmTableObject;
|
||||
typedef GCVector<WasmTableObject*, 0, SystemAllocPolicy> WasmTableObjectVector;
|
||||
typedef Rooted<WasmTableObject*> RootedWasmTableObject;
|
||||
typedef Handle<WasmTableObject*> HandleWasmTableObject;
|
||||
typedef MutableHandle<WasmTableObject*> MutableHandleWasmTableObject;
|
||||
@ -1002,6 +1003,7 @@ class Export
|
||||
DefinitionKind kind() const { return pod.kind_; }
|
||||
uint32_t funcIndex() const;
|
||||
uint32_t globalIndex() const;
|
||||
uint32_t tableIndex() const;
|
||||
|
||||
WASM_DECLARE_SERIALIZABLE(Export)
|
||||
};
|
||||
@ -1917,7 +1919,11 @@ enum class SymbolicAddress
|
||||
MemInit,
|
||||
TableCopy,
|
||||
TableDrop,
|
||||
TableGet,
|
||||
TableGrow,
|
||||
TableInit,
|
||||
TableSet,
|
||||
TableSize,
|
||||
PostBarrier,
|
||||
StructNew,
|
||||
StructNarrow,
|
||||
@ -1955,30 +1961,21 @@ struct Limits
|
||||
enum class TableKind
|
||||
{
|
||||
AnyFunction,
|
||||
AnyRef,
|
||||
TypedFunction
|
||||
};
|
||||
|
||||
struct TableDesc
|
||||
{
|
||||
// If a table is marked 'external' it is because it can contain functions
|
||||
// from multiple instances; a table is therefore marked external if it is
|
||||
// imported or exported or if it is initialized with an imported function.
|
||||
|
||||
TableKind kind;
|
||||
#ifdef WASM_PRIVATE_REFTYPES
|
||||
bool importedOrExported;
|
||||
#endif
|
||||
bool external;
|
||||
uint32_t globalDataOffset;
|
||||
Limits limits;
|
||||
|
||||
TableDesc() = default;
|
||||
TableDesc(TableKind kind, const Limits& limits)
|
||||
TableDesc(TableKind kind, const Limits& limits, bool importedOrExported = false)
|
||||
: kind(kind),
|
||||
#ifdef WASM_PRIVATE_REFTYPES
|
||||
importedOrExported(false),
|
||||
#endif
|
||||
external(false),
|
||||
importedOrExported(importedOrExported),
|
||||
globalDataOffset(UINT32_MAX),
|
||||
limits(limits)
|
||||
{}
|
||||
@ -2105,16 +2102,15 @@ struct TableTls
|
||||
// Length of the table in number of elements (not bytes).
|
||||
uint32_t length;
|
||||
|
||||
// Pointer to the array of elements (of type either ExternalTableElem or
|
||||
// void*).
|
||||
void* base;
|
||||
// Pointer to the array of elements (which can have various representations).
|
||||
// For tables of anyref this is null.
|
||||
void* functionBase;
|
||||
};
|
||||
|
||||
// When a table can contain functions from other instances (it is "external"),
|
||||
// the internal representation is an array of ExternalTableElem instead of just
|
||||
// an array of code pointers.
|
||||
// Table elements for TableKind::AnyFunctions carry both the code pointer and an
|
||||
// instance pointer.
|
||||
|
||||
struct ExternalTableElem
|
||||
struct FunctionTableElem
|
||||
{
|
||||
// The code to call when calling this element. The table ABI is the system
|
||||
// ABI with the additional ABI requirements that:
|
||||
@ -2168,7 +2164,6 @@ class CalleeDesc
|
||||
struct {
|
||||
uint32_t globalDataOffset_;
|
||||
uint32_t minLength_;
|
||||
bool external_;
|
||||
FuncTypeIdDesc funcTypeId_;
|
||||
} table;
|
||||
SymbolicAddress builtin_;
|
||||
@ -2193,7 +2188,6 @@ class CalleeDesc
|
||||
c.which_ = WasmTable;
|
||||
c.u.table.globalDataOffset_ = desc.globalDataOffset;
|
||||
c.u.table.minLength_ = desc.limits.initial;
|
||||
c.u.table.external_ = desc.external;
|
||||
c.u.table.funcTypeId_ = funcTypeId;
|
||||
return c;
|
||||
}
|
||||
@ -2233,13 +2227,9 @@ class CalleeDesc
|
||||
MOZ_ASSERT(isTable());
|
||||
return u.table.globalDataOffset_ + offsetof(TableTls, length);
|
||||
}
|
||||
uint32_t tableBaseGlobalDataOffset() const {
|
||||
uint32_t tableFunctionBaseGlobalDataOffset() const {
|
||||
MOZ_ASSERT(isTable());
|
||||
return u.table.globalDataOffset_ + offsetof(TableTls, base);
|
||||
}
|
||||
bool wasmTableIsExternal() const {
|
||||
MOZ_ASSERT(which_ == WasmTable);
|
||||
return u.table.external_;
|
||||
return u.table.globalDataOffset_ + offsetof(TableTls, functionBase);
|
||||
}
|
||||
FuncTypeIdDesc wasmTableSigId() const {
|
||||
MOZ_ASSERT(which_ == WasmTable);
|
||||
|
@ -579,9 +579,9 @@ DecodeFunctionBodyExprs(const ModuleEnvironment& env, const FuncType& funcType,
|
||||
CHECK(iter.readCall(&unusedIndex, &unusedArgs));
|
||||
}
|
||||
case uint16_t(Op::CallIndirect): {
|
||||
uint32_t unusedIndex;
|
||||
uint32_t unusedIndex, unusedIndex2;
|
||||
ValidatingOpIter::ValueVector unusedArgs;
|
||||
CHECK(iter.readCallIndirect(&unusedIndex, ¬hing, &unusedArgs));
|
||||
CHECK(iter.readCallIndirect(&unusedIndex, &unusedIndex2, ¬hing, &unusedArgs));
|
||||
}
|
||||
case uint16_t(Op::I32Const): {
|
||||
int32_t unused;
|
||||
@ -904,9 +904,12 @@ DecodeFunctionBodyExprs(const ModuleEnvironment& env, const FuncType& funcType,
|
||||
case uint16_t(MiscOp::I64TruncUSatF64):
|
||||
CHECK(iter.readConversion(ValType::F64, ValType::I64, ¬hing));
|
||||
#ifdef ENABLE_WASM_BULKMEM_OPS
|
||||
case uint16_t(MiscOp::MemCopy):
|
||||
CHECK(iter.readMemOrTableCopy(/*isMem=*/true,
|
||||
¬hing, ¬hing, ¬hing));
|
||||
case uint16_t(MiscOp::MemCopy): {
|
||||
uint32_t unusedDestMemIndex;
|
||||
uint32_t unusedSrcMemIndex;
|
||||
CHECK(iter.readMemOrTableCopy(/*isMem=*/true, &unusedDestMemIndex,
|
||||
¬hing, &unusedSrcMemIndex, ¬hing, ¬hing));
|
||||
}
|
||||
case uint16_t(MiscOp::MemDrop): {
|
||||
uint32_t unusedSegIndex;
|
||||
CHECK(iter.readMemOrTableDrop(/*isMem=*/true, &unusedSegIndex));
|
||||
@ -915,20 +918,43 @@ DecodeFunctionBodyExprs(const ModuleEnvironment& env, const FuncType& funcType,
|
||||
CHECK(iter.readMemFill(¬hing, ¬hing, ¬hing));
|
||||
case uint16_t(MiscOp::MemInit): {
|
||||
uint32_t unusedSegIndex;
|
||||
uint32_t unusedTableIndex;
|
||||
CHECK(iter.readMemOrTableInit(/*isMem=*/true,
|
||||
&unusedSegIndex, ¬hing, ¬hing, ¬hing));
|
||||
&unusedSegIndex, &unusedTableIndex, ¬hing, ¬hing, ¬hing));
|
||||
}
|
||||
case uint16_t(MiscOp::TableCopy): {
|
||||
uint32_t unusedDestTableIndex;
|
||||
uint32_t unusedSrcTableIndex;
|
||||
CHECK(iter.readMemOrTableCopy(/*isMem=*/false, &unusedDestTableIndex,
|
||||
¬hing, &unusedSrcTableIndex, ¬hing, ¬hing));
|
||||
}
|
||||
case uint16_t(MiscOp::TableCopy):
|
||||
CHECK(iter.readMemOrTableCopy(/*isMem=*/false,
|
||||
¬hing, ¬hing, ¬hing));
|
||||
case uint16_t(MiscOp::TableDrop): {
|
||||
uint32_t unusedSegIndex;
|
||||
CHECK(iter.readMemOrTableDrop(/*isMem=*/false, &unusedSegIndex));
|
||||
}
|
||||
case uint16_t(MiscOp::TableInit): {
|
||||
uint32_t unusedSegIndex;
|
||||
uint32_t unusedTableIndex;
|
||||
CHECK(iter.readMemOrTableInit(/*isMem=*/false,
|
||||
&unusedSegIndex, ¬hing, ¬hing, ¬hing));
|
||||
&unusedSegIndex, &unusedTableIndex, ¬hing, ¬hing, ¬hing));
|
||||
}
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
case uint16_t(MiscOp::TableGet): {
|
||||
uint32_t unusedTableIndex;
|
||||
CHECK(iter.readTableGet(&unusedTableIndex, ¬hing));
|
||||
}
|
||||
case uint16_t(MiscOp::TableGrow): {
|
||||
uint32_t unusedTableIndex;
|
||||
CHECK(iter.readTableGrow(&unusedTableIndex, ¬hing, ¬hing));
|
||||
}
|
||||
case uint16_t(MiscOp::TableSet): {
|
||||
uint32_t unusedTableIndex;
|
||||
CHECK(iter.readTableSet(&unusedTableIndex, ¬hing, ¬hing));
|
||||
}
|
||||
case uint16_t(MiscOp::TableSize): {
|
||||
uint32_t unusedTableIndex;
|
||||
CHECK(iter.readTableSize(&unusedTableIndex));
|
||||
}
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GC
|
||||
@ -1429,10 +1455,14 @@ DecodeGCFeatureOptInSection(Decoder& d, ModuleEnvironment* env)
|
||||
// For documentation of what's in the various versions, see
|
||||
// https://github.com/lars-t-hansen/moz-gc-experiments
|
||||
//
|
||||
// When we evolve the engine to handle v2, we will continue to recognize v1
|
||||
// here if v2 is fully backwards compatible with v1.
|
||||
// Version 1 is complete.
|
||||
// Version 2 is in progress, currently backward compatible with version 1.
|
||||
|
||||
if (version != 1) {
|
||||
switch (version) {
|
||||
case 1:
|
||||
case 2:
|
||||
break;
|
||||
default:
|
||||
return d.fail("unsupported version of the gc feature");
|
||||
}
|
||||
|
||||
@ -1598,15 +1628,29 @@ DecodeLimits(Decoder& d, Limits* limits, Shareable allowShared = Shareable::Fals
|
||||
}
|
||||
|
||||
static bool
|
||||
DecodeTableLimits(Decoder& d, TableDescVector* tables)
|
||||
DecodeTableTypeAndLimits(Decoder& d, HasGcTypes gcTypesEnabled, TableDescVector* tables)
|
||||
{
|
||||
uint8_t elementType;
|
||||
if (!d.readFixedU8(&elementType)) {
|
||||
return d.fail("expected table element type");
|
||||
}
|
||||
|
||||
if (elementType != uint8_t(TypeCode::AnyFunc)) {
|
||||
TableKind tableKind;
|
||||
if (elementType == uint8_t(TypeCode::AnyFunc)) {
|
||||
tableKind = TableKind::AnyFunction;
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
} else if (elementType == uint8_t(TypeCode::AnyRef)) {
|
||||
if (gcTypesEnabled == HasGcTypes::False) {
|
||||
return d.fail("reference types not enabled");
|
||||
}
|
||||
tableKind = TableKind::AnyRef;
|
||||
#endif
|
||||
} else {
|
||||
#ifdef ENABLE_WASM_GENERALIZED_TABLES
|
||||
return d.fail("expected 'anyfunc' or 'anyref' element type");
|
||||
#else
|
||||
return d.fail("expected 'anyfunc' element type");
|
||||
#endif
|
||||
}
|
||||
|
||||
Limits limits;
|
||||
@ -1622,11 +1666,11 @@ DecodeTableLimits(Decoder& d, TableDescVector* tables)
|
||||
limits.maximum.value() > MaxTableMaximumLength)))
|
||||
return d.fail("too many table elements");
|
||||
|
||||
if (tables->length()) {
|
||||
return d.fail("already have default table");
|
||||
if (tables->length() >= MaxTables) {
|
||||
return d.fail("too many tables");
|
||||
}
|
||||
|
||||
return tables->emplaceBack(TableKind::AnyFunction, limits);
|
||||
return tables->emplaceBack(tableKind, limits);
|
||||
}
|
||||
|
||||
static bool
|
||||
@ -1775,13 +1819,10 @@ DecodeImport(Decoder& d, ModuleEnvironment* env)
|
||||
break;
|
||||
}
|
||||
case DefinitionKind::Table: {
|
||||
if (!DecodeTableLimits(d, &env->tables)) {
|
||||
if (!DecodeTableTypeAndLimits(d, env->gcTypesEnabled(), &env->tables)) {
|
||||
return false;
|
||||
}
|
||||
env->tables.back().external = true;
|
||||
#ifdef WASM_PRIVATE_REFTYPES
|
||||
env->tables.back().importedOrExported = true;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case DefinitionKind::Memory: {
|
||||
@ -1905,12 +1946,8 @@ DecodeTableSection(Decoder& d, ModuleEnvironment* env)
|
||||
return d.fail("failed to read number of tables");
|
||||
}
|
||||
|
||||
if (numTables > 1) {
|
||||
return d.fail("the number of tables must be at most one");
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < numTables; ++i) {
|
||||
if (!DecodeTableLimits(d, &env->tables)) {
|
||||
if (!DecodeTableTypeAndLimits(d, env->gcTypesEnabled(), &env->tables)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -2151,14 +2188,8 @@ DecodeExport(Decoder& d, ModuleEnvironment* env, CStringSet* dupSet)
|
||||
if (tableIndex >= env->tables.length()) {
|
||||
return d.fail("exported table index out of bounds");
|
||||
}
|
||||
|
||||
MOZ_ASSERT(env->tables.length() == 1);
|
||||
env->tables[tableIndex].external = true;
|
||||
#ifdef WASM_PRIVATE_REFTYPES
|
||||
env->tables[tableIndex].importedOrExported = true;
|
||||
#endif
|
||||
|
||||
return env->exports.emplaceBack(std::move(fieldName), DefinitionKind::Table);
|
||||
return env->exports.emplaceBack(std::move(fieldName), tableIndex, DefinitionKind::Table);
|
||||
}
|
||||
case DefinitionKind::Memory: {
|
||||
uint32_t memoryIndex;
|
||||
@ -2302,7 +2333,6 @@ DecodeElemSection(Decoder& d, ModuleEnvironment* env)
|
||||
|
||||
InitializerKind initializerKind = InitializerKind(initializerKindVal);
|
||||
|
||||
MOZ_ASSERT(env->tables.length() <= 1);
|
||||
if (env->tables.length() == 0) {
|
||||
return d.fail("elem segment requires a table section");
|
||||
}
|
||||
@ -2317,9 +2347,17 @@ DecodeElemSection(Decoder& d, ModuleEnvironment* env)
|
||||
if (!d.readVarU32(&tableIndex)) {
|
||||
return d.fail("expected table index");
|
||||
}
|
||||
if (tableIndex > 0) {
|
||||
return d.fail("table index must be zero");
|
||||
}
|
||||
}
|
||||
if (tableIndex >= env->tables.length()) {
|
||||
return d.fail("table index out of range for element segment");
|
||||
}
|
||||
if (initializerKind == InitializerKind::Passive) {
|
||||
// Too many bugs result from keeping this value zero. For passive
|
||||
// segments, there really is no segment index, and we should never
|
||||
// touch the field.
|
||||
tableIndex = (uint32_t)-1;
|
||||
} else if (env->tables[tableIndex].kind != TableKind::AnyFunction) {
|
||||
return d.fail("only tables of 'anyfunc' may have element segments");
|
||||
}
|
||||
|
||||
seg->tableIndex = tableIndex;
|
||||
@ -2349,6 +2387,15 @@ DecodeElemSection(Decoder& d, ModuleEnvironment* env)
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef WASM_PRIVATE_REFTYPES
|
||||
// We assume that passive segments may be applied to external tables.
|
||||
// We can do slightly better: if there are no external tables in the
|
||||
// module then we don't need to worry about passive segments either.
|
||||
// But this is a temporary restriction.
|
||||
bool exportedTable = initializerKind == InitializerKind::Passive ||
|
||||
env->tables[tableIndex].importedOrExported;
|
||||
#endif
|
||||
|
||||
for (uint32_t i = 0; i < numElems; i++) {
|
||||
uint32_t funcIndex;
|
||||
if (!d.readVarU32(&funcIndex)) {
|
||||
@ -2359,17 +2406,8 @@ DecodeElemSection(Decoder& d, ModuleEnvironment* env)
|
||||
return d.fail("table element out of range");
|
||||
}
|
||||
|
||||
// If a table element function value is imported then the table can
|
||||
// contain functions from multiple instances and must be marked
|
||||
// external.
|
||||
if (env->funcIsImport(funcIndex)) {
|
||||
env->tables[0].external = true;
|
||||
}
|
||||
|
||||
#ifdef WASM_PRIVATE_REFTYPES
|
||||
if (env->tables[0].importedOrExported &&
|
||||
!FuncTypeIsJSCompatible(d, *env->funcTypes[funcIndex]))
|
||||
{
|
||||
if (exportedTable && !FuncTypeIsJSCompatible(d, *env->funcTypes[funcIndex])) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
16170
media/webrtc/gn-configs/x64_False_arm64_win.json
Normal file
16170
media/webrtc/gn-configs/x64_False_arm64_win.json
Normal file
File diff suppressed because it is too large
Load Diff
15680
media/webrtc/gn-configs/x64_False_x64_win.json
Normal file
15680
media/webrtc/gn-configs/x64_False_x64_win.json
Normal file
File diff suppressed because it is too large
Load Diff
16614
media/webrtc/gn-configs/x64_False_x86_linux.json
Normal file
16614
media/webrtc/gn-configs/x64_False_x86_linux.json
Normal file
File diff suppressed because it is too large
Load Diff
15455
media/webrtc/gn-configs/x64_True_arm64_win.json
Normal file
15455
media/webrtc/gn-configs/x64_True_arm64_win.json
Normal file
File diff suppressed because it is too large
Load Diff
14975
media/webrtc/gn-configs/x64_True_x64_win.json
Normal file
14975
media/webrtc/gn-configs/x64_True_x64_win.json
Normal file
File diff suppressed because it is too large
Load Diff
16188
media/webrtc/gn-configs/x64_True_x86_linux.json
Normal file
16188
media/webrtc/gn-configs/x64_True_x86_linux.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -34,6 +34,8 @@ webrtc_non_unified_sources = [
|
||||
'trunk/webrtc/modules/audio_processing/aec/echo_cancellation.cc', # Because of conflicts over 'near' on windows
|
||||
'trunk/webrtc/modules/audio_processing/aecm/aecm_core.cc', # Because of the PART_LEN2 define
|
||||
'trunk/webrtc/modules/audio_processing/aecm/aecm_core_c.cc', # Because of the PART_LEN2 define
|
||||
'trunk/webrtc/modules/audio_processing/aecm/aecm_core_mips.cc', # Because of the PART_LEN2 define
|
||||
'trunk/webrtc/modules/audio_processing/aecm/aecm_core_neon.cc', # Because of the PART_LEN2 define
|
||||
'trunk/webrtc/modules/audio_processing/aecm/echo_control_mobile.cc', # Because of the PART_LEN2 define
|
||||
'trunk/webrtc/modules/audio_processing/agc/legacy/analog_agc.c', # Because of name clash in the kInitCheck variable
|
||||
'trunk/webrtc/modules/audio_processing/beamformer/covariance_matrix_generator.cc', # Because of needing to define _USE_MATH_DEFINES before including <cmath>
|
||||
|
@ -184,6 +184,12 @@ if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "FreeBSD":
|
||||
"-msse2"
|
||||
]
|
||||
|
||||
if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "Linux":
|
||||
|
||||
CXXFLAGS += [
|
||||
"-msse2"
|
||||
]
|
||||
|
||||
if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "NetBSD":
|
||||
|
||||
CXXFLAGS += [
|
||||
|
@ -184,6 +184,12 @@ if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "FreeBSD":
|
||||
"-msse2"
|
||||
]
|
||||
|
||||
if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "Linux":
|
||||
|
||||
CXXFLAGS += [
|
||||
"-msse2"
|
||||
]
|
||||
|
||||
if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "NetBSD":
|
||||
|
||||
CXXFLAGS += [
|
||||
|
@ -186,6 +186,12 @@ if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "FreeBSD":
|
||||
"-msse2"
|
||||
]
|
||||
|
||||
if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "Linux":
|
||||
|
||||
CXXFLAGS += [
|
||||
"-msse2"
|
||||
]
|
||||
|
||||
if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "NetBSD":
|
||||
|
||||
CXXFLAGS += [
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user