зеркало из https://github.com/mozilla/gecko-dev.git
Merge inbound to mozilla-central. a=merge
This commit is contained in:
Коммит
0eedaf7640
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<button id="rerender">Re-render</button>
|
||||
<button id="logState">Log state</button>
|
||||
<button id="debugFrame" hidden>Debug frame</button>
|
||||
<button id="toggleDirectionality">Toggle :dir</button>
|
||||
</section>
|
||||
<section class="group">
|
||||
<h1>Requests</h1>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
async function setup(addresses = [], cards = []) {
|
||||
await setupFormAutofillStorage();
|
||||
await cleanupFormAutofillStorage();
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -20,7 +20,9 @@ Test the basic-card-form element
|
|||
<link rel="stylesheet" type="text/css" href="../../res/components/accepted-cards.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<p id="display">
|
||||
<p id="display" style="height: 100vh; margin: 0;">
|
||||
<iframe id="templateFrame" src="../../res/paymentRequest.xhtml" width="0" height="0"
|
||||
style="float: left;"></iframe>
|
||||
</p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -292,6 +292,45 @@ ScalarTypeDescr::call(JSContext* cx, unsigned argc, Value* vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
/* static */ TypeDescr*
|
||||
GlobalObject::getOrCreateScalarTypeDescr(JSContext* cx, Handle<GlobalObject*> 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<TypedObjectModuleObject*> module(cx,
|
||||
&GlobalObject::getOrCreateTypedObjectModule(cx, global)->as<TypedObjectModuleObject>());
|
||||
if (!module) {
|
||||
return nullptr;
|
||||
}
|
||||
return &module->getReservedSlot(slot).toObject().as<TypeDescr>();
|
||||
}
|
||||
|
||||
/* static */ TypeDescr*
|
||||
GlobalObject::getOrCreateReferenceTypeDescr(JSContext* cx, Handle<GlobalObject*> global,
|
||||
ReferenceType type)
|
||||
{
|
||||
int32_t slot = 0;
|
||||
switch (type) {
|
||||
case ReferenceType::TYPE_OBJECT: slot = TypedObjectModuleObject::ObjectDesc; break;
|
||||
default: MOZ_CRASH("NYI");
|
||||
}
|
||||
|
||||
Rooted<TypedObjectModuleObject*> module(cx,
|
||||
&GlobalObject::getOrCreateTypedObjectModule(cx, global)->as<TypedObjectModuleObject>());
|
||||
if (!module) {
|
||||
return nullptr;
|
||||
}
|
||||
return &module->getReservedSlot(slot).toObject().as<TypeDescr>();
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* 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<bool> fieldMutabilities(cx);
|
||||
Vector<StructFieldProps> 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<bool>& fieldMutabilities)
|
||||
Vector<StructFieldProps>& 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<GlobalObject*> 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);
|
||||
|
|
|
@ -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<bool>& fieldMutabilities);
|
||||
Vector<StructFieldProps>& 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
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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/);
|
||||
|
|
|
@ -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/);
|
|
@ -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,
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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<GlobalObject*> global,
|
||||
Scalar::Type scalarType);
|
||||
|
||||
static TypeDescr*
|
||||
getOrCreateReferenceTypeDescr(JSContext* cx, Handle<GlobalObject*> global,
|
||||
ReferenceType type);
|
||||
|
||||
TypedObjectModuleObject& getTypedObjectModule() const;
|
||||
|
||||
static JSObject*
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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<RegPtr>& 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<RegPtr>& 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<RegPtr>& 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))
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Code>(std::move(codeTier), metadata, std::move(jumpTables));
|
||||
StructTypeVector structTypes;
|
||||
cursor = DeserializeVector(cursor, &structTypes);
|
||||
if (!cursor) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MutableCode code = js_new<Code>(std::move(codeTier), metadata, std::move(jumpTables),
|
||||
std::move(structTypes));
|
||||
if (!code || !code->initialize(linkData)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -686,11 +686,13 @@ class Code : public ShareableBase<Code>
|
|||
SharedMetadata metadata_;
|
||||
ExclusiveData<CacheableCharsVector> 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<Code>
|
|||
|
||||
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();
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1037,11 +1037,6 @@ ModuleGenerator::finishModule(const ShareableBytes& bytecode,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
MutableCode code = js_new<Code>(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<Code>(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<Module>(*code,
|
||||
std::move(env_->imports),
|
||||
std::move(env_->exports),
|
||||
std::move(structTypes),
|
||||
std::move(dataSegments),
|
||||
std::move(env_->elemSegments),
|
||||
std::move(customSections),
|
||||
|
|
|
@ -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*> 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<TypedObject*> obj(cx);
|
||||
Rooted<StructTypeDescr*> typeDescr(cx);
|
||||
|
||||
if (mustUnboxAnyref) {
|
||||
Rooted<NativeObject*> no(cx, static_cast<NativeObject*>(nonnullPtr));
|
||||
if (!no->is<TypedObject>()) {
|
||||
return nullptr;
|
||||
}
|
||||
obj = &no->as<TypedObject>();
|
||||
Rooted<TypeDescr*> td(cx, &obj->typeDescr());
|
||||
if (td->kind() != type::Struct) {
|
||||
return nullptr;
|
||||
}
|
||||
typeDescr = &td->as<StructTypeDescr>();
|
||||
} else {
|
||||
obj = static_cast<TypedObject*>(nonnullPtr);
|
||||
typeDescr = &obj->typeDescr().as<StructTypeDescr>();
|
||||
}
|
||||
|
||||
// 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<WasmInstanceObject*> object,
|
||||
SharedCode code,
|
||||
UniqueTlsData tlsDataIn,
|
||||
HandleWasmMemoryObject memory,
|
||||
SharedTableVector&& tables,
|
||||
StructTypeDescrVector&& structTypeDescrs,
|
||||
Handle<FunctionVector> 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
|
||||
|
|
|
@ -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<FunctionVector> 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<Instance> UniqueInstance;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<FunctionVector> 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,
|
||||
|
|
|
@ -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<RefPtr<wasm::Table>, 0, SystemAllocPolicy>&& tables,
|
||||
GCVector<HeapPtr<StructTypeDescr*>, 0, SystemAllocPolicy>&& structTypeDescrs,
|
||||
Handle<FunctionVector> funcImports,
|
||||
const wasm::GlobalDescVector& globals,
|
||||
wasm::HandleValVector globalImportValues,
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#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<Module>(*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<Code>(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<Code>(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<StructFieldProps>* 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<TypeDescr*> 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<StructTypeDescrVector> 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<NativeObject>());
|
||||
RootedObject prototype(cx, &toModule->getReservedSlot(
|
||||
TypedObjectModuleObject::StructTypePrototype).toObject());
|
||||
|
||||
for (const StructType& structType : structTypes()) {
|
||||
AutoIdVector ids(cx);
|
||||
AutoValueVector fieldTypeObjs(cx);
|
||||
Vector<StructFieldProps> 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*>
|
||||
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<FunctionVector> funcImports,
|
||||
|
@ -1134,6 +1279,13 @@ Module::instantiate(JSContext* cx,
|
|||
code = code_;
|
||||
}
|
||||
|
||||
// Create type descriptors for any struct types that the module has.
|
||||
|
||||
Rooted<StructTypeDescrVector> 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,
|
||||
|
|
|
@ -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<StructTypeDescrVector> 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:
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Value>* addr);
|
||||
MOZ_MUST_USE bool readLinearMemoryAddressAligned(uint32_t byteSize, LinearMemoryAddress<Value>* 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<Value, 8, SystemAllocPolicy>* 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<Policy>::fail(const char* msg)
|
|||
return d_.fail(lastOpcodeOffset(), msg);
|
||||
}
|
||||
|
||||
template <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::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<Policy>::readRefNull(ValType* type)
|
||||
{
|
||||
MOZ_ASSERT(Classify(op_) == OpKind::RefNull);
|
||||
if (!readReferenceType(type, "ref.null")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return push(StackType(*type));
|
||||
}
|
||||
|
||||
template <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::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 <typename Policy>
|
||||
|
@ -2119,6 +2163,165 @@ OpIter<Policy>::readMemOrTableInit(bool isMem, uint32_t* segIndex,
|
|||
return true;
|
||||
}
|
||||
|
||||
template <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::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 <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::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 <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::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 <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::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 <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::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 <typename Policy>
|
||||
inline bool
|
||||
OpIter<Policy>::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
|
||||
|
||||
|
|
|
@ -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<AstMemFill>());
|
||||
case AstExprKind::MemOrTableInit:
|
||||
return ResolveMemOrTableInit(r, expr.as<AstMemOrTableInit>());
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GC
|
||||
case AstExprKind::StructNew:
|
||||
return ResolveStructNew(r, expr.as<AstStructNew>());
|
||||
case AstExprKind::StructGet:
|
||||
return ResolveStructGet(r, expr.as<AstStructGet>());
|
||||
case AstExprKind::StructSet:
|
||||
return ResolveStructSet(r, expr.as<AstStructSet>());
|
||||
case AstExprKind::StructNarrow:
|
||||
return ResolveStructNarrow(r, expr.as<AstStructNarrow>());
|
||||
#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<AstMemFill>());
|
||||
case AstExprKind::MemOrTableInit:
|
||||
return EncodeMemOrTableInit(e, expr.as<AstMemOrTableInit>());
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_GC
|
||||
case AstExprKind::StructNew:
|
||||
return EncodeStructNew(e, expr.as<AstStructNew>());
|
||||
case AstExprKind::StructGet:
|
||||
return EncodeStructGet(e, expr.as<AstStructGet>());
|
||||
case AstExprKind::StructSet:
|
||||
return EncodeStructSet(e, expr.as<AstStructSet>());
|
||||
case AstExprKind::StructNarrow:
|
||||
return EncodeStructNarrow(e, expr.as<AstStructNarrow>());
|
||||
#endif
|
||||
}
|
||||
MOZ_CRASH("Bad expr kind");
|
||||
|
|
|
@ -74,6 +74,9 @@ class WasmGlobalObject;
|
|||
typedef GCVector<WasmGlobalObject*, 0, SystemAllocPolicy> WasmGlobalObjectVector;
|
||||
typedef Rooted<WasmGlobalObject*> RootedWasmGlobalObject;
|
||||
|
||||
class StructTypeDescr;
|
||||
typedef GCVector<HeapPtr<StructTypeDescr*>, 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<StructField, 0, SystemAllocPolicy> 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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -165,6 +165,7 @@ struct ModuleEnvironment
|
|||
MemoryUsage memoryUsage;
|
||||
uint32_t minMemoryLength;
|
||||
Maybe<uint32_t> 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;
|
||||
}
|
||||
|
|
|
@ -184,6 +184,7 @@ public:
|
|||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mResolve(mFD);
|
||||
mResolve = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
|
||||
<!--
|
||||
XUL Widget Test for bug 562554
|
||||
|
@ -47,12 +46,15 @@ function test() {
|
|||
let tests = [
|
||||
// Click on the toolbarbutton itself - should call popupshowing
|
||||
() => synthesizeMouse($("toolbarmenu"), 10, 50, {}, window),
|
||||
() => is(eventCount.popupshowing, 1, "Got first popupshowing event"),
|
||||
|
||||
// Click on button1 which has allowevents="true" - should call clickbutton1
|
||||
() => synthesizeMouse($("toolbarmenu"), 10, 15, {}, window),
|
||||
() => is(eventCount.clickbutton1, 1, "Button 1 clicked"),
|
||||
|
||||
// Click on button2 where it intersects with button1 - should call popupshowing
|
||||
() => synthesizeMouse($("toolbarmenu"), 85, 15, {}, window),
|
||||
() => is(eventCount.popupshowing, 2, "Got second popupshowing event"),
|
||||
|
||||
// Click on button2 outside of intersection - should call popupshowing
|
||||
() => synthesizeMouse($("toolbarmenu"), 150, 15, {}, window)
|
||||
|
|
|
@ -20,7 +20,9 @@
|
|||
@import url("chrome://global/skin/progressmeter.css");
|
||||
@import url("chrome://global/skin/radio.css");
|
||||
@import url("chrome://global/skin/richlistbox.css");
|
||||
@import url("chrome://global/skin/scrollbox.css");
|
||||
@import url("chrome://global/skin/splitter.css");
|
||||
@import url("chrome://global/skin/tabbox.css");
|
||||
@import url("chrome://global/skin/toolbar.css");
|
||||
@import url("chrome://global/skin/toolbarbutton.css");
|
||||
@import url("chrome://global/skin/wizard.css");
|
||||
|
|
|
@ -18,10 +18,6 @@
|
|||
</binding>
|
||||
|
||||
<binding id="arrowscrollbox" extends="chrome://global/content/bindings/general.xml#basecontrol">
|
||||
<resources>
|
||||
<stylesheet src="chrome://global/skin/scrollbox.css"/>
|
||||
</resources>
|
||||
|
||||
<content>
|
||||
<xul:toolbarbutton class="scrollbutton-up"
|
||||
anonid="scrollbutton-up"
|
||||
|
|
|
@ -11,10 +11,6 @@
|
|||
|
||||
<binding id="toolbarbutton" display="xul:button"
|
||||
extends="chrome://global/content/bindings/button.xml#button-base">
|
||||
<resources>
|
||||
<stylesheet src="chrome://global/skin/toolbarbutton.css"/>
|
||||
</resources>
|
||||
|
||||
<content>
|
||||
<children includes="observes|template|menupopup|panel|tooltip"/>
|
||||
<xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,type,consumeanchor,triggeringprincipal=iconloadingprincipal"/>
|
||||
|
|
|
@ -28,7 +28,7 @@ toolbarbutton {
|
|||
}
|
||||
|
||||
.toolbarbutton-text {
|
||||
margin: 0 !important; /* !important for overriding global.css */
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ toolbarbutton:hover {
|
|||
color: -moz-buttonhovertext;
|
||||
}
|
||||
|
||||
toolbarbutton:hover:active,
|
||||
toolbarbutton:hover:active:not([disabled="true"]),
|
||||
toolbarbutton[open="true"] {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 2px;
|
||||
|
@ -49,11 +49,7 @@ toolbarbutton[open="true"] {
|
|||
color: ButtonText;
|
||||
}
|
||||
|
||||
toolbarbutton[disabled="true"],
|
||||
toolbarbutton[disabled="true"]:hover,
|
||||
toolbarbutton[disabled="true"]:hover:active,
|
||||
toolbarbutton[disabled="true"][open="true"] {
|
||||
padding: 3px;
|
||||
toolbarbutton[disabled="true"] {
|
||||
color: GrayText;
|
||||
}
|
||||
|
||||
|
@ -68,11 +64,7 @@ toolbarbutton:-moz-lwtheme:not(:hover):not([checked="true"]):not([open="true"]):
|
|||
/* ::::: toolbarbutton menu ::::: */
|
||||
|
||||
.toolbarbutton-menu-dropmarker {
|
||||
-moz-appearance: toolbarbutton-dropdown !important;
|
||||
}
|
||||
|
||||
.toolbarbutton-menu-dropmarker[disabled="true"] {
|
||||
padding: 0 !important;
|
||||
-moz-appearance: toolbarbutton-dropdown;
|
||||
}
|
||||
|
||||
/* ::::: toolbarbutton badged ::::: */
|
||||
|
|
|
@ -13,8 +13,8 @@ toolbarbutton {
|
|||
}
|
||||
|
||||
.toolbarbutton-text {
|
||||
margin: 0 !important; /* !important for overriding global.css */
|
||||
padding: 0px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ toolbarbutton[disabled="true"][open="true"] {
|
|||
/* ::::: toolbarbutton menu ::::: */
|
||||
|
||||
.toolbarbutton-menu-dropmarker {
|
||||
-moz-appearance: none !important;
|
||||
-moz-appearance: none;
|
||||
list-style-image: url("chrome://global/skin/arrow/arrow-dn.png");
|
||||
padding-inline-start: 2px;
|
||||
width: auto;
|
||||
|
@ -37,8 +37,6 @@ toolbarbutton[disabled="true"][open="true"] {
|
|||
|
||||
.toolbarbutton-menu-dropmarker[disabled="true"] {
|
||||
list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.png");
|
||||
padding: 0;
|
||||
padding-inline-start: 2px;
|
||||
}
|
||||
|
||||
/* ::::: toolbarbutton badged ::::: */
|
||||
|
|
|
@ -24,7 +24,7 @@ toolbarbutton {
|
|||
}
|
||||
|
||||
.toolbarbutton-text {
|
||||
margin: 0 !important; /* !important for overriding global.css */
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ toolbarbutton[checked="true"]:not([disabled="true"]) {
|
|||
/* ::::: toolbarbutton menu ::::: */
|
||||
|
||||
.toolbarbutton-menu-dropmarker {
|
||||
-moz-appearance: none !important;
|
||||
-moz-appearance: none;
|
||||
list-style-image: url("chrome://global/skin/icons/arrow-dropdown-12.svg");
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
|
|
Загрузка…
Ссылка в новой задаче