Merge inbound to mozilla-central. a=merge

This commit is contained in:
Bogdan Tara 2018-09-22 00:59:34 +03:00
Родитель 374dd2c7c2 36c45bba54
Коммит 0eedaf7640
63 изменённых файлов: 2933 добавлений и 203 удалений

Просмотреть файл

@ -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, &nothing)) {
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, &nothing, &nothing)) {
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, &nothing)) {
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, &nothing, &nothing, &nothing));
}
#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, &nothing));
}
case uint16_t(MiscOp::StructSet): {
if (env.gcTypesEnabled() == HasGcTypes::False) {
return iter.unrecognizedOpcode(&op);
}
uint32_t unusedUint1, unusedUint2;
CHECK(iter.readStructSet(&unusedUint1, &unusedUint2, &nothing, &nothing));
}
case uint16_t(MiscOp::StructNarrow): {
if (env.gcTypesEnabled() == HasGcTypes::False) {
return iter.unrecognizedOpcode(&op);
}
ValType unusedTy, unusedTy2;
CHECK(iter.readStructNarrow(&unusedTy, &unusedTy2, &nothing));
}
#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;