diff --git a/browser/components/payments/res/containers/address-form.css b/browser/components/payments/res/containers/address-form.css
index d9148bb38fb5..202c6dd2dc9d 100644
--- a/browser/components/payments/res/containers/address-form.css
+++ b/browser/components/payments/res/containers/address-form.css
@@ -2,26 +2,22 @@
* 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/. */
-.error-text:not(:empty) {
+.error-text {
color: #fff;
background-color: #d70022;
border-radius: 2px;
- /* The padding-top and padding-bottom are referenced by address-form.js */
+ margin: 5px 3px 0 3px;
+ /* The padding-top and padding-bottom are referenced by address-form.js */ /* TODO */
padding: 5px 12px;
position: absolute;
z-index: 1;
pointer-events: none;
+ top: 100%;
+ visibility: hidden;
}
-body[dir="ltr"] .error-text {
- left: 3px;
-}
-
-body[dir="rtl"] .error-text {
- right: 3px;
-}
-
-:-moz-any(input, textarea, select):focus ~ .error-text:not(:empty)::before {
+/* ::before is the error on the error text panel */
+:-moz-any(input, textarea, select) ~ .error-text::before {
background-color: #d70022;
top: -7px;
content: '.';
@@ -34,17 +30,17 @@ body[dir="rtl"] .error-text {
z-index: -1
}
-body[dir=ltr] .error-text::before {
+/* Position the arrow */
+.error-text:dir(ltr)::before {
left: 12px
}
-body[dir=rtl] .error-text::before {
+.error-text:dir(rtl)::before {
right: 12px
}
-:-moz-any(input, textarea, select):not(:focus) ~ .error-text,
-:-moz-any(input, textarea, select):valid ~ .error-text {
- display: none;
+:-moz-any(input, textarea, select):-moz-ui-invalid:focus ~ .error-text {
+ visibility: visible;
}
address-form > footer > .cancel-button {
diff --git a/browser/components/payments/res/containers/address-form.js b/browser/components/payments/res/containers/address-form.js
index bd4781e5804b..a9a31f46f9e8 100644
--- a/browser/components/payments/res/containers/address-form.js
+++ b/browser/components/payments/res/containers/address-form.js
@@ -4,6 +4,7 @@
/* import-globals-from ../../../../../browser/extensions/formautofill/content/autofillEditForms.js*/
import LabelledCheckbox from "../components/labelled-checkbox.js";
+import PaymentDialog from "./payment-dialog.js";
import PaymentRequestPage from "../components/payment-request-page.js";
import PaymentStateSubscriberMixin from "../mixins/PaymentStateSubscriberMixin.js";
import paymentRequest from "../paymentRequest.js";
@@ -97,6 +98,12 @@ export default class AddressForm extends PaymentStateSubscriberMixin(PaymentRequ
this.form.addEventListener("invalid", this);
this.form.addEventListener("change", this);
+ // The "invalid" event does not bubble and needs to be listened for on each
+ // form element.
+ for (let field of this.form.elements) {
+ field.addEventListener("invalid", this);
+ }
+
this.body.appendChild(this.persistCheckbox);
this.body.appendChild(this.genericErrorText);
@@ -176,40 +183,11 @@ export default class AddressForm extends PaymentStateSubscriberMixin(PaymentRequ
let container = this.form.querySelector(errorSelector + "-container");
let field = this.form.querySelector(errorSelector);
let errorText = (shippingAddressErrors && shippingAddressErrors[errorName]) || "";
- container.classList.toggle("error", !!errorText);
field.setCustomValidity(errorText);
- let span = container.querySelector(".error-text");
- if (!span) {
- span = document.createElement("span");
- span.className = "error-text";
- container.appendChild(span);
- }
+ let span = PaymentDialog.maybeCreateFieldErrorElement(container);
span.textContent = errorText;
}
- // Position the error messages all at once so layout flushes only once.
- let formRect = this.form.getBoundingClientRect();
- let errorSpanData = [...this.form.querySelectorAll(".error-text:not(:empty)")].map(span => {
- let relatedInput = span.parentNode.querySelector("input, textarea, select");
- let relatedRect = relatedInput.getBoundingClientRect();
- return {
- span,
- top: relatedRect.height,
- left: relatedRect.left - formRect.left,
- right: formRect.right - relatedRect.right,
- };
- });
- let isRTL = this.form.matches(":dir(rtl)");
- for (let data of errorSpanData) {
- // Add 10px for the padding-top and padding-bottom.
- data.span.style.top = (data.top + 10) + "px";
- if (isRTL) {
- data.span.style.right = data.right + "px";
- } else {
- data.span.style.left = data.left + "px";
- }
- }
-
this.updateSaveButtonState();
}
@@ -228,7 +206,12 @@ export default class AddressForm extends PaymentStateSubscriberMixin(PaymentRequ
break;
}
case "invalid": {
- this.onInvalid(event);
+ if (event.target instanceof HTMLFormElement) {
+ this.onInvalidForm(event);
+ break;
+ }
+
+ this.onInvalidField(event);
break;
}
}
@@ -269,20 +252,22 @@ export default class AddressForm extends PaymentStateSubscriberMixin(PaymentRequ
}
onInput(event) {
- let container = event.target.closest(`#${event.target.id}-container`);
- if (container) {
- container.classList.remove("error");
- event.target.setCustomValidity("");
-
- let span = container.querySelector(".error-text");
- if (span) {
- span.textContent = "";
- }
- }
+ event.target.setCustomValidity("");
this.updateSaveButtonState();
}
- onInvalid(event) {
+ /**
+ * @param {Event} event - "invalid" event
+ * Note: Keep this in-sync with the equivalent version in basic-card-form.js
+ */
+ onInvalidField(event) {
+ let field = event.target;
+ let container = field.closest(`#${field.id}-container`);
+ let errorTextSpan = PaymentDialog.maybeCreateFieldErrorElement(container);
+ errorTextSpan.textContent = field.validationMessage;
+ }
+
+ onInvalidForm() {
this.saveButton.disabled = true;
}
diff --git a/browser/components/payments/res/containers/basic-card-form.js b/browser/components/payments/res/containers/basic-card-form.js
index 52f240ee2ccb..8009a44b6288 100644
--- a/browser/components/payments/res/containers/basic-card-form.js
+++ b/browser/components/payments/res/containers/basic-card-form.js
@@ -5,6 +5,7 @@
/* import-globals-from ../../../../../browser/extensions/formautofill/content/autofillEditForms.js*/
import AcceptedCards from "../components/accepted-cards.js";
import LabelledCheckbox from "../components/labelled-checkbox.js";
+import PaymentDialog from "./payment-dialog.js";
import PaymentRequestPage from "../components/payment-request-page.js";
import PaymentStateSubscriberMixin from "../mixins/PaymentStateSubscriberMixin.js";
import paymentRequest from "../paymentRequest.js";
@@ -97,6 +98,12 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
form.addEventListener("input", this);
form.addEventListener("invalid", this);
+ // The "invalid" event does not bubble and needs to be listened for on each
+ // form element.
+ for (let field of this.form.elements) {
+ field.addEventListener("invalid", this);
+ }
+
let fragment = document.createDocumentFragment();
fragment.append(this.addressAddLink);
fragment.append(" ");
@@ -222,7 +229,12 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
break;
}
case "invalid": {
- this.onInvalid(event);
+ if (event.target instanceof HTMLFormElement) {
+ this.onInvalidForm(event);
+ break;
+ }
+
+ this.onInvalidField(event);
break;
}
}
@@ -319,10 +331,22 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
}
onInput(event) {
+ event.target.setCustomValidity("");
this.updateSaveButtonState();
}
- onInvalid(event) {
+ /**
+ * @param {Event} event - "invalid" event
+ * Note: Keep this in-sync with the equivalent version in address-form.js
+ */
+ onInvalidField(event) {
+ let field = event.target;
+ let container = field.closest(`#${field.id}-container`);
+ let errorTextSpan = PaymentDialog.maybeCreateFieldErrorElement(container);
+ errorTextSpan.textContent = field.validationMessage;
+ }
+
+ onInvalidForm() {
this.saveButton.disabled = true;
}
diff --git a/browser/components/payments/res/containers/payment-dialog.js b/browser/components/payments/res/containers/payment-dialog.js
index e98757b0fd99..efdfdcc2506a 100644
--- a/browser/components/payments/res/containers/payment-dialog.js
+++ b/browser/components/payments/res/containers/payment-dialog.js
@@ -382,6 +382,16 @@ export default class PaymentDialog extends PaymentStateSubscriberMixin(HTMLEleme
this.setAttribute("complete-status", request.completeStatus);
this._disabledOverlay.hidden = !state.changesPrevented;
}
+
+ static maybeCreateFieldErrorElement(container) {
+ let span = container.querySelector(".error-text");
+ if (!span) {
+ span = document.createElement("span");
+ span.className = "error-text";
+ container.appendChild(span);
+ }
+ return span;
+ }
}
customElements.define("payment-dialog", PaymentDialog);
diff --git a/browser/components/payments/res/containers/payment-method-picker.js b/browser/components/payments/res/containers/payment-method-picker.js
index d1acbaa035d8..a04fbc697a20 100644
--- a/browser/components/payments/res/containers/payment-method-picker.js
+++ b/browser/components/payments/res/containers/payment-method-picker.js
@@ -20,6 +20,9 @@ export default class PaymentMethodPicker extends RichPicker {
this.securityCodeInput.autocomplete = "off";
this.securityCodeInput.placeholder = this.dataset.cvvPlaceholder;
this.securityCodeInput.size = 3;
+ this.securityCodeInput.required = true;
+ // 3 or more digits
+ this.securityCodeInput.pattern = "[0-9]{3,}";
this.securityCodeInput.classList.add("security-code");
this.securityCodeInput.addEventListener("change", this);
}
diff --git a/browser/components/payments/res/containers/rich-picker.css b/browser/components/payments/res/containers/rich-picker.css
index 5a099706dfe7..41309ff8eb21 100644
--- a/browser/components/payments/res/containers/rich-picker.css
+++ b/browser/components/payments/res/containers/rich-picker.css
@@ -28,7 +28,7 @@
.rich-picker > .edit-link {
grid-area: edit;
- border-right: 1px solid #0C0C0D33;
+ border-inline-end: 1px solid #0C0C0D33;
}
.rich-picker > rich-select {
@@ -60,8 +60,10 @@ payment-method-picker.rich-picker {
payment-method-picker > input {
border: 1px solid #0C0C0D33;
- border-left: none;
+ border-inline-start: none;
grid-area: cvv;
margin: 14px 0; /* Has to be same as rich-select */
padding: 8px;
+ /* So the error outline appears above the adjacent dropdown */
+ z-index: 1;
}
diff --git a/browser/components/payments/res/debugging.html b/browser/components/payments/res/debugging.html
index 3b33b97b9765..18b62b5f0a5c 100644
--- a/browser/components/payments/res/debugging.html
+++ b/browser/components/payments/res/debugging.html
@@ -16,6 +16,7 @@
+
Requests
diff --git a/browser/components/payments/res/debugging.js b/browser/components/payments/res/debugging.js
index 2fcdebeb2cdf..f00a9e6789a8 100644
--- a/browser/components/payments/res/debugging.js
+++ b/browser/components/payments/res/debugging.js
@@ -514,6 +514,11 @@ let buttonActions = {
request: Object.assign({}, request, { completeStatus }),
});
},
+
+ toggleDirectionality() {
+ let body = paymentDialog.ownerDocument.body;
+ body.dir = body.dir == "rtl" ? "ltr" : "rtl";
+ },
};
window.addEventListener("click", function onButtonClick(evt) {
diff --git a/browser/components/payments/res/paymentRequest.css b/browser/components/payments/res/paymentRequest.css
index 991927260b9d..9a257a21905e 100644
--- a/browser/components/payments/res/paymentRequest.css
+++ b/browser/components/payments/res/paymentRequest.css
@@ -145,7 +145,7 @@ payment-dialog #pay::before {
content: url(chrome://browser/skin/connection-secure.svg);
fill: currentColor;
height: 16px;
- margin-right: 0.5em;
+ margin-inline-end: 0.5em;
vertical-align: text-bottom;
width: 16px;
}
diff --git a/browser/components/payments/test/browser/browser_card_edit.js b/browser/components/payments/test/browser/browser_card_edit.js
index 0943966094b0..8b71b1b39937 100644
--- a/browser/components/payments/test/browser/browser_card_edit.js
+++ b/browser/components/payments/test/browser/browser_card_edit.js
@@ -2,6 +2,8 @@
"use strict";
+requestLongerTimeout(2);
+
async function setup(addresses = [], cards = []) {
await setupFormAutofillStorage();
await cleanupFormAutofillStorage();
diff --git a/browser/components/payments/test/mochitest/mochitest.ini b/browser/components/payments/test/mochitest/mochitest.ini
index 9f940851d673..55fa35736279 100644
--- a/browser/components/payments/test/mochitest/mochitest.ini
+++ b/browser/components/payments/test/mochitest/mochitest.ini
@@ -14,6 +14,7 @@ skip-if = !e10s
[test_accepted_cards.html]
[test_address_form.html]
[test_address_option.html]
+skip-if = os == "linux" # Bug 1490077 comment 7
[test_address_picker.html]
[test_basic_card_form.html]
[test_basic_card_option.html]
diff --git a/browser/components/payments/test/mochitest/test_address_form.html b/browser/components/payments/test/mochitest/test_address_form.html
index 062625f64e16..8c2933b50ce5 100644
--- a/browser/components/payments/test/mochitest/test_address_form.html
+++ b/browser/components/payments/test/mochitest/test_address_form.html
@@ -518,6 +518,47 @@ add_task(async function test_field_validation() {
form.remove();
});
+
+add_task(async function test_field_validation_dom_errors() {
+ let form = new AddressForm();
+ await form.promiseReady;
+ const state = {
+ page: {
+ id: "address-page",
+ },
+ "address-page": {
+ title: "Sample page title",
+ },
+ };
+ await form.requestStore.setState(state);
+ display.appendChild(form);
+ await asyncElementRendered();
+
+ const BAD_POSTAL_CODE = "hi mom";
+ let postalCode = document.getElementById("postal-code");
+ postalCode.focus();
+ sendString(BAD_POSTAL_CODE, window);
+ postalCode.blur();
+ let errorTextSpan = postalCode.parentNode.querySelector(".error-text");
+ is(errorTextSpan.textContent, "Please match the requested format.",
+ "DOM validation messages should be reflected in the error-text #1");
+
+ postalCode.focus();
+ while (postalCode.value) {
+ sendKey("BACK_SPACE", window);
+ }
+ postalCode.blur();
+ is(errorTextSpan.textContent, "Please fill out this field.",
+ "DOM validation messages should be reflected in the error-text #2");
+
+ postalCode.focus();
+ sendString("12345", window);
+ is(errorTextSpan.innerText, "", "DOM validation message should be removed when no error");
+ postalCode.blur();
+
+ form.remove();
+});
+
-
+
+
@@ -53,6 +55,13 @@ function checkCCForm(customEl, expectedCard) {
}
}
+add_task(async function setup_once() {
+ let templateFrame = document.getElementById("templateFrame");
+ await SimpleTest.promiseFocus(templateFrame.contentWindow);
+ let displayEl = document.getElementById("display");
+ importDialogDependencies(templateFrame, displayEl);
+});
+
add_task(async function test_initialState() {
let form = new BasicCardForm();
let {page} = form.requestStore.getState();
@@ -143,6 +152,7 @@ add_task(async function test_saveButton() {
let messagePromise = promiseContentToChromeMessage("updateAutofillRecord");
is(form.saveButton.textContent, "Add", "Check label");
+ form.saveButton.scrollIntoView();
synthesizeMouseAtCenter(form.saveButton, {});
let details = await messagePromise;
diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm
index 8d4eb4050db5..657ebd1c41b3 100644
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -34,11 +34,12 @@ class UrlbarInput {
constructor(options = {}) {
this.textbox = options.textbox;
this.panel = options.panel;
+ this.window = this.textbox.ownerGlobal;
this.controller = options.controller || new UrlbarController();
this.view = new UrlbarView(this);
this.valueIsTyped = false;
this.userInitiatedFocus = false;
- this.isPrivate = PrivateBrowsingUtils.isWindowPrivate(this.panel.ownerGlobal);
+ this.isPrivate = PrivateBrowsingUtils.isWindowPrivate(this.window);
const METHODS = ["addEventListener", "removeEventListener",
"setAttribute", "hasAttribute", "removeAttribute", "getAttribute",
@@ -75,6 +76,9 @@ class UrlbarInput {
}
this.addEventListener("input", this);
+ this.inputField.addEventListener("overflow", this);
+ this.inputField.addEventListener("underflow", this);
+ this.inputField.addEventListener("scrollend", this);
}
formatValue() {
@@ -104,6 +108,25 @@ class UrlbarInput {
// Private methods below.
+ _updateTextOverflow() {
+ if (!this._inOverflow) {
+ this.removeAttribute("textoverflow");
+ return;
+ }
+
+ this.window.promiseDocumentFlushed(() => {
+ // Check overflow again to ensure it didn't change in the meantime.
+ let input = this.inputField;
+ if (input && this._inOverflow) {
+ let side = input.scrollLeft &&
+ input.scrollLeft == input.scrollLeftMax ? "start" : "end";
+ this.setAttribute("textoverflow", side);
+ }
+ });
+ }
+
+ // Event handlers below.
+
_oninput(event) {
// XXX Fill in lastKey & maxResults, and add anything else we need.
this.controller.handleQuery(new QueryContext({
@@ -113,4 +136,32 @@ class UrlbarInput {
isPrivate: this.isPrivate,
}));
}
+
+ _onoverflow(event) {
+ const targetIsPlaceholder =
+ !event.originalTarget.classList.contains("anonymous-div");
+ // We only care about the non-placeholder text.
+ // This shouldn't be needed, see bug 1487036.
+ if (targetIsPlaceholder) {
+ return;
+ }
+ this._inOverflow = true;
+ this._updateTextOverflow();
+ }
+
+ _onunderflow(event) {
+ const targetIsPlaceholder =
+ !event.originalTarget.classList.contains("anonymous-div");
+ // We only care about the non-placeholder text.
+ // This shouldn't be needed, see bug 1487036.
+ if (targetIsPlaceholder) {
+ return;
+ }
+ this._inOverflow = false;
+ this._updateTextOverflow();
+ }
+
+ _onscrollend(event) {
+ this._updateTextOverflow();
+ }
}
diff --git a/browser/components/urlbar/tests/unit/test_UrlbarInput_unit.js b/browser/components/urlbar/tests/unit/test_UrlbarInput_unit.js
index b92b76595e60..3f06de5d1d8d 100644
--- a/browser/components/urlbar/tests/unit/test_UrlbarInput_unit.js
+++ b/browser/components/urlbar/tests/unit/test_UrlbarInput_unit.js
@@ -76,8 +76,10 @@ add_task(function setup() {
sandbox.stub(fakeController, "handleQuery");
sandbox.stub(PrivateBrowsingUtils, "isWindowPrivate").returns(false);
+ let textbox = createFakeElement();
+ textbox.inputField = createFakeElement();
inputOptions = {
- textbox: createFakeElement(),
+ textbox,
panel: {
ownerDocument: {},
querySelector() {
diff --git a/browser/themes/shared/tabs.inc.css b/browser/themes/shared/tabs.inc.css
index 6a39629ddddb..4d20724a9141 100644
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -702,7 +702,7 @@
.tabbrowser-arrowscrollbox > .scrollbutton-up,
.tabbrowser-arrowscrollbox > .scrollbutton-down {
- list-style-image: url(chrome://browser/skin/arrow-left.svg);
+ list-style-image: url(chrome://browser/skin/arrow-left.svg) !important;
-moz-context-properties: fill, fill-opacity;
fill: currentColor;
fill-opacity: var(--toolbarbutton-icon-fill-opacity);
diff --git a/browser/themes/shared/toolbarbutton-icons.inc.css b/browser/themes/shared/toolbarbutton-icons.inc.css
index b913e11f19d0..41c2f8b92912 100644
--- a/browser/themes/shared/toolbarbutton-icons.inc.css
+++ b/browser/themes/shared/toolbarbutton-icons.inc.css
@@ -13,7 +13,7 @@ toolbar[brighttext] {
.toolbarbutton-animatable-box,
.toolbarbutton-1 {
- color: inherit;
+ color: inherit !important;
-moz-context-properties: fill, fill-opacity;
fill: var(--lwt-toolbarbutton-icon-fill, currentColor);
fill-opacity: var(--toolbarbutton-icon-fill-opacity);
diff --git a/browser/themes/shared/toolbarbuttons.inc.css b/browser/themes/shared/toolbarbuttons.inc.css
index 70d590f174b3..1c44261fc286 100644
--- a/browser/themes/shared/toolbarbuttons.inc.css
+++ b/browser/themes/shared/toolbarbuttons.inc.css
@@ -63,7 +63,7 @@ toolbar[brighttext] {
}
.toolbarbutton-1 > .toolbarbutton-icon {
- margin-inline-end: 0;
+ margin-inline-end: 0 !important;
}
.toolbarbutton-1 > .toolbarbutton-icon,
@@ -74,13 +74,13 @@ toolbar[brighttext] {
#TabsToolbar .toolbarbutton-1,
.tabbrowser-arrowscrollbox > .scrollbutton-up,
.tabbrowser-arrowscrollbox > .scrollbutton-down {
- margin: 0 0 @navbarTabsShadowSize@;
+ margin: 0 0 @navbarTabsShadowSize@ !important;
}
.tabbrowser-arrowscrollbox > .scrollbutton-up,
.tabbrowser-arrowscrollbox > .scrollbutton-down {
-moz-appearance: none;
- padding: 0 var(--toolbarbutton-inner-padding);
+ padding: 0 var(--toolbarbutton-inner-padding) !important;
}
#navigator-toolbox:not(:hover) > #TabsToolbar > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .scrollbutton-down:not([highlight]) {
@@ -100,13 +100,13 @@ toolbar[brighttext] {
toolbar .toolbarbutton-1 {
-moz-appearance: none;
margin: 0;
- padding: 0 var(--toolbarbutton-outer-padding);
+ padding: 0 var(--toolbarbutton-outer-padding) !important;
-moz-box-pack: center;
}
:root:not([uidensity=compact]) #PanelUI-menu-button {
- padding-inline-start: 5px;
- padding-inline-end: 5px;
+ padding-inline-start: 5px !important;
+ padding-inline-end: 5px !important;
}
toolbar .toolbarbutton-1 > menupopup {
@@ -149,8 +149,8 @@ toolbar .toolbaritem-combined-buttons {
}
toolbar .toolbaritem-combined-buttons > .toolbarbutton-1 {
- padding-left: 0;
- padding-right: 0;
+ padding-left: 0 !important;
+ padding-right: 0 !important;
}
toolbar .toolbaritem-combined-buttons:not(:hover) > separator {
@@ -199,9 +199,9 @@ toolbar .toolbarbutton-1[checked]:not(:active):hover > .toolbarbutton-icon {
}
:root:not([uidensity=compact]) #back-button {
- padding-top: 3px;
- padding-bottom: 3px;
- padding-inline-start: 3px;
+ padding-top: 3px !important;
+ padding-bottom: 3px !important;
+ padding-inline-start: 3px !important;
padding-inline-end: 0 !important;
position: relative !important;
z-index: 1 !important;
@@ -230,9 +230,9 @@ toolbar .toolbarbutton-1[checked]:not(:active):hover > .toolbarbutton-icon {
}
:root[uidensity=touch] #back-button {
- padding-top: 1px;
- padding-bottom: 1px;
- padding-inline-start: 1px;
+ padding-top: 1px !important;
+ padding-bottom: 1px !important;
+ padding-inline-start: 1px !important;
}
:root[uidensity=touch] #back-button > .toolbarbutton-icon {
@@ -343,6 +343,6 @@ toolbarbutton.bookmark-item {
}
#PersonalToolbar .toolbarbutton-1 {
- padding: 1px var(--toolbarbutton-inner-padding);
+ padding: 1px var(--toolbarbutton-inner-padding) !important;
border-radius: var(--toolbarbutton-border-radius);
}
diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp
index 08b477bb8102..697fc2b8d096 100644
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -3294,9 +3294,6 @@ nsFocusManager::GetNextTabbableContentInScope(nsIContent* aOwner,
break;
}
- // Get the tab index of the next element. For NAC we rely on frames.
- //XXXsmaug we should probably use frames also for Shadow DOM and special
- // case only display:contents elements.
int32_t tabIndex = 0;
if (iterContent->IsInNativeAnonymousSubtree() &&
iterContent->GetPrimaryFrame()) {
@@ -3304,7 +3301,11 @@ nsFocusManager::GetNextTabbableContentInScope(nsIContent* aOwner,
} else if (IsHostOrSlot(iterContent)) {
tabIndex = HostOrSlotTabIndexValue(iterContent);
} else {
- iterContent->IsFocusable(&tabIndex);
+ nsIFrame* frame = iterContent->GetPrimaryFrame();
+ if (!frame) {
+ continue;
+ }
+ frame->IsFocusable(&tabIndex, 0);
}
if (tabIndex < 0 || !(aIgnoreTabIndex || tabIndex == aCurrentTabIndex)) {
// If the element has native anonymous content, we may need to
diff --git a/dom/base/test/file_bug1453693.html b/dom/base/test/file_bug1453693.html
index 430d6b4b7aae..7d751c279934 100644
--- a/dom/base/test/file_bug1453693.html
+++ b/dom/base/test/file_bug1453693.html
@@ -125,6 +125,10 @@
shadowInput.onfocus = focusLogger;
sr.appendChild(shadowInput);
+ var hiddenShadowButton = document.createElement("button");
+ hiddenShadowButton.setAttribute("style", "display: none;");
+ sr.appendChild(hiddenShadowButton);
+
var input = document.createElement("input");
input.onfocus = focusLogger;
document.body.appendChild(input);
diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp
index be449ec72600..767feafdcc3e 100644
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -292,6 +292,45 @@ ScalarTypeDescr::call(JSContext* cx, unsigned argc, Value* vp)
return true;
}
+/* static */ TypeDescr*
+GlobalObject::getOrCreateScalarTypeDescr(JSContext* cx, Handle
global,
+ Scalar::Type scalarType)
+{
+ int32_t slot = 0;
+ switch (scalarType) {
+ case Scalar::Int32: slot = TypedObjectModuleObject::Int32Desc; break;
+ case Scalar::Int64: MOZ_CRASH("No Int64 support yet");
+ case Scalar::Float32: slot = TypedObjectModuleObject::Float32Desc; break;
+ case Scalar::Float64: slot = TypedObjectModuleObject::Float64Desc; break;
+ default: MOZ_CRASH("NYI");
+ }
+
+ Rooted module(cx,
+ &GlobalObject::getOrCreateTypedObjectModule(cx, global)->as());
+ if (!module) {
+ return nullptr;
+ }
+ return &module->getReservedSlot(slot).toObject().as();
+}
+
+/* static */ TypeDescr*
+GlobalObject::getOrCreateReferenceTypeDescr(JSContext* cx, Handle global,
+ ReferenceType type)
+{
+ int32_t slot = 0;
+ switch (type) {
+ case ReferenceType::TYPE_OBJECT: slot = TypedObjectModuleObject::ObjectDesc; break;
+ default: MOZ_CRASH("NYI");
+ }
+
+ Rooted module(cx,
+ &GlobalObject::getOrCreateTypedObjectModule(cx, global)->as());
+ if (!module) {
+ return nullptr;
+ }
+ return &module->getReservedSlot(slot).toObject().as();
+}
+
/***************************************************************************
* Reference type objects
*
@@ -796,7 +835,7 @@ StructMetaTypeDescr::create(JSContext* cx,
AutoValueVector fieldTypeObjs(cx); // Type descriptor of each field.
bool opaque = false; // Opacity of struct.
- Vector fieldMutabilities(cx);
+ Vector fieldProps(cx);
RootedValue fieldTypeVal(cx);
RootedId id(cx);
@@ -830,7 +869,9 @@ StructMetaTypeDescr::create(JSContext* cx,
}
// Along this path everything is mutable
- if (!fieldMutabilities.append(true)) {
+ StructFieldProps props;
+ props.isMutable = true;
+ if (!fieldProps.append(props)) {
return nullptr;
}
@@ -846,7 +887,7 @@ StructMetaTypeDescr::create(JSContext* cx,
}
return createFromArrays(cx, structTypePrototype, opaque, /* allowConstruct= */ true, ids,
- fieldTypeObjs, fieldMutabilities);
+ fieldTypeObjs, fieldProps);
}
/* static */ StructTypeDescr*
@@ -856,7 +897,7 @@ StructMetaTypeDescr::createFromArrays(JSContext* cx,
bool allowConstruct,
AutoIdVector& ids,
AutoValueVector& fieldTypeObjs,
- Vector& fieldMutabilities)
+ Vector& fieldProps)
{
StringBuffer stringBuffer(cx); // Canonical string repr
AutoValueVector fieldNames(cx); // Name of each field.
@@ -915,7 +956,10 @@ StructMetaTypeDescr::createFromArrays(JSContext* cx,
return nullptr;
}
- CheckedInt32 offset = layout.addField(fieldType->alignment(), fieldType->size());
+ CheckedInt32 offset = layout.addField(fieldProps[i].alignAsInt64
+ ? ScalarTypeDescr::alignment(Scalar::Int64)
+ : fieldType->alignment(),
+ fieldType->size());
if (!offset.isValid()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG);
return nullptr;
@@ -924,8 +968,7 @@ StructMetaTypeDescr::createFromArrays(JSContext* cx,
if (!fieldOffsets.append(Int32Value(offset.value()))) {
return nullptr;
}
-
- if (!fieldMuts.append(BooleanValue(fieldMutabilities[i]))) {
+ if (!fieldMuts.append(BooleanValue(fieldProps[i].isMutable))) {
return nullptr;
}
@@ -1371,6 +1414,22 @@ GlobalObject::initTypedObjectModule(JSContext* cx, Handle global)
JS_FOR_EACH_REFERENCE_TYPE_REPR(BINARYDATA_REFERENCE_DEFINE)
#undef BINARYDATA_REFERENCE_DEFINE
+ // Tuck away descriptors we will use for wasm.
+
+ RootedValue typeDescr(cx);
+
+ MOZ_ALWAYS_TRUE(JS_GetProperty(cx, module, "int32", &typeDescr));
+ module->initReservedSlot(TypedObjectModuleObject::Int32Desc, typeDescr);
+
+ MOZ_ALWAYS_TRUE(JS_GetProperty(cx, module, "float32", &typeDescr));
+ module->initReservedSlot(TypedObjectModuleObject::Float32Desc, typeDescr);
+
+ MOZ_ALWAYS_TRUE(JS_GetProperty(cx, module, "float64", &typeDescr));
+ module->initReservedSlot(TypedObjectModuleObject::Float64Desc, typeDescr);
+
+ MOZ_ALWAYS_TRUE(JS_GetProperty(cx, module, "Object", &typeDescr));
+ module->initReservedSlot(TypedObjectModuleObject::ObjectDesc, typeDescr);
+
// ArrayType.
RootedObject arrayType(cx);
diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h
index f9951261f299..c60225919e89 100644
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -403,6 +403,13 @@ class ArrayTypeDescr : public ComplexTypeDescr
}
};
+struct StructFieldProps
+{
+ StructFieldProps() : isMutable(0), alignAsInt64(0) {}
+ uint32_t isMutable:1;
+ uint32_t alignAsInt64:1;
+};
+
/*
* Properties and methods of the `StructType` meta type object. There
* is no `class_` field because `StructType` is just a native
@@ -424,7 +431,7 @@ class StructMetaTypeDescr : public NativeObject
bool allowConstruct,
AutoIdVector& ids,
AutoValueVector& fieldTypeObjs,
- Vector& fieldMutabilities);
+ Vector& fieldProps);
// Properties and methods to be installed on StructType.prototype,
// and hence inherited by all struct type objects:
@@ -506,6 +513,11 @@ class TypedObjectModuleObject : public NativeObject {
enum Slot {
ArrayTypePrototype,
StructTypePrototype,
+ Int32Desc,
+ Int64Desc,
+ Float32Desc,
+ Float64Desc,
+ ObjectDesc,
SlotCount
};
diff --git a/js/src/jit-test/lib/wasm-binary.js b/js/src/jit-test/lib/wasm-binary.js
index 3f9d959e2adf..38853a9af139 100644
--- a/js/src/jit-test/lib/wasm-binary.js
+++ b/js/src/jit-test/lib/wasm-binary.js
@@ -40,6 +40,7 @@ const I64Code = 0x7e;
const F32Code = 0x7d;
const F64Code = 0x7c;
const AnyFuncCode = 0x70;
+const AnyrefCode = 0x6f;
const RefCode = 0x6e;
const FuncCode = 0x60;
const VoidCode = 0x40;
@@ -106,6 +107,12 @@ const SimdPrefix = 0xfd;
const ThreadPrefix = 0xfe;
const MozPrefix = 0xff;
+// Secondary opcode bytes for misc prefix
+const StructNew = 0x50; // UNOFFICIAL
+const StructGet = 0x51; // UNOFFICIAL
+const StructSet = 0x52; // UNOFFICIAL
+const StructNarrow = 0x53; // UNOFFICIAL
+
// DefinitionKind
const FunctionCode = 0x00;
const TableCode = 0x01;
diff --git a/js/src/jit-test/tests/wasm/binary.js b/js/src/jit-test/tests/wasm/binary.js
index 46fdf41c9ee7..38acf479bd95 100644
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -275,16 +275,18 @@ for (let i = 3; i < 0x10; i++)
for (let i = 0x4f; i < 0x100; i++)
checkIllegalPrefixed(ThreadPrefix, i);
-// Illegal Numeric opcodes
-//
-// Feb 2018 numeric draft:
-//
-// 0x00 .. 0x07 are saturating truncation ops. 0x08 .. 0x0e are from the
-// bulk memory proposal. 0x08 .. 0x0e are unofficial values, until such
-// time as there is an official assignment for memory.copy/fill subopcodes.
+// Illegal Misc opcodes
+
+var reservedMisc =
+ { // Saturating conversions (standardized)
+ 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,
+ // Structure operations (experimental, internal)
+ 0x50: true, 0x51: true, 0x52: true, 0x53: true };
for (let i = 0; i < 256; i++) {
- if (i <= 0x07 || (i >= 0x08 && i <= 0x0e))
+ if (reservedMisc.hasOwnProperty(i))
continue;
checkIllegalPrefixed(MiscPrefix, i);
}
diff --git a/js/src/jit-test/tests/wasm/gc/TypedObject.js b/js/src/jit-test/tests/wasm/gc/TypedObject.js
new file mode 100644
index 000000000000..bf69760b992f
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/TypedObject.js
@@ -0,0 +1,150 @@
+if (!wasmGcEnabled())
+ quit(0);
+
+// We can read the object fields from JS, and write them if they are mutable.
+
+{
+ let ins = wasmEvalText(`(module
+ (gc_feature_opt_in 1)
+
+ (type $p (struct (field f64) (field (mut i32))))
+
+ (func (export "mkp") (result anyref)
+ (struct.new $p (f64.const 1.5) (i32.const 33))))`).exports;
+
+ let p = ins.mkp();
+ assertEq(p._0, 1.5);
+ assertEq(p._1, 33);
+ assertEq(p._2, undefined);
+
+ p._1 = 44;
+ assertEq(p._1, 44);
+}
+
+// Writing an immutable field from JS throws.
+
+{
+ let ins = wasmEvalText(`(module
+ (gc_feature_opt_in 1)
+
+ (type $p (struct (field f64)))
+
+ (func (export "mkp") (result anyref)
+ (struct.new $p (f64.const 1.5))))`).exports;
+
+ let p = ins.mkp();
+ assertErrorMessage(() => p._0 = 5.7,
+ Error,
+ /setting immutable field/);
+}
+
+// MVA v1 restriction: structs that expose ref-typed fields should not be
+// constructible from JS.
+//
+// However, if the fields are anyref the structs can be constructed from JS.
+
+{
+ let ins = wasmEvalText(`(module
+ (gc_feature_opt_in 1)
+
+ (type $q (struct (field (mut f64))))
+ (type $p (struct (field (mut (ref $q)))))
+
+ (type $r (struct (field (mut anyref))))
+
+ (func (export "mkp") (result anyref)
+ (struct.new $p (ref.null (ref $q))))
+
+ (func (export "mkr") (result anyref)
+ (struct.new $r (ref.null anyref))))`).exports;
+
+ assertEq(typeof ins.mkp().constructor, "function");
+ assertErrorMessage(() => new (ins.mkp().constructor)({_0:null}),
+ TypeError,
+ /not constructible/);
+
+ assertEq(typeof ins.mkr().constructor, "function");
+ let r = new (ins.mkr().constructor)({_0:null});
+ assertEq(typeof r, "object");
+}
+
+// MVA v1 restriction: structs that expose ref-typed fields make those fields
+// immutable from JS even if we're trying to store the correct type.
+//
+// However, anyref fields are mutable from JS.
+
+{
+ let ins = wasmEvalText(`(module
+ (gc_feature_opt_in 1)
+
+ (type $q (struct (field (mut f64))))
+ (type $p (struct (field (mut (ref $q))) (field (mut anyref))))
+
+ (func (export "mkq") (result anyref)
+ (struct.new $q (f64.const 1.5)))
+
+ (func (export "mkp") (result anyref)
+ (struct.new $p (ref.null (ref $q)) (ref.null anyref))))`).exports;
+ let q = ins.mkq();
+ assertEq(typeof q, "object");
+ assertEq(q._0, 1.5);
+ let p = ins.mkp();
+ assertEq(typeof p, "object");
+ assertEq(p._0, null);
+
+ assertErrorMessage(() => { p._0 = q },
+ Error,
+ /setting immutable field/);
+
+ p._1 = q;
+ assertEq(p._1, q);
+}
+
+// MVA v1 restriction: structs that expose i64 fields make those fields
+// immutable from JS, and the structs are not constructible from JS.
+
+{
+ let ins = wasmEvalText(`(module
+ (gc_feature_opt_in 1)
+ (type $p (struct (field (mut i64))))
+ (func (export "mkp") (result anyref)
+ (struct.new $p (i64.const 0x1234567887654321))))`).exports;
+
+ let p = ins.mkp();
+ assertEq(typeof p, "object");
+ assertEq(p._0_high, 0x12345678)
+ assertEq(p._0_low, 0x87654321|0);
+
+ assertEq(typeof p.constructor, "function");
+ assertErrorMessage(() => new (p.constructor)({_0_high:0, _0_low:0}),
+ TypeError,
+ /not constructible/);
+
+ assertErrorMessage(() => { p._0_low = 0 },
+ Error,
+ /setting immutable field/);
+ assertErrorMessage(() => { p._0_high = 0 },
+ Error,
+ /setting immutable field/);
+}
+
+// A consequence of the current mapping of i64 as two i32 fields is that we run
+// a risk of struct.narrow not recognizing the difference. So check this.
+
+{
+ let ins = wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $p (struct (field i64)))
+ (type $q (struct (field i32) (field i32)))
+ (func $f (param anyref) (result i32)
+ (ref.is_null (struct.narrow anyref (ref $q) (get_local 0))))
+ (func $g (param anyref) (result i32)
+ (ref.is_null (struct.narrow anyref (ref $p) (get_local 0))))
+ (func (export "t1") (result i32)
+ (call $f (struct.new $p (i64.const 0))))
+ (func (export "t2") (result i32)
+ (call $g (struct.new $q (i32.const 0) (i32.const 0)))))`).exports;
+ assertEq(ins.t1(), 1);
+ assertEq(ins.t2(), 1);
+}
diff --git a/js/src/jit-test/tests/wasm/gc/binary.js b/js/src/jit-test/tests/wasm/gc/binary.js
index 334d8b3f19d0..caa36347deb5 100644
--- a/js/src/jit-test/tests/wasm/gc/binary.js
+++ b/js/src/jit-test/tests/wasm/gc/binary.js
@@ -30,7 +30,7 @@ const invalidRefNullBody = funcBody({locals:[], body:[
SelectCode,
DropCode
]});
-checkInvalid(invalidRefNullBody, /invalid nullref type/);
+checkInvalid(invalidRefNullBody, /invalid reference type for ref.null/);
const invalidRefBlockType = funcBody({locals:[], body:[
BlockCode,
diff --git a/js/src/jit-test/tests/wasm/gc/gc-feature-opt-in.js b/js/src/jit-test/tests/wasm/gc/gc-feature-opt-in.js
index cc900851f108..35628d230930 100644
--- a/js/src/jit-test/tests/wasm/gc/gc-feature-opt-in.js
+++ b/js/src/jit-test/tests/wasm/gc/gc-feature-opt-in.js
@@ -124,3 +124,27 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
(func ref.eq))`)),
WebAssembly.CompileError,
/unrecognized opcode/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (func (struct.new 0)))`)),
+ WebAssembly.CompileError,
+ /unrecognized opcode/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (func (struct.get 0 0)))`)),
+ WebAssembly.CompileError,
+ /unrecognized opcode/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (func (struct.set 0 0)))`)),
+ WebAssembly.CompileError,
+ /unrecognized opcode/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (func (struct.narrow anyref anyref)))`)),
+ WebAssembly.CompileError,
+ /unrecognized opcode/);
diff --git a/js/src/jit-test/tests/wasm/gc/ref-struct.js b/js/src/jit-test/tests/wasm/gc/ref-struct.js
new file mode 100644
index 000000000000..8bee419d36e3
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/ref-struct.js
@@ -0,0 +1,489 @@
+if (!wasmGcEnabled())
+ quit(0);
+
+// We'll be running some binary-format tests shortly.
+
+load(libdir + "wasm-binary.js");
+
+const v2vSigSection = sigSection([{args:[], ret:VoidCode}]);
+
+function checkInvalid(body, errorMessage) {
+ assertErrorMessage(() => new WebAssembly.Module(
+ moduleWithSections([gcFeatureOptInSection(1),
+ v2vSigSection,
+ declSection([0]),
+ bodySection([body])])),
+ WebAssembly.CompileError,
+ errorMessage);
+}
+
+// General test case for struct.new, struct.get, and struct.set: binary tree
+// manipulation.
+
+{
+ let bin = wasmTextToBinary(
+ `(module
+ (gc_feature_opt_in 1)
+
+ (import $print_lp "" "print_lp" (func))
+ (import $print_rp "" "print_rp" (func))
+ (import $print_int "" "print_int" (func (param i32)))
+
+ (type $wabbit (struct
+ (field $x (mut i32))
+ (field $left (mut (ref $wabbit)))
+ (field $right (mut (ref $wabbit)))))
+
+ (global $g (mut (ref $wabbit)) (ref.null (ref $wabbit)))
+
+ (global $k (mut i32) (i32.const 0))
+
+ (func (export "init") (param $n i32)
+ (set_global $g (call $make (get_local $n))))
+
+ (func $make (param $n i32) (result (ref $wabbit))
+ (local $tmp i32)
+ (set_local $tmp (get_global $k))
+ (set_global $k (i32.add (get_local $tmp) (i32.const 1)))
+ (if (ref $wabbit) (i32.le_s (get_local $n) (i32.const 2))
+ (struct.new $wabbit (get_local $tmp) (ref.null (ref $wabbit)) (ref.null (ref $wabbit)))
+ (block (ref $wabbit)
+ (struct.new $wabbit
+ (get_local $tmp)
+ (call $make (i32.sub (get_local $n) (i32.const 1)))
+ (call $make (i32.sub (get_local $n) (i32.const 2)))))))
+
+ (func (export "accumulate") (result i32)
+ (call $accum (get_global $g)))
+
+ (func $accum (param $w (ref $wabbit)) (result i32)
+ (if i32 (ref.is_null (get_local $w))
+ (i32.const 0)
+ (i32.add (struct.get $wabbit 0 (get_local $w))
+ (i32.sub (call $accum (struct.get $wabbit 1 (get_local $w)))
+ (call $accum (struct.get $wabbit 2 (get_local $w)))))))
+
+ (func (export "reverse")
+ (call $reverse (get_global $g)))
+
+ (func $reverse (param $w (ref $wabbit))
+ (local $tmp (ref $wabbit))
+ (if (i32.eqz (ref.is_null (get_local $w)))
+ (block
+ (struct.set $wabbit 0 (get_local $w) (i32.mul (i32.const 2) (struct.get $wabbit 0 (get_local $w))))
+ (set_local $tmp (struct.get $wabbit 1 (get_local $w)))
+ (struct.set $wabbit 1 (get_local $w) (struct.get $wabbit 2 (get_local $w)))
+ (struct.set $wabbit 2 (get_local $w) (get_local $tmp))
+ (call $reverse (struct.get $wabbit 1 (get_local $w)))
+ (call $reverse (struct.get $wabbit 2 (get_local $w))))))
+
+ (func (export "print")
+ (call $pr (get_global $g)))
+
+ (func $pr (param $w (ref $wabbit))
+ (if (i32.eqz (ref.is_null (get_local $w)))
+ (block
+ (call $print_lp)
+ (call $print_int (struct.get $wabbit 0 (get_local $w)))
+ (call $pr (struct.get $wabbit 1 (get_local $w)))
+ (call $pr (struct.get $wabbit 2 (get_local $w)))
+ (call $print_rp))))
+ )`);
+
+ let s = "";
+ function pr_int(k) { s += k + " "; }
+ function pr_lp() { s += "(" };
+ function pr_rp() { s += ")" }
+
+ let mod = new WebAssembly.Module(bin);
+ let ins = new WebAssembly.Instance(mod, {"":{print_int:pr_int,print_lp:pr_lp,print_rp:pr_rp}}).exports;
+
+ ins.init(6);
+ s = ""; ins.print(); assertEq(s, "(0 (1 (2 (3 (4 )(5 ))(6 ))(7 (8 )(9 )))(10 (11 (12 )(13 ))(14 )))");
+ assertEq(ins.accumulate(), -13);
+
+ ins.reverse();
+ s = ""; ins.print(); assertEq(s, "(0 (20 (28 )(22 (26 )(24 )))(2 (14 (18 )(16 ))(4 (12 )(6 (10 )(8 )))))");
+ assertEq(ins.accumulate(), 14);
+
+ for (let i=10; i < 22; i++ ) {
+ ins.init(i);
+ ins.reverse();
+ gc();
+ ins.reverse();
+ }
+}
+
+// Sanity check for struct.set: we /can/ store a (ref T) into a (ref U) field
+// with struct.set if T <: U; this should fall out of normal coercion but good
+// to test.
+
+wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field (mut (ref $node)))))
+ (type $nix (struct (field (mut (ref $node))) (field i32)))
+ (func $f (param $p (ref $node)) (param $q (ref $nix))
+ (struct.set $node 0 (get_local $p) (get_local $q))))`);
+
+// struct.narrow: if the pointer's null we get null
+
+assertEq(wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (type $node2 (struct (field i32) (field f32)))
+ (func $f (param $p (ref $node)) (result (ref $node2))
+ (struct.narrow (ref $node) (ref $node2) (get_local $p)))
+ (func (export "test") (result anyref)
+ (call $f (ref.null (ref $node)))))`).exports.test(),
+ null);
+
+// struct.narrow: if the downcast succeeds we get the original pointer
+
+assertEq(wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (type $node2 (struct (field i32) (field f32)))
+ (func $f (param $p (ref $node)) (result (ref $node2))
+ (struct.narrow (ref $node) (ref $node2) (get_local $p)))
+ (func (export "test") (result i32)
+ (local $n (ref $node))
+ (set_local $n (struct.new $node2 (i32.const 0) (f32.const 12)))
+ (ref.eq (call $f (get_local $n)) (get_local $n))))`).exports.test(),
+ 1);
+
+// And once more with mutable fields
+
+assertEq(wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field (mut i32))))
+ (type $node2 (struct (field (mut i32)) (field f32)))
+ (func $f (param $p (ref $node)) (result (ref $node2))
+ (struct.narrow (ref $node) (ref $node2) (get_local $p)))
+ (func (export "test") (result i32)
+ (local $n (ref $node))
+ (set_local $n (struct.new $node2 (i32.const 0) (f32.const 12)))
+ (ref.eq (call $f (get_local $n)) (get_local $n))))`).exports.test(),
+ 1);
+
+// A more subtle case: the downcast is to a struct that looks like the original
+// struct should succeed because struct.narrow is a structural cast with nominal
+// per-field type equality.
+//
+// We use ref-typed fields here because they have the trickiest equality rules,
+// and we have two cases: one where the ref types are the same, and one where
+// they reference different structures that look the same; this latter case
+// should fail because our structural compatibility is shallow.
+
+assertEq(wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+
+ (type $node (struct (field i32)))
+ (type $node2a (struct (field i32) (field (ref $node))))
+ (type $node2b (struct (field i32) (field (ref $node))))
+
+ (func $f (param $p (ref $node)) (result (ref $node2b))
+ (struct.narrow (ref $node) (ref $node2b) (get_local $p)))
+
+ (func (export "test") (result i32)
+ (local $n (ref $node))
+ (set_local $n (struct.new $node2a (i32.const 0) (ref.null (ref $node))))
+ (ref.eq (call $f (get_local $n)) (get_local $n))))`).exports.test(),
+ 1);
+
+assertEq(wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+
+ (type $node (struct (field i32)))
+ (type $nodeCopy (struct (field i32)))
+ (type $node2a (struct (field i32) (field (ref $node))))
+ (type $node2b (struct (field i32) (field (ref $nodeCopy))))
+
+ (func $f (param $p (ref $node)) (result (ref $node2b))
+ (struct.narrow (ref $node) (ref $node2b) (get_local $p)))
+
+ (func (export "test") (result i32)
+ (local $n (ref $node))
+ (set_local $n (struct.new $node2a (i32.const 0) (ref.null (ref $node))))
+ (ref.eq (call $f (get_local $n)) (get_local $n))))`).exports.test(),
+ 0);
+
+// Another subtle case: struct.narrow can target a type that is not the concrete
+// type of the object, but a prefix of that concrete type.
+
+assertEq(wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (type $node2 (struct (field i32) (field f32)))
+ (type $node3 (struct (field i32) (field f32) (field f64)))
+ (func $f (param $p (ref $node)) (result (ref $node2))
+ (struct.narrow (ref $node) (ref $node2) (get_local $p)))
+ (func (export "test") (result i32)
+ (local $n (ref $node))
+ (set_local $n (struct.new $node3 (i32.const 0) (f32.const 12) (f64.const 17)))
+ (ref.eq (call $f (get_local $n)) (get_local $n))))`).exports.test(),
+ 1);
+
+// struct.narrow: if the downcast fails we get null
+
+assertEq(wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (type $node2 (struct (field i32) (field f32)))
+ (type $snort (struct (field i32) (field f64)))
+ (func $f (param $p (ref $node)) (result (ref $node2))
+ (struct.narrow (ref $node) (ref $node2) (get_local $p)))
+ (func (export "test") (result anyref)
+ (call $f (struct.new $snort (i32.const 0) (f64.const 12)))))`).exports.test(),
+ null);
+
+// struct.narrow: anyref -> struct when the anyref is the right struct;
+// special case since anyref requires unboxing
+
+assertEq(wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (func $f (param $p anyref) (result (ref $node))
+ (struct.narrow anyref (ref $node) (get_local $p)))
+ (func (export "test") (result i32)
+ (local $n (ref $node))
+ (set_local $n (struct.new $node (i32.const 0)))
+ (ref.eq (call $f (get_local $n)) (get_local $n))))`).exports.test(),
+ 1);
+
+// struct.narrow: anyref -> struct when the anyref is some random gunk.
+
+assertEq(wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (func (export "test") (param $p anyref) (result anyref)
+ (struct.narrow anyref (ref $node) (get_local $p))))`).exports.test({hi:37}),
+ null);
+
+// Types are private to an instance and struct.narrow can't break this
+
+{
+ let txt =
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (func (export "make") (param $n i32) (result anyref)
+ (struct.new $node (get_local $n)))
+ (func (export "coerce") (param $p anyref) (result i32)
+ (ref.is_null (struct.narrow anyref (ref $node) (get_local $p)))))`;
+ let mod = new WebAssembly.Module(wasmTextToBinary(txt));
+ let ins1 = new WebAssembly.Instance(mod).exports;
+ let ins2 = new WebAssembly.Instance(mod).exports;
+ let obj = ins1.make(37);
+ assertEq(obj._0, 37);
+ assertEq(ins2.coerce(obj), 1);
+}
+
+// Negative tests
+
+// Attempting to mutate immutable field with struct.set
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (func $f (param $p (ref $node))
+ (struct.set $node 0 (get_local $p) (i32.const 37))))`),
+ WebAssembly.CompileError,
+ /field is not mutable/);
+
+// Attempting to store incompatible value in mutable field with struct.set
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field (mut i32))))
+ (func $f (param $p (ref $node))
+ (struct.set $node 0 (get_local $p) (f32.const 37))))`),
+ WebAssembly.CompileError,
+ /expression has type f32 but expected i32/);
+
+// Out-of-bounds reference for struct.get
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (func $f (param $p (ref $node)) (result i32)
+ (struct.get $node 1 (get_local $p))))`),
+ WebAssembly.CompileError,
+ /field index out of range/);
+
+// Out-of-bounds reference for struct.set
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field (mut i32))))
+ (func $f (param $p (ref $node))
+ (struct.set $node 1 (get_local $p) (i32.const 37))))`),
+ WebAssembly.CompileError,
+ /field index out of range/);
+
+// Base pointer is of unrelated type to stated type in struct.get
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (type $snort (struct (field f64)))
+ (func $f (param $p (ref $snort)) (result i32)
+ (struct.get $node 0 (get_local $p))))`),
+ WebAssembly.CompileError,
+ /expression has type.*but expected.*/);
+
+// Base pointer is of unrelated type to stated type in struct.set
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field (mut i32))))
+ (type $snort (struct (field f64)))
+ (func $f (param $p (ref $snort)) (result i32)
+ (struct.set $node 0 (get_local $p) (i32.const 0))))`),
+ WebAssembly.CompileError,
+ /expression has type.*but expected.*/);
+
+// Base pointer is of unrelated type to stated type in struct.narrow
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (type $node2 (struct (field i32) (field f32)))
+ (type $snort (struct (field f64)))
+ (func $f (param $p (ref $snort)) (result (ref $node2))
+ (struct.narrow (ref $node) (ref $node2) (get_local 0))))`),
+ WebAssembly.CompileError,
+ /expression has type.*but expected.*/);
+
+// source and target types are compatible except for mutability
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (type $node2 (struct (field (mut i32)) (field f32)))
+ (func $f (param $p (ref $node)) (result (ref $node2))
+ (struct.narrow (ref $node) (ref $node2) (get_local 0))))`),
+ WebAssembly.CompileError,
+ /invalid narrowing operation/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field (mut i32))))
+ (type $node2 (struct (field i32) (field f32)))
+ (func $f (param $p (ref $node)) (result (ref $node2))
+ (struct.narrow (ref $node) (ref $node2) (get_local 0))))`),
+ WebAssembly.CompileError,
+ /invalid narrowing operation/);
+
+// source and target types must be ref types: source syntax
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (func $f (param $p (ref $node)) (result anyref)
+ (struct.narrow i32 anyref (get_local 0))))`),
+ SyntaxError,
+ /struct.narrow requires ref type/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (func $f (param $p (ref $node)) (result anyref)
+ (struct.narrow anyref i32 (get_local 0))))`),
+ SyntaxError,
+ /struct.narrow requires ref type/);
+
+// source and target types must be ref types: binary format
+
+checkInvalid(funcBody({locals:[],
+ body:[
+ RefNull,
+ AnyrefCode,
+ MiscPrefix, StructNarrow, I32Code, AnyrefCode,
+ DropCode
+ ]}),
+ /invalid reference type for struct.narrow/);
+
+checkInvalid(funcBody({locals:[],
+ body:[
+ RefNull,
+ AnyrefCode,
+ MiscPrefix, StructNarrow, AnyrefCode, I32Code,
+ DropCode
+ ]}),
+ /invalid reference type for struct.narrow/);
+
+// target type is anyref so source type must be anyref as well (no upcasts)
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (func $f (param $p (ref $node)) (result anyref)
+ (struct.narrow (ref $node) anyref (get_local 0))))`),
+ WebAssembly.CompileError,
+ /invalid type combination in struct.narrow/);
+
+// target type must be subtype of source type (no upcasts)
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (type $node2 (struct (field i32) (field f32)))
+ (func $f (param $p (ref $node2)) (result anyref)
+ (struct.narrow (ref $node2) (ref $node) (get_local 0))))`),
+ WebAssembly.CompileError,
+ /invalid narrowing operation/);
+
+// Null pointer dereference in struct.get
+
+assertErrorMessage(function() {
+ let ins = wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32)))
+ (func (export "test")
+ (drop (call $f (ref.null (ref $node)))))
+ (func $f (param $p (ref $node)) (result i32)
+ (struct.get $node 0 (get_local $p))))`);
+ ins.exports.test();
+},
+ WebAssembly.RuntimeError,
+ /dereferencing null pointer/);
+
+// Null pointer dereference in struct.set
+
+assertErrorMessage(function() {
+ let ins = wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field (mut i32))))
+ (func (export "test")
+ (call $f (ref.null (ref $node))))
+ (func $f (param $p (ref $node))
+ (struct.set $node 0 (get_local $p) (i32.const 0))))`);
+ ins.exports.test();
+},
+ WebAssembly.RuntimeError,
+ /dereferencing null pointer/);
diff --git a/js/src/jit-test/tests/wasm/gc/structs.js b/js/src/jit-test/tests/wasm/gc/structs.js
index 7636cb5d36c8..3da9499c35b8 100644
--- a/js/src/jit-test/tests/wasm/gc/structs.js
+++ b/js/src/jit-test/tests/wasm/gc/structs.js
@@ -1,6 +1,8 @@
if (!wasmGcEnabled())
quit();
+var conf = getBuildConfiguration();
+
var bin = wasmTextToBinary(
`(module
(gc_feature_opt_in 1)
@@ -56,6 +58,138 @@ var bin = wasmTextToBinary(
(func (export "x2") (param f64) (result f64)
(call $x2 (get_local 0)))
+
+ ;; Useful for testing to ensure that the type is not type #0 here.
+
+ (func (export "mk_point") (result anyref)
+ (struct.new $point (i32.const 37) (i32.const 42)))
+
+ (func (export "mk_int_node") (param i32) (param anyref) (result anyref)
+ (struct.new $int_node (get_local 0) (get_local 1)))
+
+ ;; Too big to fit in an InlineTypedObject.
+
+ (type $bigger (struct
+ (field $a i32)
+ (field $b i32)
+ (field $c i32)
+ (field $d i32)
+ (field $e i32)
+ (field $f i32)
+ (field $g i32)
+ (field $h i32)
+ (field $i i32)
+ (field $j i32)
+ (field $k i32)
+ (field $l i32)
+ (field $m i32)
+ (field $n i32)
+ (field $o i32)
+ (field $p i32)
+ (field $q i32)
+ (field $r i32)
+ (field $s i32)
+ (field $t i32)
+ (field $u i32)
+ (field $v i32)
+ (field $w i32)
+ (field $x i32)
+ (field $y i32)
+ (field $z i32)
+ (field $aa i32)
+ (field $ab i32)
+ (field $ac i32)
+ (field $ad i32)
+ (field $ae i32)
+ (field $af i32)
+ (field $ag i32)
+ (field $ah i32)
+ (field $ai i32)
+ (field $aj i32)
+ (field $ak i32)
+ (field $al i32)
+ (field $am i32)
+ (field $an i32)
+ (field $ao i32)
+ (field $ap i32)
+ (field $aq i32)
+ (field $ar i32)
+ (field $as i32)
+ (field $at i32)
+ (field $au i32)
+ (field $av i32)
+ (field $aw i32)
+ (field $ax i32)
+ (field $ay i32)
+ (field $az i32)))
+
+ (func (export "mk_bigger") (result anyref)
+ (struct.new $bigger
+ (i32.const 0)
+ (i32.const 1)
+ (i32.const 2)
+ (i32.const 3)
+ (i32.const 4)
+ (i32.const 5)
+ (i32.const 6)
+ (i32.const 7)
+ (i32.const 8)
+ (i32.const 9)
+ (i32.const 10)
+ (i32.const 11)
+ (i32.const 12)
+ (i32.const 13)
+ (i32.const 14)
+ (i32.const 15)
+ (i32.const 16)
+ (i32.const 17)
+ (i32.const 18)
+ (i32.const 19)
+ (i32.const 20)
+ (i32.const 21)
+ (i32.const 22)
+ (i32.const 23)
+ (i32.const 24)
+ (i32.const 25)
+ (i32.const 26)
+ (i32.const 27)
+ (i32.const 28)
+ (i32.const 29)
+ (i32.const 30)
+ (i32.const 31)
+ (i32.const 32)
+ (i32.const 33)
+ (i32.const 34)
+ (i32.const 35)
+ (i32.const 36)
+ (i32.const 37)
+ (i32.const 38)
+ (i32.const 39)
+ (i32.const 40)
+ (i32.const 41)
+ (i32.const 42)
+ (i32.const 43)
+ (i32.const 44)
+ (i32.const 45)
+ (i32.const 46)
+ (i32.const 47)
+ (i32.const 48)
+ (i32.const 49)
+ (i32.const 50)
+ (i32.const 51)))
+
+ (type $withfloats (struct
+ (field $f1 f32)
+ (field $f2 f64)
+ (field $f3 anyref)
+ (field $f4 f32)
+ (field $f5 i32)))
+
+ (func (export "mk_withfloats")
+ (param f32) (param f64) (param anyref) (param f32) (param i32)
+ (result anyref)
+ (struct.new $withfloats (get_local 0) (get_local 1) (get_local 2) (get_local 3) (get_local 4)))
+
)`)
var mod = new WebAssembly.Module(bin);
@@ -67,6 +201,310 @@ assertEq(ins.hello(4.0, 1), 16.0)
assertEq(ins.x1(12), 36)
assertEq(ins.x2(8), Math.PI)
+var point = ins.mk_point();
+assertEq("_0" in point, true);
+assertEq("_1" in point, true);
+assertEq("_2" in point, false);
+assertEq(point._0, 37);
+assertEq(point._1, 42);
+
+var int_node = ins.mk_int_node(78, point);
+assertEq(int_node._0, 78);
+assertEq(int_node._1, point);
+
+var bigger = ins.mk_bigger();
+for ( let i=0; i < 52; i++ )
+ assertEq(bigger["_" + i], i);
+
+var withfloats = ins.mk_withfloats(1/3, Math.PI, bigger, 5/6, 0x1337);
+assertEq(withfloats._0, Math.fround(1/3));
+assertEq(withfloats._1, Math.PI);
+assertEq(withfloats._2, bigger);
+assertEq(withfloats._3, Math.fround(5/6));
+assertEq(withfloats._4, 0x1337);
+
+// A simple stress test
+
+var stress = wasmTextToBinary(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $node (struct (field i32) (field (ref $node))))
+ (func (export "iota1") (param $n i32) (result anyref)
+ (local $list (ref $node))
+ (block $exit
+ (loop $loop
+ (br_if $exit (i32.eqz (get_local $n)))
+ (set_local $list (struct.new $node (get_local $n) (get_local $list)))
+ (set_local $n (i32.sub (get_local $n) (i32.const 1)))
+ (br $loop)))
+ (get_local $list)))`);
+var stressIns = new WebAssembly.Instance(new WebAssembly.Module(stress)).exports;
+var stressLevel = conf.x64 && !conf.tsan && !conf.asan && !conf.valgrind ? 100000 : 1000;
+var the_list = stressIns.iota1(stressLevel);
+for (let i=1; i <= stressLevel; i++) {
+ assertEq(the_list._0, i);
+ the_list = the_list._1;
+}
+assertEq(the_list, null);
+
+// Fields and their exposure in JS. We can't export types yet so hide them
+// inside the module with globals.
+
+// i64 fields.
+
+{
+ let txt =
+ `(module
+ (gc_feature_opt_in 1)
+
+ (type $big (struct
+ (field (mut i32))
+ (field (mut i64))
+ (field (mut i32))))
+
+ (func (export "set") (param anyref)
+ (local (ref $big))
+ (set_local 1 (struct.narrow anyref (ref $big) (get_local 0)))
+ (struct.set $big 1 (get_local 1) (i64.const 0x3333333376544567)))
+
+ (func (export "set2") (param $p anyref)
+ (struct.set $big 1
+ (struct.narrow anyref (ref $big) (get_local $p))
+ (i64.const 0x3141592653589793)))
+
+ (func (export "low") (param $p anyref) (result i32)
+ (i32.wrap/i64 (struct.get $big 1 (struct.narrow anyref (ref $big) (get_local $p)))))
+
+ (func (export "high") (param $p anyref) (result i32)
+ (i32.wrap/i64 (i64.shr_u
+ (struct.get $big 1 (struct.narrow anyref (ref $big) (get_local $p)))
+ (i64.const 32))))
+
+ (func (export "mk") (result anyref)
+ (struct.new $big (i32.const 0x7aaaaaaa) (i64.const 0x4201020337) (i32.const 0x6bbbbbbb)))
+
+ )`;
+
+ let ins = wasmEvalText(txt).exports;
+
+ let v = ins.mk();
+ assertEq(typeof v, "object");
+ assertEq(v._0, 0x7aaaaaaa);
+ assertEq(v._1_low, 0x01020337);
+ assertEq(v._1_high, 0x42);
+ assertEq(ins.low(v), 0x01020337);
+ assertEq(ins.high(v), 0x42);
+ assertEq(v._2, 0x6bbbbbbb);
+
+ v._0 = 0x5ccccccc;
+ v._2 = 0x4ddddddd;
+ assertEq(v._1_low, 0x01020337);
+
+ ins.set(v);
+ assertEq(v._0, 0x5ccccccc);
+ assertEq(v._1_low, 0x76544567);
+ assertEq(v._2, 0x4ddddddd);
+
+ ins.set2(v);
+ assertEq(v._1_low, 0x53589793);
+ assertEq(v._1_high, 0x31415926)
+ assertEq(ins.low(v), 0x53589793);
+ assertEq(ins.high(v), 0x31415926)
+}
+
+{
+ let txt =
+ `(module
+ (gc_feature_opt_in 1)
+
+ (type $big (struct
+ (field (mut i32))
+ (field (mut i64))
+ (field (mut i32))))
+
+ (global $g (mut (ref $big)) (ref.null (ref $big)))
+
+ (func (export "make") (result anyref)
+ (set_global $g
+ (struct.new $big (i32.const 0x7aaaaaaa) (i64.const 0x4201020337) (i32.const 0x6bbbbbbb)))
+ (get_global $g))
+
+ (func (export "update0") (param $x i32)
+ (struct.set $big 0 (get_global $g) (get_local $x)))
+
+ (func (export "get0") (result i32)
+ (struct.get $big 0 (get_global $g)))
+
+ (func (export "update1") (param $hi i32) (param $lo i32)
+ (struct.set $big 1 (get_global $g)
+ (i64.or
+ (i64.shl (i64.extend_u/i32 (get_local $hi)) (i64.const 32))
+ (i64.extend_u/i32 (get_local $lo)))))
+
+ (func (export "get1_low") (result i32)
+ (i32.wrap/i64 (struct.get $big 1 (get_global $g))))
+
+ (func (export "get1_high") (result i32)
+ (i32.wrap/i64
+ (i64.shr_u (struct.get $big 1 (get_global $g)) (i64.const 32))))
+
+ (func (export "update2") (param $x i32)
+ (struct.set $big 2 (get_global $g) (get_local $x)))
+
+ (func (export "get2") (result i32)
+ (struct.get $big 2 (get_global $g)))
+
+ )`;
+
+ let ins = wasmEvalText(txt).exports;
+
+ let v = ins.make();
+ assertEq(v._0, 0x7aaaaaaa);
+ assertEq(v._1_low, 0x01020337);
+ assertEq(v._1_high, 0x42);
+ assertEq(v._2, 0x6bbbbbbb);
+
+ ins.update0(0x45367101);
+ assertEq(v._0, 0x45367101);
+ assertEq(ins.get0(), 0x45367101);
+ assertEq(v._1_low, 0x01020337);
+ assertEq(v._1_high, 0x42);
+ assertEq(v._2, 0x6bbbbbbb);
+
+ ins.update2(0x62345123);
+ assertEq(v._0, 0x45367101);
+ assertEq(v._1_low, 0x01020337);
+ assertEq(v._1_high, 0x42);
+ assertEq(ins.get2(), 0x62345123);
+ assertEq(v._2, 0x62345123);
+
+ ins.update1(0x77777777, 0x22222222);
+ assertEq(v._0, 0x45367101);
+ assertEq(ins.get1_low(), 0x22222222);
+ assertEq(v._1_low, 0x22222222);
+ assertEq(ins.get1_high(), 0x77777777);
+ assertEq(v._1_high, 0x77777777);
+ assertEq(v._2, 0x62345123);
+}
+
+
+var bin = wasmTextToBinary(
+ `(module
+ (gc_feature_opt_in 1)
+
+ (type $cons (struct (field i32) (field (ref $cons))))
+
+ (global $g (mut (ref $cons)) (ref.null (ref $cons)))
+
+ (func (export "push") (param i32)
+ (set_global $g (struct.new $cons (get_local 0) (get_global $g))))
+
+ (func (export "top") (result i32)
+ (struct.get $cons 0 (get_global $g)))
+
+ (func (export "pop")
+ (set_global $g (struct.get $cons 1 (get_global $g))))
+
+ (func (export "is_empty") (result i32)
+ (ref.is_null (get_global $g)))
+
+ )`);
+
+var mod = new WebAssembly.Module(bin);
+var ins = new WebAssembly.Instance(mod).exports;
+ins.push(37);
+ins.push(42);
+ins.push(86);
+assertEq(ins.top(), 86);
+ins.pop();
+assertEq(ins.top(), 42);
+ins.pop();
+assertEq(ins.top(), 37);
+ins.pop();
+assertEq(ins.is_empty(), 1);
+assertErrorMessage(() => ins.pop(),
+ WebAssembly.RuntimeError,
+ /dereferencing null pointer/);
+
+// Check that a wrapped object cannot be unboxed from anyref even if the wrapper
+// points to the right type. This is a temporary restriction, until we're able
+// to avoid dealing with wrappers inside the engine.
+
+{
+ var ins = wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $Node (struct (field i32)))
+ (func (export "mk") (result anyref)
+ (struct.new $Node (i32.const 37)))
+ (func (export "f") (param $n anyref) (result anyref)
+ (struct.narrow anyref (ref $Node) (get_local $n))))`).exports;
+ var n = ins.mk();
+ assertEq(ins.f(n), n);
+ assertEq(ins.f(wrapWithProto(n, {})), null);
+}
+
+// negative tests
+
+// Wrong type passed as initializer
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (gc_feature_opt_in 1)
+ (type $r (struct (field i32)))
+ (func $f (param f64) (result anyref)
+ (struct.new $r (get_local 0)))
+)`)),
+WebAssembly.CompileError, /type mismatch/);
+
+// Too few values passed for initializer
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (gc_feature_opt_in 1)
+ (type $r (struct (field i32) (field i32)))
+ (func $f (result anyref)
+ (struct.new $r (i32.const 0)))
+)`)),
+WebAssembly.CompileError, /popping value from empty stack/);
+
+// Too many values passed for initializer, sort of
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (gc_feature_opt_in 1)
+ (type $r (struct (field i32) (field i32)))
+ (func $f (result anyref)
+ (i32.const 0)
+ (i32.const 1)
+ (i32.const 2)
+ struct.new $r)
+)`)),
+WebAssembly.CompileError, /unused values/);
+
+// Not referencing a structure type
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (gc_feature_opt_in 1)
+ (type (func (param i32) (result i32)))
+ (func $f (result anyref)
+ (struct.new 0))
+)`)),
+WebAssembly.CompileError, /not a struct type/);
+
+// Nominal type equivalence for structs, but the prefix rule allows this
+// conversion to succeed.
+
+wasmEvalText(`
+ (module
+ (gc_feature_opt_in 1)
+ (type $p (struct (field i32)))
+ (type $q (struct (field i32)))
+ (func $f (result (ref $p))
+ (struct.new $q (i32.const 0))))
+`);
+
// The field name is optional, so this should work.
wasmEvalText(`
@@ -141,6 +579,29 @@ assertErrorMessage(() => wasmEvalText(`
`),
WebAssembly.CompileError, /signature index references non-signature/);
+// Can't set immutable fields from JS
+
+{
+ let ins = wasmEvalText(
+ `(module
+ (gc_feature_opt_in 1)
+ (type $s (struct
+ (field i32)
+ (field (mut i64))))
+ (func (export "make") (result anyref)
+ (struct.new $s (i32.const 37) (i64.const 42))))`).exports;
+ let v = ins.make();
+ assertErrorMessage(() => v._0 = 12,
+ Error,
+ /setting immutable field/);
+ assertErrorMessage(() => v._1_low = 12,
+ Error,
+ /setting immutable field/);
+ assertErrorMessage(() => v._1_high = 12,
+ Error,
+ /setting immutable field/);
+}
+
// Function should not reference struct type: binary test
var bad = new Uint8Array([0x00, 0x61, 0x73, 0x6d,
diff --git a/js/src/js.msg b/js/src/js.msg
index 228dd2573c66..5ed5fed5dd6c 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -393,6 +393,7 @@ MSG_DEF(JSMSG_WASM_UNALIGNED_ACCESS, 0, JSEXN_WASMRUNTIMEERROR, "unaligned mem
MSG_DEF(JSMSG_WASM_WAKE_OVERFLOW, 0, JSEXN_WASMRUNTIMEERROR, "too many woken agents")
MSG_DEF(JSMSG_WASM_INVALID_PASSIVE_DATA_SEG, 0, JSEXN_WASMRUNTIMEERROR, "use of invalid passive data segment")
MSG_DEF(JSMSG_WASM_INVALID_PASSIVE_ELEM_SEG, 0, JSEXN_WASMRUNTIMEERROR, "use of invalid passive element segment")
+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_BAD_UINT32, 2, JSEXN_TYPEERR, "bad {0} {1}")
diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h
index 00861dfbffdb..ecef5cbc2178 100644
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -26,6 +26,8 @@ class TypedObjectModuleObject;
class LexicalEnvironmentObject;
class RegExpStatics;
+enum class ReferenceType;
+
/*
* Global object slots are reserved as follows:
*
@@ -472,6 +474,14 @@ class GlobalObject : public NativeObject
initTypedObjectModule);
}
+ static TypeDescr*
+ getOrCreateScalarTypeDescr(JSContext* cx, Handle global,
+ Scalar::Type scalarType);
+
+ static TypeDescr*
+ getOrCreateReferenceTypeDescr(JSContext* cx, Handle global,
+ ReferenceType type);
+
TypedObjectModuleObject& getTypedObjectModule() const;
static JSObject*
diff --git a/js/src/wasm/WasmAST.h b/js/src/wasm/WasmAST.h
index c72ec041fc20..b36a8fb69690 100644
--- a/js/src/wasm/WasmAST.h
+++ b/js/src/wasm/WasmAST.h
@@ -497,6 +497,12 @@ enum class AstExprKind
MemOrTableDrop,
MemFill,
MemOrTableInit,
+#endif
+#ifdef ENABLE_WASM_GC
+ StructNew,
+ StructGet,
+ StructSet,
+ StructNarrow,
#endif
Nop,
Pop,
@@ -1053,6 +1059,82 @@ class AstMemOrTableInit : public AstExpr
};
#endif
+#ifdef ENABLE_WASM_GC
+class AstStructNew : public AstExpr
+{
+ AstRef structType_;
+ AstExprVector fieldValues_;
+
+ public:
+ static const AstExprKind Kind = AstExprKind::StructNew;
+ AstStructNew(AstRef structType, AstExprType type, AstExprVector&& fieldVals)
+ : AstExpr(Kind, type), structType_(structType), fieldValues_(std::move(fieldVals))
+ {}
+ AstRef& structType() { return structType_; }
+ const AstExprVector& fieldValues() const { return fieldValues_; }
+};
+
+class AstStructGet : public AstExpr
+{
+ AstRef structType_;
+ uint32_t index_;
+ AstExpr* ptr_;
+
+ public:
+ static const AstExprKind Kind = AstExprKind::StructGet;
+ AstStructGet(AstRef structType, uint32_t index, AstExprType type, AstExpr* ptr)
+ : AstExpr(Kind, type),
+ structType_(structType),
+ index_(index),
+ ptr_(ptr)
+ {}
+ AstRef& structType() { return structType_; }
+ uint32_t index() { return index_; }
+ AstExpr& ptr() const { return *ptr_; }
+};
+
+class AstStructSet : public AstExpr
+{
+ AstRef structType_;
+ uint32_t index_;
+ AstExpr* ptr_;
+ AstExpr* value_;
+
+ public:
+ static const AstExprKind Kind = AstExprKind::StructSet;
+ AstStructSet(AstRef structType, uint32_t index, AstExpr* ptr, AstExpr* value)
+ : AstExpr(Kind, ExprType::Void),
+ structType_(structType),
+ index_(index),
+ ptr_(ptr),
+ value_(value)
+ {}
+ AstRef& structType() { return structType_; }
+ uint32_t index() { return index_; }
+ AstExpr& ptr() const { return *ptr_; }
+ AstExpr& value() const { return *value_; }
+};
+
+class AstStructNarrow : public AstExpr
+{
+ AstValType inputStruct_;
+ AstValType outputStruct_;
+ AstExpr* ptr_;
+
+ public:
+ static const AstExprKind Kind = AstExprKind::StructNarrow;
+ AstStructNarrow(AstValType inputStruct, AstValType outputStruct, AstExpr* ptr)
+ : AstExpr(Kind, AstExprType(outputStruct)),
+ inputStruct_(inputStruct),
+ outputStruct_(outputStruct),
+ ptr_(ptr)
+ {}
+ AstValType& inputStruct() { return inputStruct_; }
+ AstValType& outputStruct() { return outputStruct_; }
+ AstExpr& ptr() const { return *ptr_; }
+};
+#endif
+
class AstCurrentMemory final : public AstExpr
{
public:
diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp
index 5b04233f1884..a1ceb252b7e4 100644
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -1927,6 +1927,7 @@ class BaseCompiler final : public BaseCompilerInterface
MIRTypeVector SigPII_;
MIRTypeVector SigPIII_;
MIRTypeVector SigPIIL_;
+ MIRTypeVector SigPIIP_;
MIRTypeVector SigPILL_;
MIRTypeVector SigPIIII_;
NonAssertingLabel returnLabel_;
@@ -5782,32 +5783,44 @@ class BaseCompiler final : public BaseCompilerInterface
masm.bind(&skipBarrier);
}
- // This emits a GC post-write barrier. This is needed to ensure that the GC
- // is aware of slots of tenured things containing references to nursery
- // values. Pass None for object when the field's owner object is known to
- // be tenured or heap-allocated.
+ // Emit a GC post-write barrier. The barrier is needed to ensure that the
+ // GC is aware of slots of tenured things containing references to nursery
+ // values.
//
- // This frees the register `valueAddr`.
+ // The barrier has five easy steps:
+ //
+ // Label skipBarrier;
+ // sync();
+ // emitPostBarrierGuard(..., &skipBarrier);
+ // emitPostBarrier(...);
+ // bind(&skipBarrier);
+ //
+ // These are divided up to allow other actions to be placed between them,
+ // such as saving and restoring live registers. postBarrier() will make a
+ // call to C++ and will kill all live registers. The initial sync() is
+ // required to make sure all paths sync the same amount.
- void emitPostBarrier(const Maybe& object, RegPtr otherScratch, RegPtr valueAddr, RegPtr setValue) {
- Label skipBarrier;
-
- // One of the branches (in case we need the C++ call) will cause a sync,
- // so ensure the stack is sync'd before, so that the join is sync'd too.
- sync();
+ // Pass None for `object` when the field's owner object is known to be
+ // tenured or heap-allocated.
+ void emitPostBarrierGuard(const Maybe& object, RegPtr otherScratch, RegPtr setValue,
+ Label* skipBarrier)
+ {
// If the pointer being stored is null, no barrier.
- masm.branchTestPtr(Assembler::Zero, setValue, setValue, &skipBarrier);
+ masm.branchTestPtr(Assembler::Zero, setValue, setValue, skipBarrier);
// If there is a containing object and it is in the nursery, no barrier.
if (object) {
- masm.branchPtrInNurseryChunk(Assembler::Equal, *object, otherScratch, &skipBarrier);
+ masm.branchPtrInNurseryChunk(Assembler::Equal, *object, otherScratch, skipBarrier);
}
// If the pointer being stored is to a tenured object, no barrier.
- masm.branchPtrInNurseryChunk(Assembler::NotEqual, setValue, otherScratch, &skipBarrier);
+ masm.branchPtrInNurseryChunk(Assembler::NotEqual, setValue, otherScratch, skipBarrier);
+ }
- // Need a barrier.
+ // This frees the register `valueAddr`.
+
+ void emitPostBarrier(RegPtr valueAddr) {
uint32_t bytecodeOffset = iter_.lastOpcodeOffset();
// The `valueAddr` is a raw pointer to the cell within some GC object or
@@ -5820,16 +5833,21 @@ class BaseCompiler final : public BaseCompilerInterface
pushI32(RegI32(valueAddr));
emitInstanceCall(bytecodeOffset, SigPI_, ExprType::Void, SymbolicAddress::PostBarrier);
# endif
-
- masm.bind(&skipBarrier);
}
void emitBarrieredStore(const Maybe& object, RegPtr valueAddr, RegPtr value) {
emitPreBarrier(valueAddr); // Preserves valueAddr
masm.storePtr(value, Address(valueAddr, 0));
+
+ Label skipBarrier;
+ sync();
+
RegPtr otherScratch = needRef();
- emitPostBarrier(object, otherScratch, valueAddr, value); // Consumes valueAddr
+ emitPostBarrierGuard(object, otherScratch, value, &skipBarrier);
freeRef(otherScratch);
+
+ emitPostBarrier(valueAddr);
+ masm.bind(&skipBarrier);
}
#endif // ENABLE_WASM_GC
@@ -6146,6 +6164,12 @@ class BaseCompiler final : public BaseCompilerInterface
MOZ_MUST_USE bool emitMemFill();
MOZ_MUST_USE bool emitMemOrTableInit(bool isMem);
#endif
+#ifdef ENABLE_WASM_GC
+ MOZ_MUST_USE bool emitStructNew();
+ MOZ_MUST_USE bool emitStructGet();
+ MOZ_MUST_USE bool emitStructSet();
+ MOZ_MUST_USE bool emitStructNarrow();
+#endif
};
void
@@ -9209,8 +9233,9 @@ BaseCompiler::emitInstanceCall(uint32_t lineOrBytecode, const MIRTypeVector& sig
for (uint32_t i = 1; i < sig.length(); i++) {
ValType t;
switch (sig[i]) {
- case MIRType::Int32: t = ValType::I32; break;
- case MIRType::Int64: t = ValType::I64; break;
+ case MIRType::Int32: t = ValType::I32; break;
+ case MIRType::Int64: t = ValType::I64; break;
+ case MIRType::Pointer: t = ValType::AnyRef; break;
default: MOZ_CRASH("Unexpected type");
}
passArg(t, peek(numArgs - i), &baselineCall);
@@ -9734,6 +9759,368 @@ BaseCompiler::emitMemOrTableInit(bool isMem)
}
#endif
+#ifdef ENABLE_WASM_GC
+bool
+BaseCompiler::emitStructNew()
+{
+ uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
+
+ uint32_t typeIndex;
+ BaseOpIter::ValueVector args;
+ if (!iter_.readStructNew(&typeIndex, &args)) {
+ return false;
+ }
+
+ if (deadCode_) {
+ return true;
+ }
+
+ // Allocate zeroed storage. The parameter to StructNew is an index into a
+ // descriptor table that the instance has.
+
+ const StructType& structType = env_.types[typeIndex].structType();
+
+ pushI32(structType.moduleIndex_);
+ emitInstanceCall(lineOrBytecode, SigPI_, ExprType::AnyRef, SymbolicAddress::StructNew);
+
+ // Null pointer check.
+
+ Label ok;
+ masm.branchTestPtr(Assembler::NotEqual, ReturnReg, ReturnReg, &ok);
+ trap(Trap::ThrowReported);
+ masm.bind(&ok);
+
+ // As many arguments as there are fields.
+
+ MOZ_ASSERT(args.length() == structType.fields_.length());
+
+ // Optimization opportunity: Iterate backward to pop arguments off the
+ // stack. This will generate more instructions than we want, since we
+ // really only need to pop the stack once at the end, not for every element,
+ // but to do better we need a bit more machinery to load elements off the
+ // stack into registers.
+
+ RegPtr rp = popRef();
+ RegPtr rdata = rp;
+
+ if (!structType.isInline_) {
+ rdata = needRef();
+ masm.loadPtr(Address(rp, OutlineTypedObject::offsetOfData()), rdata);
+ }
+
+ // Optimization opportunity: when the value being stored is a known
+ // zero/null we need store nothing. This case may be somewhat common
+ // because struct.new forces a value to be specified for every field.
+
+ uint32_t fieldNo = structType.fields_.length();
+ while (fieldNo-- > 0) {
+ uint32_t offs = structType.fields_[fieldNo].offset;
+ switch (structType.fields_[fieldNo].type.code()) {
+ case ValType::I32: {
+ RegI32 r = popI32();
+ masm.store32(r, Address(rdata, offs));
+ freeI32(r);
+ break;
+ }
+ case ValType::I64: {
+ RegI64 r = popI64();
+ masm.store64(r, Address(rdata, offs));
+ freeI64(r);
+ break;
+ }
+ case ValType::F32: {
+ RegF32 r = popF32();
+ masm.storeFloat32(r, Address(rdata, offs));
+ freeF32(r);
+ break;
+ }
+ case ValType::F64: {
+ RegF64 r = popF64();
+ masm.storeDouble(r, Address(rdata, offs));
+ freeF64(r);
+ break;
+ }
+ case ValType::Ref:
+ case ValType::AnyRef: {
+ RegPtr value = popRef();
+ masm.storePtr(value, Address(rdata, offs));
+
+ // A write barrier is needed here for the extremely unlikely case
+ // that the object is allocated in the tenured area - a result of
+ // a GC artifact.
+
+ Label skipBarrier;
+
+ sync();
+
+ RegPtr rowner = rp;
+ if (!structType.isInline_) {
+ rowner = needRef();
+ masm.loadPtr(Address(rp, OutlineTypedObject::offsetOfOwner()), rowner);
+ }
+
+ RegPtr otherScratch = needRef();
+ emitPostBarrierGuard(Some(rowner), otherScratch, value, &skipBarrier);
+ freeRef(otherScratch);
+
+ if (!structType.isInline_) {
+ freeRef(rowner);
+ }
+
+ freeRef(value);
+
+ pushRef(rp); // Save rp across the call
+ RegPtr valueAddr = needRef();
+ masm.computeEffectiveAddress(Address(rdata, offs), valueAddr);
+ emitPostBarrier(valueAddr); // Consumes valueAddr
+ popRef(rp); // Restore rp
+ if (!structType.isInline_) {
+ masm.loadPtr(Address(rp, OutlineTypedObject::offsetOfData()), rdata);
+ }
+
+ masm.bind(&skipBarrier);
+ break;
+ }
+ default: {
+ MOZ_CRASH("Unexpected field type");
+ }
+ }
+ }
+
+ if (!structType.isInline_) {
+ freeRef(rdata);
+ }
+
+ pushRef(rp);
+
+ return true;
+}
+
+bool
+BaseCompiler::emitStructGet()
+{
+ uint32_t typeIndex;
+ uint32_t fieldIndex;
+ Nothing nothing;
+ if (!iter_.readStructGet(&typeIndex, &fieldIndex, ¬hing)) {
+ return false;
+ }
+
+ if (deadCode_) {
+ return true;
+ }
+
+ const StructType& structType = env_.types[typeIndex].structType();
+
+ RegPtr rp = popRef();
+
+ Label ok;
+ masm.branchTestPtr(Assembler::NotEqual, rp, rp, &ok);
+ trap(Trap::NullPointerDereference);
+ masm.bind(&ok);
+
+ if (!structType.isInline_) {
+ masm.loadPtr(Address(rp, OutlineTypedObject::offsetOfData()), rp);
+ }
+
+ uint32_t offs = structType.fields_[fieldIndex].offset;
+ switch (structType.fields_[fieldIndex].type.code()) {
+ case ValType::I32: {
+ RegI32 r = needI32();
+ masm.load32(Address(rp, offs), r);
+ pushI32(r);
+ break;
+ }
+ case ValType::I64: {
+ RegI64 r = needI64();
+ masm.load64(Address(rp, offs), r);
+ pushI64(r);
+ break;
+ }
+ case ValType::F32: {
+ RegF32 r = needF32();
+ masm.loadFloat32(Address(rp, offs), r);
+ pushF32(r);
+ break;
+ }
+ case ValType::F64: {
+ RegF64 r = needF64();
+ masm.loadDouble(Address(rp, offs), r);
+ pushF64(r);
+ break;
+ }
+ case ValType::Ref:
+ case ValType::AnyRef: {
+ RegPtr r = needRef();
+ masm.loadPtr(Address(rp, offs), r);
+ pushRef(r);
+ break;
+ }
+ default: {
+ MOZ_CRASH("Unexpected field type");
+ }
+ }
+
+ freeRef(rp);
+
+ return true;
+}
+
+bool
+BaseCompiler::emitStructSet()
+{
+ uint32_t typeIndex;
+ uint32_t fieldIndex;
+ Nothing nothing;
+ if (!iter_.readStructSet(&typeIndex, &fieldIndex, ¬hing, ¬hing)) {
+ return false;
+ }
+
+ if (deadCode_) {
+ return true;
+ }
+
+ const StructType& structType = env_.types[typeIndex].structType();
+
+ RegI32 ri;
+ RegI64 rl;
+ RegF32 rf;
+ RegF64 rd;
+ RegPtr rr;
+
+#ifdef ENABLE_WASM_GC
+ // Reserve this register early if we will need it so that it is not taken by
+ // rr or rp.
+ RegPtr valueAddr;
+ if (structType.fields_[fieldIndex].type.isRefOrAnyRef()) {
+ valueAddr = RegPtr(PreBarrierReg);
+ needRef(valueAddr);
+ }
+#endif
+
+ switch (structType.fields_[fieldIndex].type.code()) {
+ case ValType::I32:
+ ri = popI32();
+ break;
+ case ValType::I64:
+ rl = popI64();
+ break;
+ case ValType::F32:
+ rf = popF32();
+ break;
+ case ValType::F64:
+ rd = popF64();
+ break;
+ case ValType::Ref:
+ case ValType::AnyRef:
+ rr = popRef();
+ break;
+ default:
+ MOZ_CRASH("Unexpected field type");
+ }
+
+ RegPtr rp = popRef();
+
+ Label ok;
+ masm.branchTestPtr(Assembler::NotEqual, rp, rp, &ok);
+ trap(Trap::NullPointerDereference);
+ masm.bind(&ok);
+
+ if (!structType.isInline_) {
+ masm.loadPtr(Address(rp, OutlineTypedObject::offsetOfData()), rp);
+ }
+
+ uint32_t offs = structType.fields_[fieldIndex].offset;
+ switch (structType.fields_[fieldIndex].type.code()) {
+ case ValType::I32: {
+ masm.store32(ri, Address(rp, offs));
+ freeI32(ri);
+ break;
+ }
+ case ValType::I64: {
+ masm.store64(rl, Address(rp, offs));
+ freeI64(rl);
+ break;
+ }
+ case ValType::F32: {
+ masm.storeFloat32(rf, Address(rp, offs));
+ freeF32(rf);
+ break;
+ }
+ case ValType::F64: {
+ masm.storeDouble(rd, Address(rp, offs));
+ freeF64(rd);
+ break;
+ }
+#ifdef ENABLE_WASM_GC
+ case ValType::Ref:
+ case ValType::AnyRef:
+ masm.computeEffectiveAddress(Address(rp, offs), valueAddr);
+ emitBarrieredStore(Some(rp), valueAddr, rr);// Consumes valueAddr
+ freeRef(rr);
+ break;
+#endif
+ default: {
+ MOZ_CRASH("Unexpected field type");
+ }
+ }
+
+ freeRef(rp);
+
+ return true;
+}
+
+bool
+BaseCompiler::emitStructNarrow()
+{
+ uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
+
+ ValType inputType, outputType;
+ Nothing nothing;
+ if (!iter_.readStructNarrow(&inputType, &outputType, ¬hing)) {
+ return false;
+ }
+
+ if (deadCode_) {
+ return true;
+ }
+
+ // AnyRef -> AnyRef is a no-op, just leave the value on the stack.
+
+ if (inputType == ValType::AnyRef && outputType == ValType::AnyRef) {
+ return true;
+ }
+
+ // Null pointers are just passed through.
+
+ Label done;
+ Label doTest;
+ RegPtr rp = popRef();
+ masm.branchTestPtr(Assembler::NotEqual, rp, rp, &doTest);
+ pushRef(NULLREF_VALUE);
+ masm.jump(&done);
+
+ // AnyRef -> (ref T) must first unbox; leaves rp or null
+
+ bool mustUnboxAnyref = inputType == ValType::AnyRef;
+
+ // Dynamic downcast (ref T) -> (ref U), leaves rp or null
+
+ const StructType& outputStruct = env_.types[outputType.refTypeIndex()].structType();
+
+ masm.bind(&doTest);
+
+ pushI32(mustUnboxAnyref);
+ pushI32(outputStruct.moduleIndex_);
+ pushRef(rp);
+ emitInstanceCall(lineOrBytecode, SigPIIP_, ExprType::AnyRef, SymbolicAddress::StructNarrow);
+
+ masm.bind(&done);
+
+ return true;
+}
+#endif
+
bool
BaseCompiler::emitBody()
{
@@ -10387,6 +10774,28 @@ BaseCompiler::emitBody()
case uint16_t(MiscOp::TableInit):
CHECK_NEXT(emitMemOrTableInit(/*isMem=*/false));
#endif // ENABLE_WASM_BULKMEM_OPS
+#ifdef ENABLE_WASM_GC
+ case uint16_t(MiscOp::StructNew):
+ if (env_.gcTypesEnabled() == HasGcTypes::False) {
+ return iter_.unrecognizedOpcode(&op);
+ }
+ CHECK_NEXT(emitStructNew());
+ case uint16_t(MiscOp::StructGet):
+ if (env_.gcTypesEnabled() == HasGcTypes::False) {
+ return iter_.unrecognizedOpcode(&op);
+ }
+ CHECK_NEXT(emitStructGet());
+ case uint16_t(MiscOp::StructSet):
+ if (env_.gcTypesEnabled() == HasGcTypes::False) {
+ return iter_.unrecognizedOpcode(&op);
+ }
+ CHECK_NEXT(emitStructSet());
+ case uint16_t(MiscOp::StructNarrow):
+ if (env_.gcTypesEnabled() == HasGcTypes::False) {
+ return iter_.unrecognizedOpcode(&op);
+ }
+ CHECK_NEXT(emitStructNarrow());
+#endif
default:
break;
} // switch (op.b1)
@@ -10549,7 +10958,7 @@ BaseCompiler::emitBody()
break;
}
- // asm.js operations
+ // asm.js and other private operations
case uint16_t(Op::MozPrefix):
return iter_.unrecognizedOpcode(&op);
@@ -10652,6 +11061,11 @@ BaseCompiler::init()
{
return false;
}
+ if (!SigPIIP_.append(MIRType::Pointer) || !SigPIIP_.append(MIRType::Int32) ||
+ !SigPIIP_.append(MIRType::Int32) || !SigPIIP_.append(MIRType::Pointer))
+ {
+ return false;
+ }
if (!SigPILL_.append(MIRType::Pointer) || !SigPILL_.append(MIRType::Int32) ||
!SigPILL_.append(MIRType::Int64) || !SigPILL_.append(MIRType::Int64))
{
diff --git a/js/src/wasm/WasmBuiltins.cpp b/js/src/wasm/WasmBuiltins.cpp
index d8ee4b20de1d..c221f3f7fd42 100644
--- a/js/src/wasm/WasmBuiltins.cpp
+++ b/js/src/wasm/WasmBuiltins.cpp
@@ -266,6 +266,8 @@ WasmHandleTrap()
return ReportError(cx, JSMSG_WASM_IND_CALL_TO_NULL);
case Trap::IndirectCallBadSig:
return ReportError(cx, JSMSG_WASM_IND_CALL_BAD_SIG);
+ case Trap::NullPointerDereference:
+ return ReportError(cx, JSMSG_WASM_DEREF_NULL);
case Trap::OutOfBounds:
return ReportError(cx, JSMSG_WASM_OUT_OF_BOUNDS);
case Trap::UnalignedAccess:
@@ -691,6 +693,12 @@ AddressOf(SymbolicAddress imm, ABIFunctionType* abiType)
*abiType = Args_General2;
return FuncCast(Instance::postBarrier, *abiType);
#endif
+ case SymbolicAddress::StructNew:
+ *abiType = Args_General2;
+ return FuncCast(Instance::structNew, *abiType);
+ case SymbolicAddress::StructNarrow:
+ *abiType = Args_General4;
+ return FuncCast(Instance::structNarrow, *abiType);
#if defined(JS_CODEGEN_MIPS32)
case SymbolicAddress::js_jit_gAtomic64Lock:
return &js::jit::gAtomic64Lock;
@@ -775,6 +783,8 @@ wasm::NeedsBuiltinThunk(SymbolicAddress sym)
#ifdef ENABLE_WASM_GC
case SymbolicAddress::PostBarrier:
#endif
+ case SymbolicAddress::StructNew:
+ case SymbolicAddress::StructNarrow:
return true;
case SymbolicAddress::Limit:
break;
diff --git a/js/src/wasm/WasmCode.cpp b/js/src/wasm/WasmCode.cpp
index 430e980251f2..8425488379e4 100644
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -1230,11 +1230,13 @@ JumpTables::init(CompileMode mode, const ModuleSegment& ms, const CodeRangeVecto
return true;
}
-Code::Code(UniqueCodeTier tier1, const Metadata& metadata, JumpTables&& maybeJumpTables)
+Code::Code(UniqueCodeTier tier1, const Metadata& metadata, JumpTables&& maybeJumpTables,
+ StructTypeVector&& structTypes)
: tier1_(std::move(tier1)),
metadata_(&metadata),
profilingLabels_(mutexid::WasmCodeProfilingLabels, CacheableCharsVector()),
- jumpTables_(std::move(maybeJumpTables))
+ jumpTables_(std::move(maybeJumpTables)),
+ structTypes_(std::move(structTypes))
{}
bool
@@ -1528,13 +1530,15 @@ Code::addSizeOfMiscIfNotSeen(MallocSizeOf mallocSizeOf,
for (auto t : tiers()) {
codeTier(t).addSizeOfMisc(mallocSizeOf, code, data);
}
+ *data += SizeOfVectorExcludingThis(structTypes_, mallocSizeOf);
}
size_t
Code::serializedSize() const
{
return metadata().serializedSize() +
- codeTier(Tier::Serialized).serializedSize();
+ codeTier(Tier::Serialized).serializedSize() +
+ SerializedVectorSize(structTypes_);
}
uint8_t*
@@ -1544,6 +1548,7 @@ Code::serialize(uint8_t* cursor, const LinkData& linkData) const
cursor = metadata().serialize(cursor);
cursor = codeTier(Tier::Serialized).serialize(cursor, linkData);
+ cursor = SerializeVector(cursor, structTypes_);
return cursor;
}
@@ -1569,7 +1574,14 @@ Code::deserialize(const uint8_t* cursor,
return nullptr;
}
- MutableCode code = js_new(std::move(codeTier), metadata, std::move(jumpTables));
+ StructTypeVector structTypes;
+ cursor = DeserializeVector(cursor, &structTypes);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ MutableCode code = js_new(std::move(codeTier), metadata, std::move(jumpTables),
+ std::move(structTypes));
if (!code || !code->initialize(linkData)) {
return nullptr;
}
diff --git a/js/src/wasm/WasmCode.h b/js/src/wasm/WasmCode.h
index ef98687656fc..96693d860298 100644
--- a/js/src/wasm/WasmCode.h
+++ b/js/src/wasm/WasmCode.h
@@ -686,11 +686,13 @@ class Code : public ShareableBase
SharedMetadata metadata_;
ExclusiveData profilingLabels_;
JumpTables jumpTables_;
+ StructTypeVector structTypes_;
public:
Code(UniqueCodeTier tier1,
const Metadata& metadata,
- JumpTables&& maybeJumpTables);
+ JumpTables&& maybeJumpTables,
+ StructTypeVector&& structTypes);
bool initialized() const { return tier1_->initialized(); }
bool initialize(const LinkData& linkData);
@@ -714,6 +716,7 @@ class Code : public ShareableBase
const CodeTier& codeTier(Tier tier) const;
const Metadata& metadata() const { return *metadata_; }
+ const StructTypeVector& structTypes() const { return structTypes_; }
const ModuleSegment& segment(Tier iter) const {
return codeTier(iter).segment();
diff --git a/js/src/wasm/WasmConstants.h b/js/src/wasm/WasmConstants.h
index a2fcf4a2dc9d..00252d12dc22 100644
--- a/js/src/wasm/WasmConstants.h
+++ b/js/src/wasm/WasmConstants.h
@@ -104,6 +104,8 @@ enum class Trap
IndirectCallToNull,
// call_indirect signature mismatch.
IndirectCallBadSig,
+ // Dereference null pointer in operation on (Ref T)
+ NullPointerDereference,
// The internal stack space was exhausted. For compatibility, this throws
// the same over-recursed error as JS.
@@ -399,6 +401,12 @@ enum class MiscOp
TableDrop = 0x0d,
TableCopy = 0x0e,
+ // Structure operations. Note, these are unofficial.
+ StructNew = 0x50,
+ StructGet = 0x51,
+ StructSet = 0x52,
+ StructNarrow = 0x53,
+
Limit
};
diff --git a/js/src/wasm/WasmFrameIter.cpp b/js/src/wasm/WasmFrameIter.cpp
index e8c58f2226ab..68b2556cb671 100644
--- a/js/src/wasm/WasmFrameIter.cpp
+++ b/js/src/wasm/WasmFrameIter.cpp
@@ -1405,6 +1405,10 @@ ThunkedNativeToDescription(SymbolicAddress func)
case SymbolicAddress::PostBarrier:
return "call to native GC postbarrier (in wasm)";
#endif
+ case SymbolicAddress::StructNew:
+ return "call to native struct.new (in wasm)";
+ case SymbolicAddress::StructNarrow:
+ return "call to native struct.narrow (in wasm)";
#if defined(JS_CODEGEN_MIPS32)
case SymbolicAddress::js_jit_gAtomic64Lock:
MOZ_CRASH();
diff --git a/js/src/wasm/WasmGenerator.cpp b/js/src/wasm/WasmGenerator.cpp
index 032ab638f38f..c2f48cef5d41 100644
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -1037,11 +1037,6 @@ ModuleGenerator::finishModule(const ShareableBytes& bytecode,
return nullptr;
}
- MutableCode code = js_new(std::move(codeTier), *metadata_, std::move(jumpTables));
- if (!code || !code->initialize(*linkData_)) {
- return nullptr;
- }
-
StructTypeVector structTypes;
for (TypeDef& td : env_->types) {
if (td.isStructType() && !structTypes.append(std::move(td.structType()))) {
@@ -1049,6 +1044,12 @@ ModuleGenerator::finishModule(const ShareableBytes& bytecode,
}
}
+ MutableCode code = js_new(std::move(codeTier), *metadata_, std::move(jumpTables),
+ std::move(structTypes));
+ if (!code || !code->initialize(*linkData_)) {
+ return nullptr;
+ }
+
// Copy over data from the Bytecode, which is going away at the end of
// compilation.
@@ -1118,7 +1119,6 @@ ModuleGenerator::finishModule(const ShareableBytes& bytecode,
MutableModule module = js_new(*code,
std::move(env_->imports),
std::move(env_->exports),
- std::move(structTypes),
std::move(dataSegments),
std::move(env_->elemSegments),
std::move(customSections),
diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp
index a6d5547063de..ec95f82f7791 100644
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -696,12 +696,85 @@ Instance::postBarrier(Instance* instance, gc::Cell** location)
}
#endif // ENABLE_WASM_GC
+// The typeIndex is an index into the structTypeDescrs_ table in the instance.
+// That table holds TypeDescr objects.
+//
+// When we fail to allocate we return a nullptr; the wasm side must check this
+// and propagate it as an error.
+
+/* static */ void*
+Instance::structNew(Instance* instance, uint32_t typeIndex)
+{
+ JSContext* cx = TlsContext.get();
+ Rooted typeDescr(cx, instance->structTypeDescrs_[typeIndex]);
+ return TypedObject::createZeroed(cx, typeDescr);
+}
+
+/* static */ void*
+Instance::structNarrow(Instance* instance, uint32_t mustUnboxAnyref, uint32_t outputTypeIndex,
+ void* nonnullPtr)
+{
+ JSContext* cx = TlsContext.get();
+
+ Rooted obj(cx);
+ Rooted typeDescr(cx);
+
+ if (mustUnboxAnyref) {
+ Rooted no(cx, static_cast(nonnullPtr));
+ if (!no->is()) {
+ return nullptr;
+ }
+ obj = &no->as();
+ Rooted td(cx, &obj->typeDescr());
+ if (td->kind() != type::Struct) {
+ return nullptr;
+ }
+ typeDescr = &td->as();
+ } else {
+ obj = static_cast(nonnullPtr);
+ typeDescr = &obj->typeDescr().as();
+ }
+
+ // Optimization opportunity: instead of this loop we could perhaps load an
+ // index from `typeDescr` and use that to index into the structTypes table
+ // of the instance. If the index is in bounds and the desc at that index is
+ // the desc we have then we know the index is good, and we can use that for
+ // the prefix check.
+
+ uint32_t found = UINT32_MAX;
+ for (uint32_t i = 0; i < instance->structTypeDescrs_.length(); i++) {
+ if (instance->structTypeDescrs_[i] == typeDescr) {
+ found = i;
+ break;
+ }
+ }
+
+ if (found == UINT32_MAX) {
+ return nullptr;
+ }
+
+ // Also asserted in constructor; let's just be double sure.
+
+ MOZ_ASSERT(instance->structTypeDescrs_.length() == instance->structTypes().length());
+
+ // Now we know that the object was created by the instance, and we know its
+ // concrete type. We need to check that its type is an extension of the
+ // type of outputTypeIndex.
+
+ if (!instance->structTypes()[found].hasPrefix(instance->structTypes()[outputTypeIndex])) {
+ return nullptr;
+ }
+
+ return nonnullPtr;
+}
+
Instance::Instance(JSContext* cx,
Handle object,
SharedCode code,
UniqueTlsData tlsDataIn,
HandleWasmMemoryObject memory,
SharedTableVector&& tables,
+ StructTypeDescrVector&& structTypeDescrs,
Handle funcImports,
HandleValVector globalImportValues,
const WasmGlobalObjectVector& globalObjs,
@@ -712,9 +785,11 @@ Instance::Instance(JSContext* cx,
tlsData_(std::move(tlsDataIn)),
memory_(memory),
tables_(std::move(tables)),
- maybeDebug_(std::move(maybeDebug))
+ maybeDebug_(std::move(maybeDebug)),
+ structTypeDescrs_(std::move(structTypeDescrs))
{
MOZ_ASSERT(!!maybeDebug_ == metadata().debugEnabled);
+ MOZ_ASSERT(structTypeDescrs_.length() == structTypes().length());
#ifdef DEBUG
for (auto t : code_->tiers()) {
@@ -970,6 +1045,7 @@ Instance::tracePrivate(JSTracer* trc)
#endif
TraceNullableEdge(trc, &memory_, "wasm buffer");
+ structTypeDescrs_.trace(trc);
}
void
diff --git a/js/src/wasm/WasmInstance.h b/js/src/wasm/WasmInstance.h
index 8e4e2f6eae3c..c38299216f8c 100644
--- a/js/src/wasm/WasmInstance.h
+++ b/js/src/wasm/WasmInstance.h
@@ -19,6 +19,7 @@
#ifndef wasm_instance_h
#define wasm_instance_h
+#include "builtin/TypedObject.h"
#include "gc/Barrier.h"
#include "jit/shared/Assembler-shared.h"
#include "vm/SharedMem.h"
@@ -57,6 +58,7 @@ class Instance
DataSegmentVector passiveDataSegments_;
ElemSegmentVector passiveElemSegments_;
const UniqueDebugState maybeDebug_;
+ StructTypeDescrVector structTypeDescrs_;
// Internal helpers:
const void** addressOfFuncTypeId(const FuncTypeIdDesc& funcTypeId) const;
@@ -77,6 +79,7 @@ class Instance
UniqueTlsData tlsData,
HandleWasmMemoryObject memory,
SharedTableVector&& tables,
+ StructTypeDescrVector&& structTypeDescrs,
Handle funcImports,
HandleValVector globalImportValues,
const WasmGlobalObjectVector& globalObjs,
@@ -107,6 +110,7 @@ class Instance
#ifdef JS_SIMULATOR
bool memoryAccessInGuardRegion(uint8_t* addr, unsigned numBytes) const;
#endif
+ const StructTypeVector& structTypes() const { return code_->structTypes(); }
static constexpr size_t offsetOfJSJitArgsRectifier() {
return offsetof(Instance, jsJitArgsRectifier_);
@@ -195,6 +199,8 @@ class Instance
#ifdef ENABLE_WASM_GC
static void postBarrier(Instance* instance, gc::Cell** location);
#endif
+ static void* structNew(Instance* instance, uint32_t typeIndex);
+ static void* structNarrow(Instance* instance, uint32_t mustUnboxAnyref, uint32_t outputTypeIndex, void* ptr);
};
typedef UniquePtr UniqueInstance;
diff --git a/js/src/wasm/WasmIonCompile.cpp b/js/src/wasm/WasmIonCompile.cpp
index cdf6bbb043e6..d109a20eeea5 100644
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -3612,6 +3612,14 @@ EmitBodyExprs(FunctionCompiler& f)
CHECK(EmitMemOrTableDrop(f, /*isMem=*/false));
case uint16_t(MiscOp::TableInit):
CHECK(EmitMemOrTableInit(f, /*isMem=*/false));
+#endif
+#ifdef ENABLE_WASM_GC
+ case uint16_t(MiscOp::StructNew):
+ case uint16_t(MiscOp::StructGet):
+ case uint16_t(MiscOp::StructSet):
+ case uint16_t(MiscOp::StructNarrow):
+ // Not yet supported
+ return f.iter().unrecognizedOpcode(&op);
#endif
default:
return f.iter().unrecognizedOpcode(&op);
diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp
index 36ac03babf1e..6874cef21b5c 100644
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -24,6 +24,7 @@
#include "mozilla/RangedPtr.h"
#include "builtin/Promise.h"
+#include "builtin/TypedObject.h"
#include "gc/FreeOp.h"
#include "jit/AtomicOperations.h"
#include "jit/JitOptions.h"
@@ -1176,6 +1177,7 @@ WasmInstanceObject::create(JSContext* cx,
UniqueTlsData tlsData,
HandleWasmMemoryObject memory,
SharedTableVector&& tables,
+ StructTypeDescrVector&& structTypeDescrs,
Handle funcImports,
const GlobalDescVector& globals,
HandleValVector globalImportValues,
@@ -1244,6 +1246,7 @@ WasmInstanceObject::create(JSContext* cx,
std::move(tlsData),
memory,
std::move(tables),
+ std::move(structTypeDescrs),
funcImports,
globalImportValues,
globalObjs,
diff --git a/js/src/wasm/WasmJS.h b/js/src/wasm/WasmJS.h
index be18f10844c9..29489404f9ff 100644
--- a/js/src/wasm/WasmJS.h
+++ b/js/src/wasm/WasmJS.h
@@ -26,6 +26,7 @@
namespace js {
class GlobalObject;
+class StructTypeDescr;
class TypedArrayObject;
class WasmFunctionScope;
class WasmInstanceScope;
@@ -230,6 +231,7 @@ class WasmInstanceObject : public NativeObject
wasm::UniqueTlsData tlsData,
HandleWasmMemoryObject memory,
Vector, 0, SystemAllocPolicy>&& tables,
+ GCVector, 0, SystemAllocPolicy>&& structTypeDescrs,
Handle funcImports,
const wasm::GlobalDescVector& globals,
wasm::HandleValVector globalImportValues,
diff --git a/js/src/wasm/WasmModule.cpp b/js/src/wasm/WasmModule.cpp
index 61477a0fbacf..6b2080b417a3 100644
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -21,6 +21,7 @@
#include
#include
+#include "builtin/TypedObject.h"
#include "jit/JitOptions.h"
#include "threading/LockGuard.h"
#include "util/NSPR.h"
@@ -196,7 +197,6 @@ Module::serializedSize(const LinkData& linkData) const
linkData.serializedSize() +
SerializedVectorSize(imports_) +
SerializedVectorSize(exports_) +
- SerializedVectorSize(structTypes_) +
SerializedVectorSize(dataSegments_) +
SerializedVectorSize(elemSegments_) +
SerializedVectorSize(customSections_) +
@@ -218,7 +218,6 @@ Module::serialize(const LinkData& linkData, uint8_t* begin, size_t size) const
cursor = linkData.serialize(cursor);
cursor = SerializeVector(cursor, imports_);
cursor = SerializeVector(cursor, exports_);
- cursor = SerializeVector(cursor, structTypes_);
cursor = SerializeVector(cursor, dataSegments_);
cursor = SerializeVector(cursor, elemSegments_);
cursor = SerializeVector(cursor, customSections_);
@@ -268,12 +267,6 @@ Module::deserialize(const uint8_t* begin, size_t size, Metadata* maybeMetadata)
return nullptr;
}
- StructTypeVector structTypes;
- cursor = DeserializeVector(cursor, &structTypes);
- if (!cursor) {
- return nullptr;
- }
-
DataSegmentVector dataSegments;
cursor = DeserializeVector(cursor, &dataSegments);
if (!cursor) {
@@ -311,7 +304,6 @@ Module::deserialize(const uint8_t* begin, size_t size, Metadata* maybeMetadata)
return js_new(*code,
std::move(imports),
std::move(exports),
- std::move(structTypes),
std::move(dataSegments),
std::move(elemSegments),
std::move(customSections));
@@ -459,7 +451,6 @@ Module::addSizeOfMisc(MallocSizeOf mallocSizeOf,
*data += mallocSizeOf(this) +
SizeOfVectorExcludingThis(imports_, mallocSizeOf) +
SizeOfVectorExcludingThis(exports_, mallocSizeOf) +
- SizeOfVectorExcludingThis(structTypes_, mallocSizeOf) +
SizeOfVectorExcludingThis(dataSegments_, mallocSizeOf) +
SizeOfVectorExcludingThis(elemSegments_, mallocSizeOf) +
SizeOfVectorExcludingThis(customSections_, mallocSizeOf);
@@ -979,7 +970,17 @@ Module::getDebugEnabledCode() const
return nullptr;
}
- MutableCode debugCode = js_new(std::move(codeTier), metadata(), std::move(jumpTables));
+ StructTypeVector structTypes;
+ if (!structTypes.resize(code_->structTypes().length())) {
+ return nullptr;
+ }
+ for (uint32_t i = 0; i < code_->structTypes().length(); i++) {
+ if (!structTypes[i].copyFrom(code_->structTypes()[i])) {
+ return nullptr;
+ }
+ }
+ MutableCode debugCode = js_new(std::move(codeTier), metadata(), std::move(jumpTables),
+ std::move(structTypes));
if (!debugCode || !debugCode->initialize(*debugLinkData_)) {
return nullptr;
}
@@ -1081,6 +1082,150 @@ CreateExportObject(JSContext* cx,
return true;
}
+static bool
+MakeStructField(JSContext* cx, const ValType& v, bool isMutable, const char* format,
+ uint32_t fieldNo, AutoIdVector* ids, AutoValueVector* fieldTypeObjs,
+ Vector* fieldProps)
+{
+ char buf[20];
+ sprintf(buf, format, fieldNo);
+ RootedString str(cx, JS_AtomizeAndPinString(cx, buf));
+ if (!str) {
+ return false;
+ }
+
+ StructFieldProps props;
+ props.isMutable = isMutable;
+
+ Rooted t(cx);
+ switch (v.code()) {
+ case ValType::I32:
+ t = GlobalObject::getOrCreateScalarTypeDescr(cx, cx->global(), Scalar::Int32);
+ break;
+ case ValType::I64:
+ // Align for int64 but allocate only an int32, another int32 allocation
+ // will follow immediately. JS will see two immutable int32 values but
+ // wasm knows it's a single int64. See makeStructTypeDescrs(), below.
+ props.alignAsInt64 = true;
+ t = GlobalObject::getOrCreateScalarTypeDescr(cx, cx->global(), Scalar::Int32);
+ break;
+ case ValType::F32:
+ t = GlobalObject::getOrCreateScalarTypeDescr(cx, cx->global(), Scalar::Float32);
+ break;
+ case ValType::F64:
+ t = GlobalObject::getOrCreateScalarTypeDescr(cx, cx->global(), Scalar::Float64);
+ break;
+ case ValType::Ref:
+ case ValType::AnyRef:
+ t = GlobalObject::getOrCreateReferenceTypeDescr(cx, cx->global(),
+ ReferenceType::TYPE_OBJECT);
+ break;
+ default:
+ MOZ_CRASH("Bad field type");
+ }
+ MOZ_ASSERT(t != nullptr);
+
+ if (!ids->append(INTERNED_STRING_TO_JSID(cx, str))) {
+ return false;
+ }
+
+ if (!fieldTypeObjs->append(ObjectValue(*t))) {
+ return false;
+ }
+
+ if (!fieldProps->append(props)) {
+ return false;
+ }
+
+ return true;
+}
+
+
+bool
+Module::makeStructTypeDescrs(JSContext* cx,
+ MutableHandle structTypeDescrs) const
+{
+ // Not just any prototype object will do, we must have the actual StructTypePrototype.
+ RootedObject typedObjectModule(cx, GlobalObject::getOrCreateTypedObjectModule(cx,
+ cx->global()));
+ if (!typedObjectModule) {
+ return false;
+ }
+
+ RootedNativeObject toModule(cx, &typedObjectModule->as());
+ RootedObject prototype(cx, &toModule->getReservedSlot(
+ TypedObjectModuleObject::StructTypePrototype).toObject());
+
+ for (const StructType& structType : structTypes()) {
+ AutoIdVector ids(cx);
+ AutoValueVector fieldTypeObjs(cx);
+ Vector fieldProps(cx);
+ bool allowConstruct = true;
+
+ uint32_t k = 0;
+ for (StructField sf : structType.fields_) {
+ const ValType& v = sf.type;
+ if (v.code() == ValType::I64) {
+ // TypedObjects don't yet have a notion of int64 fields. Thus
+ // we handle int64 by allocating two adjacent int32 fields, the
+ // first of them aligned as for int64. We mark these fields as
+ // immutable for JS and render the object non-constructible
+ // from JS. Wasm however sees one i64 field with appropriate
+ // mutability.
+ sf.isMutable = false;
+ allowConstruct = false;
+
+ if (!MakeStructField(cx, ValType::I64, sf.isMutable, "_%d_low", k, &ids,
+ &fieldTypeObjs, &fieldProps))
+ {
+ return false;
+ }
+ if (!MakeStructField(cx, ValType::I32, sf.isMutable, "_%d_high", k++, &ids,
+ &fieldTypeObjs, &fieldProps))
+ {
+ return false;
+ }
+ } else {
+ // TypedObjects don't yet have a sufficient notion of type
+ // constraints on TypedObject properties. Thus we handle fields
+ // of type (ref T) by marking them as immutable for JS and by
+ // rendering the objects non-constructible from JS. Wasm
+ // however sees properly-typed (ref T) fields with appropriate
+ // mutability.
+ if (v.isRef()) {
+ sf.isMutable = false;
+ allowConstruct = false;
+ }
+
+ if (!MakeStructField(cx, v, sf.isMutable, "_%d", k++, &ids, &fieldTypeObjs,
+ &fieldProps))
+ {
+ return false;
+ }
+ }
+ }
+
+ // Types must be opaque, which we ensure here, and sealed, which is true
+ // for every TypedObject. If they contain fields of type Ref T then we
+ // prevent JS from constructing instances of them.
+
+ Rooted
+ structTypeDescr(cx, StructMetaTypeDescr::createFromArrays(cx,
+ prototype,
+ /* opaque= */ true,
+ allowConstruct,
+ ids,
+ fieldTypeObjs,
+ fieldProps));
+
+ if (!structTypeDescr || !structTypeDescrs.append(structTypeDescr)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
bool
Module::instantiate(JSContext* cx,
Handle funcImports,
@@ -1134,6 +1279,13 @@ Module::instantiate(JSContext* cx,
code = code_;
}
+ // Create type descriptors for any struct types that the module has.
+
+ Rooted structTypeDescrs(cx);
+ if (!makeStructTypeDescrs(cx, &structTypeDescrs)) {
+ return false;
+ }
+
instance.set(WasmInstanceObject::create(cx,
code,
dataSegments_,
@@ -1141,6 +1293,7 @@ Module::instantiate(JSContext* cx,
std::move(tlsData),
memory,
std::move(tables),
+ std::move(structTypeDescrs.get()),
funcImports,
metadata().globals,
globalImportValues,
diff --git a/js/src/wasm/WasmModule.h b/js/src/wasm/WasmModule.h
index 8fc3f2e565be..412947df15f8 100644
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -58,7 +58,6 @@ class Module : public JS::WasmModule
const SharedCode code_;
const ImportVector imports_;
const ExportVector exports_;
- const StructTypeVector structTypes_;
const DataSegmentVector dataSegments_;
const ElemSegmentVector elemSegments_;
const CustomSectionVector customSections_;
@@ -100,6 +99,8 @@ class Module : public JS::WasmModule
HandleWasmMemoryObject memory,
HandleValVector globalImportValues) const;
SharedCode getDebugEnabledCode() const;
+ bool makeStructTypeDescrs(JSContext* cx,
+ MutableHandle structTypeDescrs) const;
class Tier2GeneratorTaskImpl;
@@ -107,7 +108,6 @@ class Module : public JS::WasmModule
Module(const Code& code,
ImportVector&& imports,
ExportVector&& exports,
- StructTypeVector&& structTypes,
DataSegmentVector&& dataSegments,
ElemSegmentVector&& elemSegments,
CustomSectionVector&& customSections,
@@ -117,7 +117,6 @@ class Module : public JS::WasmModule
: code_(&code),
imports_(std::move(imports)),
exports_(std::move(exports)),
- structTypes_(std::move(structTypes)),
dataSegments_(std::move(dataSegments)),
elemSegments_(std::move(elemSegments)),
customSections_(std::move(customSections)),
@@ -140,6 +139,7 @@ class Module : public JS::WasmModule
const CustomSectionVector& customSections() const { return customSections_; }
const Bytes& debugBytecode() const { return debugBytecode_->bytes; }
uint32_t codeLength(Tier t) const { return code_->segment(t).length(); }
+ const StructTypeVector& structTypes() const { return code_->structTypes(); }
// Instantiate this module with the given imports:
diff --git a/js/src/wasm/WasmOpIter.cpp b/js/src/wasm/WasmOpIter.cpp
index 080a187c8855..06b7c35a657f 100644
--- a/js/src/wasm/WasmOpIter.cpp
+++ b/js/src/wasm/WasmOpIter.cpp
@@ -267,6 +267,16 @@ wasm::Classify(OpBytes op)
case MiscOp::MemInit:
case MiscOp::TableInit:
return OpKind::MemOrTableInit;
+#endif
+#ifdef ENABLE_WASM_GC
+ case MiscOp::StructNew:
+ return OpKind::StructNew;
+ case MiscOp::StructGet:
+ return OpKind::StructGet;
+ case MiscOp::StructSet:
+ return OpKind::StructSet;
+ case MiscOp::StructNarrow:
+ return OpKind::StructNarrow;
#endif
default:
break;
diff --git a/js/src/wasm/WasmOpIter.h b/js/src/wasm/WasmOpIter.h
index a4da8d05aa47..d2cbe7c8effb 100644
--- a/js/src/wasm/WasmOpIter.h
+++ b/js/src/wasm/WasmOpIter.h
@@ -192,6 +192,10 @@ enum class OpKind {
MemFill,
MemOrTableInit,
RefNull,
+ StructNew,
+ StructGet,
+ StructSet,
+ StructNarrow,
};
// Return the OpKind for a given Op. This is used for sanity-checking that
@@ -385,6 +389,9 @@ class MOZ_STACK_CLASS OpIter : private Policy
MOZ_MUST_USE bool readLinearMemoryAddress(uint32_t byteSize, LinearMemoryAddress* addr);
MOZ_MUST_USE bool readLinearMemoryAddressAligned(uint32_t byteSize, LinearMemoryAddress* addr);
MOZ_MUST_USE bool readBlockType(ExprType* expr);
+ MOZ_MUST_USE bool readStructTypeIndex(uint32_t* typeIndex);
+ MOZ_MUST_USE bool readFieldIndex(uint32_t* fieldIndex, const StructType& structType);
+
MOZ_MUST_USE bool popCallArgs(const ValTypeVector& expectedTypes, Vector* values);
MOZ_MUST_USE bool popAnyType(StackType* type, Value* value);
@@ -476,6 +483,9 @@ class MOZ_STACK_CLASS OpIter : private Policy
// Report a general failure.
MOZ_MUST_USE bool fail(const char* msg) MOZ_COLD;
+ // Report a general failure with a context
+ MOZ_MUST_USE bool fail_ctx(const char* fmt, const char* context) MOZ_COLD;
+
// Report an unrecognized opcode.
MOZ_MUST_USE bool unrecognizedOpcode(const OpBytes* expr) MOZ_COLD;
@@ -564,6 +574,11 @@ class MOZ_STACK_CLASS OpIter : private Policy
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);
+ 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);
+ MOZ_MUST_USE bool readStructNarrow(ValType* inputType, ValType* outputType, Value* ptr);
+ MOZ_MUST_USE bool readReferenceType(ValType* type, const char* const context);
// At a location where readOp is allowed, peek at the next opcode
// without consuming it or updating any internal state.
@@ -709,6 +724,17 @@ OpIter::fail(const char* msg)
return d_.fail(lastOpcodeOffset(), msg);
}
+template
+inline bool
+OpIter::fail_ctx(const char* fmt, const char* context)
+{
+ UniqueChars error(JS_smprintf(fmt, context));
+ if (!error) {
+ return false;
+ }
+ return fail(error.get());
+}
+
// This function pops exactly one value from the stack, yielding Any types in
// various cases and therefore making it the caller's responsibility to do the
// right thing for StackType::Any. Prefer (pop|top)WithType.
@@ -1675,20 +1701,38 @@ inline bool
OpIter::readRefNull(ValType* type)
{
MOZ_ASSERT(Classify(op_) == OpKind::RefNull);
+ if (!readReferenceType(type, "ref.null")) {
+ return false;
+ }
+
+ return push(StackType(*type));
+}
+
+template
+inline bool
+OpIter::readReferenceType(ValType* type, const char* context)
+{
uint8_t code;
uint32_t refTypeIndex;
+
if (!d_.readValType(&code, &refTypeIndex)) {
- return fail("unknown nullref type");
+ return fail_ctx("invalid reference type for %s", context);
}
+
if (code == uint8_t(TypeCode::Ref)) {
- if (refTypeIndex >= MaxTypes || refTypeIndex >= env_.types.length()) {
- return fail("invalid nullref type");
+ if (refTypeIndex >= env_.types.length()) {
+ return fail_ctx("invalid reference type for %s", context);
+ }
+ if (!env_.types[refTypeIndex].isStructType()) {
+ return fail_ctx("reference to struct required for %s", context);
}
} else if (code != uint8_t(TypeCode::AnyRef)) {
- return fail("unknown nullref type");
+ return fail_ctx("invalid reference type for %s", context);
}
+
*type = ValType(ValType::Code(code), refTypeIndex);
- return push(StackType(*type));
+
+ return true;
}
template
@@ -2119,6 +2163,165 @@ OpIter::readMemOrTableInit(bool isMem, uint32_t* segIndex,
return true;
}
+template
+inline bool
+OpIter::readStructTypeIndex(uint32_t* typeIndex)
+{
+ if (!readVarU32(typeIndex)) {
+ return fail("unable to read type index");
+ }
+
+ if (*typeIndex >= env_.types.length()) {
+ return fail("type index out of range");
+ }
+
+ if (!env_.types[*typeIndex].isStructType()) {
+ return fail("not a struct type");
+ }
+
+ return true;
+}
+
+template
+inline bool
+OpIter::readFieldIndex(uint32_t* fieldIndex, const StructType& structType)
+{
+ if (!readVarU32(fieldIndex)) {
+ return fail("unable to read field index");
+ }
+
+ if (structType.fields_.length() <= *fieldIndex) {
+ return fail("field index out of range");
+ }
+
+ return true;
+}
+
+// Semantics of struct.new, struct.get, struct.set, and struct.narrow documented
+// (for now) on https://github.com/lars-t-hansen/moz-gc-experiments.
+
+template
+inline bool
+OpIter::readStructNew(uint32_t* typeIndex, ValueVector* argValues)
+{
+ MOZ_ASSERT(Classify(op_) == OpKind::StructNew);
+
+ if (!readStructTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ const StructType& str = env_.types[*typeIndex].structType();
+
+ if (!argValues->resize(str.fields_.length())) {
+ return false;
+ }
+
+ static_assert(MaxStructFields <= INT32_MAX, "Or we iloop below");
+
+ for (int32_t i = str.fields_.length() - 1; i >= 0; i--) {
+ if (!popWithType(str.fields_[i].type, &(*argValues)[i])) {
+ return false;
+ }
+ }
+
+ return push(ValType(ValType::Ref, *typeIndex));
+}
+
+template
+inline bool
+OpIter::readStructGet(uint32_t* typeIndex, uint32_t* fieldIndex, Value* ptr)
+{
+ MOZ_ASSERT(typeIndex != fieldIndex);
+ MOZ_ASSERT(Classify(op_) == OpKind::StructGet);
+
+ if (!readStructTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ const StructType& structType = env_.types[*typeIndex].structType();
+
+ if (!readFieldIndex(fieldIndex, structType)) {
+ return false;
+ }
+
+ if (!popWithType(ValType(ValType::Ref, *typeIndex), ptr)) {
+ return false;
+ }
+
+ return push(structType.fields_[*fieldIndex].type);
+}
+
+template
+inline bool
+OpIter::readStructSet(uint32_t* typeIndex, uint32_t* fieldIndex, Value* ptr, Value* val)
+{
+ MOZ_ASSERT(typeIndex != fieldIndex);
+ MOZ_ASSERT(Classify(op_) == OpKind::StructSet);
+
+ if (!readStructTypeIndex(typeIndex)) {
+ return false;
+ }
+
+ const StructType& structType = env_.types[*typeIndex].structType();
+
+ if (!readFieldIndex(fieldIndex, structType)) {
+ return false;
+ }
+
+ if (!popWithType(structType.fields_[*fieldIndex].type, val)) {
+ return false;
+ }
+
+ if (!structType.fields_[*fieldIndex].isMutable) {
+ return fail("field is not mutable");
+ }
+
+ if (!popWithType(ValType(ValType::Ref, *typeIndex), ptr)) {
+ return false;
+ }
+
+ return true;
+}
+
+template
+inline bool
+OpIter::readStructNarrow(ValType* inputType, ValType* outputType, Value* ptr)
+{
+ MOZ_ASSERT(inputType != outputType);
+ MOZ_ASSERT(Classify(op_) == OpKind::StructNarrow);
+
+ if (!readReferenceType(inputType, "struct.narrow")) {
+ return false;
+ }
+
+ if (!readReferenceType(outputType, "struct.narrow")) {
+ return false;
+ }
+
+ if (inputType->isRef()) {
+ if (!outputType->isRef()) {
+ return fail("invalid type combination in struct.narrow");
+ }
+
+ const StructType& inputStruct = env_.types[inputType->refTypeIndex()].structType();
+ const StructType& outputStruct = env_.types[outputType->refTypeIndex()].structType();
+
+ if (!outputStruct.hasPrefix(inputStruct)) {
+ return fail("invalid narrowing operation");
+ }
+ } else if (*outputType == ValType::AnyRef) {
+ if (*inputType != ValType::AnyRef) {
+ return fail("invalid type combination in struct.narrow");
+ }
+ }
+
+ if (!popWithType(*inputType, ptr)) {
+ return false;
+ }
+
+ return push(*outputType);
+}
+
} // namespace wasm
} // namespace js
diff --git a/js/src/wasm/WasmTextToBinary.cpp b/js/src/wasm/WasmTextToBinary.cpp
index d8f0385cacc7..0752b2b1578d 100644
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -121,6 +121,12 @@ class WasmToken
Module,
Mutable,
Name,
+#ifdef ENABLE_WASM_GC
+ StructNew,
+ StructGet,
+ StructSet,
+ StructNarrow,
+#endif
Nop,
Offset,
OpenParen,
@@ -359,6 +365,12 @@ class WasmToken
case MemDrop:
case MemFill:
case MemInit:
+#endif
+#ifdef ENABLE_WASM_GC
+ case StructNew:
+ case StructGet:
+ case StructSet:
+ case StructNarrow:
#endif
case Nop:
case RefNull:
@@ -2086,6 +2098,20 @@ WasmTokenStream::next()
return WasmToken(WasmToken::Start, begin, cur_);
}
if (consume(u"struct")) {
+#ifdef ENABLE_WASM_GC
+ if (consume(u".new")) {
+ return WasmToken(WasmToken::StructNew, begin, cur_);
+ }
+ if (consume(u".get")) {
+ return WasmToken(WasmToken::StructGet, begin, cur_);
+ }
+ if (consume(u".set")) {
+ return WasmToken(WasmToken::StructSet, begin, cur_);
+ }
+ if (consume(u".narrow")) {
+ return WasmToken(WasmToken::StructNarrow, begin, cur_);
+ }
+#endif
return WasmToken(WasmToken::Struct, begin, cur_);
}
break;
@@ -3586,6 +3612,122 @@ ParseMemOrTableInit(WasmParseContext& c, bool inParens, bool isMem)
}
#endif
+#ifdef ENABLE_WASM_GC
+static AstExpr*
+ParseStructNew(WasmParseContext& c, bool inParens)
+{
+ AstRef typeDef;
+ if (!c.ts.matchRef(&typeDef, c.error)) {
+ return nullptr;
+ }
+
+ AstExprVector args(c.lifo);
+ if (inParens) {
+ if (!ParseArgs(c, &args)) {
+ return nullptr;
+ }
+ }
+
+ // An AstRef cast to AstValType turns into a Ref type, which is exactly what
+ // we need here.
+
+ return new(c.lifo) AstStructNew(typeDef,
+ AstExprType(AstValType(typeDef)),
+ std::move(args));
+}
+
+static AstExpr*
+ParseStructGet(WasmParseContext& c, bool inParens)
+{
+ AstRef typeDef;
+ if (!c.ts.matchRef(&typeDef, c.error)) {
+ return nullptr;
+ }
+
+ AstRef fieldDef;
+ if (!c.ts.matchRef(&fieldDef, c.error)) {
+ return nullptr;
+ }
+
+ if (!fieldDef.name().empty()) {
+ c.ts.generateError(c.ts.peek(), "constant field index required at this time", c.error);
+ return nullptr;
+ }
+
+ AstExpr* ptr = ParseExpr(c, inParens);
+ if (!ptr) {
+ return nullptr;
+ }
+
+ // The field type is not available here, we must first resolve the type.
+ // Fortunately, we don't need to inspect the result type of this operation.
+
+ return new(c.lifo) AstStructGet(typeDef, fieldDef.index(), ExprType(), ptr);
+}
+
+static AstExpr*
+ParseStructSet(WasmParseContext& c, bool inParens)
+{
+ AstRef typeDef;
+ if (!c.ts.matchRef(&typeDef, c.error)) {
+ return nullptr;
+ }
+
+ AstRef fieldDef;
+ if (!c.ts.matchRef(&fieldDef, c.error)) {
+ return nullptr;
+ }
+
+ if (!fieldDef.name().empty()) {
+ c.ts.generateError(c.ts.peek(), "constant field index required at this time", c.error);
+ return nullptr;
+ }
+
+ AstExpr* ptr = ParseExpr(c, inParens);
+ if (!ptr) {
+ return nullptr;
+ }
+
+ AstExpr* value = ParseExpr(c, inParens);
+ if (!value) {
+ return nullptr;
+ }
+
+ return new(c.lifo) AstStructSet(typeDef, fieldDef.index(), ptr, value);
+}
+
+static AstExpr*
+ParseStructNarrow(WasmParseContext& c, bool inParens)
+{
+ AstValType inputType;
+ if (!ParseValType(c, &inputType)) {
+ return nullptr;
+ }
+
+ if (!inputType.isRefType()) {
+ c.ts.generateError(c.ts.peek(), "struct.narrow requires ref type", c.error);
+ return nullptr;
+ }
+
+ AstValType outputType;
+ if (!ParseValType(c, &outputType)) {
+ return nullptr;
+ }
+
+ if (!outputType.isRefType()) {
+ c.ts.generateError(c.ts.peek(), "struct.narrow requires ref type", c.error);
+ return nullptr;
+ }
+
+ AstExpr* ptr = ParseExpr(c, inParens);
+ if (!ptr) {
+ return nullptr;
+ }
+
+ return new(c.lifo) AstStructNarrow(inputType, outputType, ptr);
+}
+#endif
+
static AstExpr*
ParseRefNull(WasmParseContext& c)
{
@@ -3696,6 +3838,16 @@ ParseExprBody(WasmParseContext& c, WasmToken token, bool inParens)
return ParseMemOrTableDrop(c, /*isMem=*/false);
case WasmToken::TableInit:
return ParseMemOrTableInit(c, inParens, /*isMem=*/false);
+#endif
+#ifdef ENABLE_WASM_GC
+ case WasmToken::StructNew:
+ return ParseStructNew(c, inParens);
+ case WasmToken::StructGet:
+ return ParseStructGet(c, inParens);
+ case WasmToken::StructSet:
+ return ParseStructSet(c, inParens);
+ case WasmToken::StructNarrow:
+ return ParseStructNarrow(c, inParens);
#endif
case WasmToken::RefNull:
return ParseRefNull(c);
@@ -5334,6 +5486,56 @@ ResolveMemOrTableInit(Resolver& r, AstMemOrTableInit& s)
}
#endif
+#ifdef ENABLE_WASM_GC
+static bool
+ResolveStructNew(Resolver& r, AstStructNew& s)
+{
+ if (!ResolveArgs(r, s.fieldValues())) {
+ return false;
+ }
+
+ if (!r.resolveType(s.structType())) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+ResolveStructGet(Resolver& r, AstStructGet& s)
+{
+ if (!r.resolveType(s.structType())) {
+ return false;
+ }
+
+ return ResolveExpr(r, s.ptr());
+}
+
+static bool
+ResolveStructSet(Resolver& r, AstStructSet& s)
+{
+ if (!r.resolveType(s.structType())) {
+ return false;
+ }
+
+ return ResolveExpr(r, s.ptr()) && ResolveExpr(r, s.value());
+}
+
+static bool
+ResolveStructNarrow(Resolver& r, AstStructNarrow& s)
+{
+ if (!ResolveType(r, s.inputStruct())) {
+ return false;
+ }
+
+ if (!ResolveType(r, s.outputStruct())) {
+ return false;
+ }
+
+ return ResolveExpr(r, s.ptr());
+}
+#endif
+
static bool
ResolveRefNull(Resolver& r, AstRefNull& s)
{
@@ -5422,6 +5624,16 @@ ResolveExpr(Resolver& r, AstExpr& expr)
return ResolveMemFill(r, expr.as());
case AstExprKind::MemOrTableInit:
return ResolveMemOrTableInit(r, expr.as());
+#endif
+#ifdef ENABLE_WASM_GC
+ case AstExprKind::StructNew:
+ return ResolveStructNew(r, expr.as());
+ case AstExprKind::StructGet:
+ return ResolveStructGet(r, expr.as());
+ case AstExprKind::StructSet:
+ return ResolveStructSet(r, expr.as());
+ case AstExprKind::StructNarrow:
+ return ResolveStructNarrow(r, expr.as());
#endif
}
MOZ_CRASH("Bad expr kind");
@@ -6120,6 +6332,83 @@ EncodeMemOrTableInit(Encoder& e, AstMemOrTableInit& s)
}
#endif
+#ifdef ENABLE_WASM_GC
+static bool
+EncodeStructNew(Encoder& e, AstStructNew& s)
+{
+ if (!EncodeArgs(e, s.fieldValues())) {
+ return false;
+ }
+
+ if (!e.writeOp(MiscOp::StructNew)) {
+ return false;
+ }
+
+ if (!e.writeVarU32(s.structType().index())) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+EncodeStructGet(Encoder& e, AstStructGet& s)
+{
+ if (!EncodeExpr(e, s.ptr())) {
+ return false;
+ }
+ if (!e.writeOp(MiscOp::StructGet)) {
+ return false;
+ }
+ if (!e.writeVarU32(s.structType().index())) {
+ return false;
+ }
+ if (!e.writeVarU32(s.index())) {
+ return false;
+ }
+ return true;
+}
+
+static bool
+EncodeStructSet(Encoder& e, AstStructSet& s)
+{
+ if (!EncodeExpr(e, s.ptr())) {
+ return false;
+ }
+ if (!EncodeExpr(e, s.value())) {
+ return false;
+ }
+ if (!e.writeOp(MiscOp::StructSet)) {
+ return false;
+ }
+ if (!e.writeVarU32(s.structType().index())) {
+ return false;
+ }
+ if (!e.writeVarU32(s.index())) {
+ return false;
+ }
+ return true;
+}
+
+static bool
+EncodeStructNarrow(Encoder& e, AstStructNarrow& s)
+{
+ if (!EncodeExpr(e, s.ptr())) {
+ return false;
+ }
+ if (!e.writeOp(MiscOp::StructNarrow)) {
+ return false;
+ }
+ if (!e.writeValType(s.inputStruct().type())) {
+ return false;
+ }
+ if (!e.writeValType(s.outputStruct().type())) {
+ return false;
+ }
+ return true;
+}
+#endif
+
static bool
EncodeRefNull(Encoder& e, AstRefNull& s)
{
@@ -6212,6 +6501,16 @@ EncodeExpr(Encoder& e, AstExpr& expr)
return EncodeMemFill(e, expr.as());
case AstExprKind::MemOrTableInit:
return EncodeMemOrTableInit(e, expr.as());
+#endif
+#ifdef ENABLE_WASM_GC
+ case AstExprKind::StructNew:
+ return EncodeStructNew(e, expr.as());
+ case AstExprKind::StructGet:
+ return EncodeStructGet(e, expr.as());
+ case AstExprKind::StructSet:
+ return EncodeStructSet(e, expr.as());
+ case AstExprKind::StructNarrow:
+ return EncodeStructNarrow(e, expr.as());
#endif
}
MOZ_CRASH("Bad expr kind");
diff --git a/js/src/wasm/WasmTypes.h b/js/src/wasm/WasmTypes.h
index b38d91e81aeb..09e8a72d1425 100644
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -74,6 +74,9 @@ class WasmGlobalObject;
typedef GCVector WasmGlobalObjectVector;
typedef Rooted RootedWasmGlobalObject;
+class StructTypeDescr;
+typedef GCVector, 0, SystemAllocPolicy> StructTypeDescrVector;
+
namespace wasm {
using mozilla::ArrayEqual;
@@ -830,9 +833,9 @@ struct FuncTypeHashPolicy
// Structure type.
//
-// The Module owns a dense array of Struct values that represent the structure
-// types that the module knows about. It is created from the sparse array of
-// types in the ModuleEnvironment when the Module is created.
+// The Module owns a dense array of StructType values that represent the
+// structure types that the module knows about. It is created from the sparse
+// array of types in the ModuleEnvironment when the Module is created.
struct StructField
{
@@ -846,15 +849,31 @@ typedef Vector StructFieldVector;
class StructType
{
public:
- StructFieldVector fields_;
-
+ StructFieldVector fields_; // Field type, offset, and mutability
+ uint32_t moduleIndex_; // Index in a dense array of structs in the module
+ bool isInline_; // True if this is an InlineTypedObject and we
+ // interpret the offsets from the object pointer;
+ // if false this is an OutlineTypedObject and we
+ // interpret everything relative to the pointer to
+ // the attached storage.
public:
- StructType() : fields_() {}
+ StructType() : fields_(), moduleIndex_(0), isInline_(true) {}
- explicit StructType(StructFieldVector&& fields)
- : fields_(std::move(fields))
+ StructType(StructFieldVector&& fields, uint32_t index, bool isInline)
+ : fields_(std::move(fields)),
+ moduleIndex_(index),
+ isInline_(isInline)
{}
+ bool copyFrom(const StructType& src) {
+ if (!fields_.appendAll(src.fields_)) {
+ return false;
+ }
+ moduleIndex_ = src.moduleIndex_;
+ isInline_ = src.isInline_;
+ return true;
+ }
+
bool hasPrefix(const StructType& other) const;
WASM_DECLARE_SERIALIZABLE(StructType)
@@ -1876,6 +1895,8 @@ enum class SymbolicAddress
#ifdef ENABLE_WASM_GC
PostBarrier,
#endif
+ StructNew,
+ StructNarrow,
#if defined(JS_CODEGEN_MIPS32)
js_jit_gAtomic64Lock,
#endif
diff --git a/js/src/wasm/WasmValidate.cpp b/js/src/wasm/WasmValidate.cpp
index 618ce7f72740..93a5d23ae44d 100644
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -22,6 +22,7 @@
#include "mozilla/Unused.h"
#include "mozilla/Utf8.h"
+#include "builtin/TypedObject.h"
#include "jit/JitOptions.h"
#include "js/Printf.h"
#include "vm/JSContext.h"
@@ -34,6 +35,7 @@ using namespace js::wasm;
using mozilla::CheckedInt;
using mozilla::IsValidUtf8;
+using mozilla::CheckedInt32;
using mozilla::Unused;
// Decoder implementation.
@@ -930,6 +932,37 @@ DecodeFunctionBodyExprs(const ModuleEnvironment& env, const FuncType& funcType,
CHECK(iter.readMemOrTableInit(/*isMem=*/false,
&unusedSegIndex, ¬hing, ¬hing, ¬hing));
}
+#endif
+#ifdef ENABLE_WASM_GC
+ case uint16_t(MiscOp::StructNew): {
+ if (env.gcTypesEnabled() == HasGcTypes::False) {
+ return iter.unrecognizedOpcode(&op);
+ }
+ uint32_t unusedUint;
+ ValidatingOpIter::ValueVector unusedArgs;
+ CHECK(iter.readStructNew(&unusedUint, &unusedArgs));
+ }
+ case uint16_t(MiscOp::StructGet): {
+ if (env.gcTypesEnabled() == HasGcTypes::False) {
+ return iter.unrecognizedOpcode(&op);
+ }
+ uint32_t unusedUint1, unusedUint2;
+ CHECK(iter.readStructGet(&unusedUint1, &unusedUint2, ¬hing));
+ }
+ case uint16_t(MiscOp::StructSet): {
+ if (env.gcTypesEnabled() == HasGcTypes::False) {
+ return iter.unrecognizedOpcode(&op);
+ }
+ uint32_t unusedUint1, unusedUint2;
+ CHECK(iter.readStructSet(&unusedUint1, &unusedUint2, ¬hing, ¬hing));
+ }
+ case uint16_t(MiscOp::StructNarrow): {
+ if (env.gcTypesEnabled() == HasGcTypes::False) {
+ return iter.unrecognizedOpcode(&op);
+ }
+ ValType unusedTy, unusedTy2;
+ CHECK(iter.readStructNarrow(&unusedTy, &unusedTy2, ¬hing));
+ }
#endif
default:
return iter.unrecognizedOpcode(&op);
@@ -1310,8 +1343,7 @@ DecodeStructType(Decoder& d, ModuleEnvironment* env, TypeStateVector* typeState,
return false;
}
- // TODO (subsequent patch): lay out the fields.
-
+ StructMetaTypeDescr::Layout layout;
for (uint32_t i = 0; i < numFields; i++) {
uint8_t flags;
if (!d.readFixedU8(&flags)) {
@@ -1327,14 +1359,54 @@ DecodeStructType(Decoder& d, ModuleEnvironment* env, TypeStateVector* typeState,
if (!ValidateRefType(d, typeState, fields[i].type)) {
return false;
}
+
+ CheckedInt32 offset;
+ switch (fields[i].type.code()) {
+ case ValType::I32:
+ offset = layout.addScalar(Scalar::Int32);
+ break;
+ case ValType::I64:
+ offset = layout.addScalar(Scalar::Int64);
+ break;
+ case ValType::F32:
+ offset = layout.addScalar(Scalar::Float32);
+ break;
+ case ValType::F64:
+ offset = layout.addScalar(Scalar::Float64);
+ break;
+ case ValType::Ref:
+ case ValType::AnyRef:
+ offset = layout.addReference(ReferenceType::TYPE_OBJECT);
+ break;
+ default:
+ MOZ_CRASH("Unknown type");
+ }
+ if (!offset.isValid()) {
+ return d.fail("Object too large");
+ }
+
+ fields[i].offset = offset.value();
+ }
+
+ CheckedInt32 totalSize = layout.close();
+ if (!totalSize.isValid()) {
+ return d.fail("Object too large");
+ }
+
+ bool isInline = InlineTypedObject::canAccommodateSize(totalSize.value());
+ uint32_t offsetBy = isInline ? InlineTypedObject::offsetOfDataStart() : 0;
+
+ for (StructField& f : fields) {
+ f.offset += offsetBy;
}
if ((*typeState)[typeIndex] != TypeState::None && (*typeState)[typeIndex] != TypeState::ForwardStruct) {
return d.fail("struct type entry referenced as function");
}
- env->types[typeIndex] = TypeDef(StructType(std::move(fields)));
+ env->types[typeIndex] = TypeDef(StructType(std::move(fields), env->numStructTypes, isInline));
(*typeState)[typeIndex] = TypeState::Struct;
+ env->numStructTypes++;
return true;
}
diff --git a/js/src/wasm/WasmValidate.h b/js/src/wasm/WasmValidate.h
index afeadc794aec..35ef87f89f1e 100644
--- a/js/src/wasm/WasmValidate.h
+++ b/js/src/wasm/WasmValidate.h
@@ -165,6 +165,7 @@ struct ModuleEnvironment
MemoryUsage memoryUsage;
uint32_t minMemoryLength;
Maybe maxMemoryLength;
+ uint32_t numStructTypes;
TypeDefVector types;
FuncTypeWithIdPtrVector funcTypes;
Uint32Vector funcImportGlobalDataOffsets;
@@ -196,7 +197,8 @@ struct ModuleEnvironment
gcFeatureOptIn(HasGcTypes::False),
#endif
memoryUsage(MemoryUsage::None),
- minMemoryLength(0)
+ minMemoryLength(0),
+ numStructTypes(0)
{}
Tier tier() const {
@@ -701,6 +703,9 @@ class Decoder
if (!readVarU32(refTypeIndex)) {
return false;
}
+ if (*refTypeIndex > MaxTypes) {
+ return false;
+ }
} else {
*refTypeIndex = NoRefTypeIndex;
}
diff --git a/netwerk/protocol/res/ExtensionProtocolHandler.cpp b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
index 4b5043817e91..0102e1540c5b 100644
--- a/netwerk/protocol/res/ExtensionProtocolHandler.cpp
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
@@ -184,6 +184,7 @@ public:
{
MOZ_ASSERT(NS_IsMainThread());
mResolve(mFD);
+ mResolve = nullptr;
return NS_OK;
}
diff --git a/testing/mozharness/scripts/android_emulator_unittest.py b/testing/mozharness/scripts/android_emulator_unittest.py
index a201c60a1ec5..2ebec33c38de 100644
--- a/testing/mozharness/scripts/android_emulator_unittest.py
+++ b/testing/mozharness/scripts/android_emulator_unittest.py
@@ -651,12 +651,13 @@ class AndroidEmulatorTest(TestingMixin, BaseScript, MozbaseMixin, CodeCoverageMi
def pre_create_virtualenv(self, action):
dirs = self.query_abs_dirs()
requirements = None
- if self.test_suite == 'mochitest-media':
+ suites = self._query_suites()
+ if ('mochitest-media', 'mochitest-media') in suites:
# mochitest-media is the only thing that needs this
requirements = os.path.join(dirs['abs_mochitest_dir'],
'websocketprocessbridge',
'websocketprocessbridge_requirements.txt')
- elif self.test_suite == 'marionette':
+ elif ('marionette', 'marionette') in suites:
requirements = os.path.join(dirs['abs_test_install_dir'],
'config', 'marionette_requirements.txt')
if requirements:
diff --git a/testing/mozharness/scripts/android_hardware_unittest.py b/testing/mozharness/scripts/android_hardware_unittest.py
index 1daf5f944463..f68dbc297789 100644
--- a/testing/mozharness/scripts/android_hardware_unittest.py
+++ b/testing/mozharness/scripts/android_hardware_unittest.py
@@ -169,12 +169,13 @@ class AndroidHardwareTest(TestingMixin, BaseScript, MozbaseMixin,
def _pre_create_virtualenv(self, action):
dirs = self.query_abs_dirs()
requirements = None
- if self.test_suite == 'mochitest-media':
+ suites = self._query_suites()
+ if ('mochitest-media', 'mochitest-media') in suites:
# mochitest-media is the only thing that needs this
requirements = os.path.join(dirs['abs_mochitest_dir'],
'websocketprocessbridge',
'websocketprocessbridge_requirements.txt')
- elif self.test_suite == 'marionette':
+ elif ('marionette', 'marionette') in suites:
requirements = os.path.join(dirs['abs_test_install_dir'],
'config', 'marionette_requirements.txt')
if requirements:
diff --git a/testing/mozharness/scripts/mobile_l10n.py b/testing/mozharness/scripts/mobile_l10n.py
index 8ab939d81d1e..02b0fd6ce556 100755
--- a/testing/mozharness/scripts/mobile_l10n.py
+++ b/testing/mozharness/scripts/mobile_l10n.py
@@ -207,9 +207,6 @@ class MobileSingleLocale(LocalesMixin, TooltoolMixin, AutomationMixin,
self.abs_dirs = abs_dirs
return self.abs_dirs
- def add_failure(self, locale, message, **kwargs):
- AutomationMixin.add_failure(self, locale, message=message, **kwargs)
-
# Actions {{{2
def clone_locales(self):
self.pull_locale_source()
diff --git a/toolkit/content/tests/chrome/test_bug562554.xul b/toolkit/content/tests/chrome/test_bug562554.xul
index 7ee9ef03d69c..f6f12c58e0a3 100644
--- a/toolkit/content/tests/chrome/test_bug562554.xul
+++ b/toolkit/content/tests/chrome/test_bug562554.xul
@@ -1,5 +1,4 @@
-