Merge mozilla-central to autoland. a=merge on a CLOSED TREE

This commit is contained in:
Andreea Pavel 2018-11-15 00:35:07 +02:00
commit 9cabbb0b47
647 changed files with 118862 additions and 3326 deletions

View File

@ -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]

View File

@ -215,7 +215,6 @@ var CaptivePortalWatcher = {
// Returning true prevents the notification from closing.
return true;
},
isDefault: true,
},
];

View File

@ -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);
},
};

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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.

View File

@ -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.

View File

@ -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.");
});

View File

@ -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"},

View File

@ -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);

View File

@ -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");

View File

@ -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];

View File

@ -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) {

View File

@ -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;
},

View File

@ -0,0 +1,11 @@
"use strict";
module.exports = {
"env": {
"mozilla/browser-window": true,
},
"plugins": [
"mozilla",
]
};

View File

@ -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

View 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']

View File

@ -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" });

View File

@ -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'
]

View File

@ -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>

View File

@ -107,6 +107,10 @@
flex: 0 1 100%;
}
#street-address {
resize: vertical;
}
#country-warning-message {
box-sizing: border-box;
font-size: 1rem;

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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");

View File

@ -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.");

View File

@ -63,7 +63,7 @@ const CSSUsageFront = protocol.FrontClassWithSpec(cssUsageSpec, {
}
} else {
if (notification) {
notification.remove();
notification.close();
notification = undefined;
}

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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

View File

@ -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 },

View File

@ -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]

View File

@ -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;

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();
}

View File

@ -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);

View File

@ -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();
}

View File

@ -87,7 +87,6 @@ protected:
, mMoz2DBackend(aMoz2DBackend)
{}
RefPtr<gfx::DrawTarget> mDrawTarget;
BufferDescriptor mDescriptor;
gfx::BackendType mMoz2DBackend;
};

View File

@ -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) {

View File

@ -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."),

View 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);
}
}

View File

@ -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);
}

View File

@ -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 };

View File

@ -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/);

View File

@ -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"/);

View 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);
}

View 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/);

View 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);
}
}

View File

@ -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

View File

@ -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", "",

View File

@ -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

View File

@ -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");

View File

@ -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/);
}

View File

@ -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());

View File

@ -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;

View File

@ -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)
{

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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")

View File

@ -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;

View File

@ -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;

View File

@ -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, &nothing, &nothing, &nothing)) {
if (!iter_.readMemOrTableCopy(isMem, &dstMemOrTableIndex, &nothing, &srcMemOrTableIndex,
&nothing, &nothing))
{
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, &nothing, &nothing, &nothing)) {
if (!iter_.readMemOrTableInit(isMem, &segIndex, &dstTableIndex, &nothing, &nothing, &nothing)) {
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;

View File

@ -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:

View File

@ -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;

View File

@ -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

View File

@ -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:

View File

@ -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;
}
}

View File

@ -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

View File

@ -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,

View File

@ -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):

View File

@ -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();

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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
{

View File

@ -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);

View File

@ -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, &nothing, &unusedArgs));
CHECK(iter.readCallIndirect(&unusedIndex, &unusedIndex2, &nothing, &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, &nothing));
#ifdef ENABLE_WASM_BULKMEM_OPS
case uint16_t(MiscOp::MemCopy):
CHECK(iter.readMemOrTableCopy(/*isMem=*/true,
&nothing, &nothing, &nothing));
case uint16_t(MiscOp::MemCopy): {
uint32_t unusedDestMemIndex;
uint32_t unusedSrcMemIndex;
CHECK(iter.readMemOrTableCopy(/*isMem=*/true, &unusedDestMemIndex,
&nothing, &unusedSrcMemIndex, &nothing, &nothing));
}
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(&nothing, &nothing, &nothing));
case uint16_t(MiscOp::MemInit): {
uint32_t unusedSegIndex;
uint32_t unusedTableIndex;
CHECK(iter.readMemOrTableInit(/*isMem=*/true,
&unusedSegIndex, &nothing, &nothing, &nothing));
&unusedSegIndex, &unusedTableIndex, &nothing, &nothing, &nothing));
}
case uint16_t(MiscOp::TableCopy): {
uint32_t unusedDestTableIndex;
uint32_t unusedSrcTableIndex;
CHECK(iter.readMemOrTableCopy(/*isMem=*/false, &unusedDestTableIndex,
&nothing, &unusedSrcTableIndex, &nothing, &nothing));
}
case uint16_t(MiscOp::TableCopy):
CHECK(iter.readMemOrTableCopy(/*isMem=*/false,
&nothing, &nothing, &nothing));
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, &nothing, &nothing, &nothing));
&unusedSegIndex, &unusedTableIndex, &nothing, &nothing, &nothing));
}
#endif
#ifdef ENABLE_WASM_GENERALIZED_TABLES
case uint16_t(MiscOp::TableGet): {
uint32_t unusedTableIndex;
CHECK(iter.readTableGet(&unusedTableIndex, &nothing));
}
case uint16_t(MiscOp::TableGrow): {
uint32_t unusedTableIndex;
CHECK(iter.readTableGrow(&unusedTableIndex, &nothing, &nothing));
}
case uint16_t(MiscOp::TableSet): {
uint32_t unusedTableIndex;
CHECK(iter.readTableSet(&unusedTableIndex, &nothing, &nothing));
}
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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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 += [

View File

@ -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 += [

View File

@ -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