Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ spec:webidl;
type:dfn; text:resolve
spec:ecmascript; for:ECMAScript;
type:dfn; text:realm
spec:infra;
type:dfn; text:string
</pre>

<style>
Expand Down Expand Up @@ -6096,8 +6098,9 @@ dictionary MLInstanceNormalizationOptions : MLOperatorOptions {
};

partial interface MLGraphBuilder {
MLOperand instanceNormalization(MLOperand input,
optional MLInstanceNormalizationOptions options = {});
MLOperand instanceNormalization(
MLOperand input,
optional MLInstanceNormalizationOptions options = {});
};

dictionary MLNormalizationSupportLimits {
Expand Down Expand Up @@ -7601,7 +7604,7 @@ dictionary MLPool2dOptions : MLOperatorOptions {
sequence<[EnforceRange] unsigned long> strides;
sequence<[EnforceRange] unsigned long> dilations;
MLInputOperandLayout layout = "nchw";
MLRoundingType roundingType = "floor";
MLRoundingType outputShapeRounding = "floor";
sequence<[EnforceRange] unsigned long> outputSizes;
};

Expand Down Expand Up @@ -7653,15 +7656,15 @@ partial dictionary MLOpSupportLimits {
- input tensor: *[batches, height, width, inputChannels]*
- output tensor: *[batches, height, width, outputChannels]*

: <dfn>roundingType</dfn>
: <dfn>outputShapeRounding</dfn>
::
The rounding function used to compute the output shape.
The rounding function used to compute the output shape, depending on whether full or partial window results are desired.

: <dfn>outputSizes</dfn>
::
A list of length 2.
Specifies the sizes of the two spacial dimensions of the output tensor. When the output sizes are explicitly specified, the {{MLPool2dOptions/roundingType}} is ignored.

A list of length 2: *[outputHeight, outputWidth]*
Specifies the sizes of the two spatial dimensions of the output tensor.
When the output sizes are explicitly specified, the {{MLPool2dOptions/roundingType}} is ignored.
If not specified, the output sizes are automatically computed.
</dl>

Expand All @@ -7673,7 +7676,7 @@ partial dictionary MLOpSupportLimits {

**Returns:** an {{MLOperand}}. The output 4-D tensor that contains the
result of the reduction. The logical shape is interpreted according to the
value of {{MLPool2dOptions/layout}}. More specifically, if the {{MLPool2dOptions/roundingType}} is {{MLRoundingType/"floor"}}, the spatial dimensions of the output tensor can be calculated as follows:
value of {{MLPool2dOptions/layout}}. More specifically, if the {{MLPool2dOptions/roundingType}} is {{MLRoundingType/"floor"}}, the spatial dimensions for a single dimension of the output tensor can be calculated as follows:

`output size = floor(1 + (input size - filter size + beginning padding + ending padding) / stride)`

Expand Down Expand Up @@ -7756,8 +7759,8 @@ partial dictionary MLOpSupportLimits {
1. Let « |windowHeight|, |windowWidth| » be |options|.{{MLPool2dOptions/windowDimensions}}.
1. Let « |calculatedOutputHeight|, |calculatedOutputWidth| » be the result of [=MLGraphBuilder/calculating conv2d output sizes=] given |inputHeight|, |inputWidth|, |windowHeight|, |windowWidth|, |options|.{{MLPool2dOptions/padding}}, |options|.{{MLPool2dOptions/strides}}, and |options|.{{MLPool2dOptions/dilations}}.
1. If |options|.{{MLPool2dOptions/outputSizes}} [=map/exists=], then:
1. Let « |outputHeight|, |outputWidth| » be |options|.{{MLPool2dOptions/outputSizes}}.
1. If neither |outputHeight| equals floor( |calculatedOutputHeight| ) and |outputWidth| equals floor( |calculatedOutputWidth| ), nor |outputHeight| equals ceil( |calculatedOutputHeight| ) and |outputWidth| equals ceil( |calculatedOutputWidth| ), then [=exception/throw=] a {{TypeError}}.
1. Let « |outputHeight|, |outputWidth| » be |options|.{{MLPool2dOptions/outputSizes}}.
1. If neither |outputHeight| equals floor( |calculatedOutputHeight| ) and |outputWidth| equals floor( |calculatedOutputWidth| ), nor |outputHeight| equals ceil( |calculatedOutputHeight| ) and |outputWidth| equals ceil( |calculatedOutputWidth| ), then [=exception/throw=] a {{TypeError}}.
1. Otherwise:
1. Let « |outputHeight|, |outputWidth| » be « |calculatedOutputHeight|, |calculatedOutputWidth| ».
1. Switch on |options|.{{MLPool2dOptions/roundingType}}:
Expand Down
58 changes: 37 additions & 21 deletions tools/lint.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ log(`loading Bikeshed source "${options.bikeshed}"...`);
const source = await fs.readFile(options.bikeshed, 'utf8');

log(`loading generated HTML "${options.html}"...`);
let file = await fs.readFile(options.html, 'utf8');
let htmlFileData = await fs.readFile(options.html, 'utf8');

log('massaging HTML...');
// node-html-parser doesn't understand that some elements are mutually self-closing;
Expand Down Expand Up @@ -126,13 +126,13 @@ log('massaging HTML...');
// If followed by a similar open tag, or the container close tag:
'(?=<(' + tags.join('|') + '|/(' + containers.join('|') + '))\\b)',
'sg');
file = file.replaceAll(
htmlFileData = htmlFileData.replaceAll(
// Then insert the explicit close tag right after the contents.
re, (_, opener, tag, content) => `${opener}${content}</${tag}>`);
});

log('parsing HTML...');
const root = parse(file, {
const root = parse(htmlFileData, {
blockTextElements: {
// Explicitly don't list <pre> to force children to be parsed.
// See https://github.com/taoqf/node-html-parser/issues/78
Expand Down Expand Up @@ -244,13 +244,25 @@ const DESCENDANT_COMBINATOR = ' ';

let exitCode = 0;

function getLineNumber(textData, /*HTMLElement*/ node) {
const result = textData.substring(0, node.range[0]).match(/\n/g);
if (result) {
return result.length + 1;
}
return 1;
}

// Failing checks must call `error()` which will log the error message and set
// the process exit code to signal failure.
function error(message) {
console.error(message);
exitCode = 1;
}

function errorHtml(message, /*HTMLElement*/ node) {
console.error(`${options.html}:${getLineNumber(htmlFileData, node)}: ${message}`)
}

log('running checks...');

// Checks can operate on:
Expand Down Expand Up @@ -296,7 +308,7 @@ const MAX_IDL_WIDTH = 88;
for (const idl of root.querySelectorAll(IDL_BLOCK_SELECTOR)) {
innerText(idl).split(/\n/).forEach(line => {
if (line.length > MAX_IDL_WIDTH) {
error(`Overlong IDL: ${line}`);
errorHtml(`Overlong IDL: ${line}`, idl);
}
});
}
Expand Down Expand Up @@ -346,7 +358,7 @@ for (const match of source.matchAll(/(\|\w*desc\w*\|)'s \[=MLOperand\/shape=\]/i
// itself - which we consider an error.
for (const element of root.querySelectorAll(
IDL_BLOCK_SELECTOR + DESCENDANT_COMBINATOR + DICTMEMBER_DFN_SELECTOR)) {
error(`Dictionary member missing dfn: ${element.innerText}`);
errorHtml(`Dictionary member missing dfn: ${element.innerText}`, element);
}

// [WebNN] Look for suspicious stuff in algorithm steps
Expand All @@ -356,12 +368,12 @@ for (const element of root.querySelectorAll(ALGORITHM_STEP_SELECTOR)) {
// Exclude [[ for inner slots (e.g. [[name]])
// Exclude [A for references (e.g. [WEBIDL])
for (const match of element.innerText.matchAll(/(?<!\w|\[|\]|«)\[(?!\[|[A-Z])/g)) {
error(`Non-index use of [] in algorithm: ${format(match)}`);
errorHtml(`Non-index use of [] in algorithm: ${format(match)}`, element);
}
// | in the DOM is likely an unclosed variable, since we don't use symbols for
// absolute (|n|) , logical or (a || b) or bitwise or (a | b).
for (const match of element.innerText.matchAll(/\|/g)) {
error(`Unclosed variable in algorithm: ${format(match)}`);
errorHtml(`Unclosed variable in algorithm: ${format(match)}`, element);
}
}

Expand Down Expand Up @@ -408,7 +420,7 @@ for (const algorithm of root.querySelectorAll(ALGORITHM_SELECTOR)) {
// e.g. "Let validationSteps given MLOperandDescriptor descriptor be..."
seen.add(name);
} else if (!seen.has(name)) {
error(`Uninitialized variable "${name}" in "${algorithm.getAttribute('data-algorithm')}": ${text}`);
errorHtml(`Uninitialized variable "${name}" in "${algorithm.getAttribute('data-algorithm')}": ${text}`, algorithm);
seen.add(name);
}
}
Expand All @@ -423,7 +435,7 @@ const algorithmVars = new Set(root.querySelectorAll(
ALGORITHM_SELECTOR + DESCENDANT_COMBINATOR + VAR_SELECTOR));
for (const v of root.querySelectorAll(VAR_SELECTOR)
.filter(v => !algorithmVars.has(v))) {
error(`Variable outside of algorithm: ${v.innerText}`);
errorHtml(`Variable outside of algorithm: ${v.innerText}`, v);
}

// [Generic] Algorithms should either throw or reject, never both.
Expand All @@ -450,7 +462,7 @@ for (const algorithm of root.querySelectorAll(ALGORITHM_SELECTOR)) {

// If we saw the other type in use in this algorithm, that's an error.
if (seen.has(other[type])) {
error(`Algorithm "${name}" mixes throwing with promises: ${format(match)}`);
errorHtml(`Algorithm "${name}" mixes throwing with promises: ${format(match)}`, algorithm);
break;
}
seen.add(type);
Expand All @@ -476,7 +488,7 @@ const NORMATIVE_REFERENCES = new Set([
for (const term of root.querySelectorAll('#normative + dl > dt')) {
const ref = term.innerText.trim();
if (!NORMATIVE_REFERENCES.has(ref)) {
error(`Unexpected normative reference to ${ref}`);
errorHtml(`Unexpected normative reference to ${ref}`, term);
}
}

Expand All @@ -490,15 +502,15 @@ for (const pre of root.querySelectorAll('pre.highlight:not(.idl)')) {
try {
const f = AsyncFunction([], '"use strict";' + script);
} catch (ex) {
error(`Invalid script: ${ex.message}: ${script.substr(0, 20)}`);
errorHtml(`Invalid script: ${ex.message}: ${script.substr(0, 20)}`, pre);
}
}

// [Generic] Ensure algorithm steps end in '.' or ':'.
for (const p of root.querySelectorAll(ALGORITHM_STEP_SELECTOR)) {
const match = p.innerText.match(/[^.:]$/);
if (match) {
error(`Algorithm steps should end with '.' or ':': ${format(match)}`);
errorHtml(`Algorithm steps should end with '.' or ':': ${format(match)}`, p);
}
}

Expand All @@ -507,7 +519,7 @@ for (const p of root.querySelectorAll(ALGORITHM_STEP_SELECTOR)) {
// a WebNN-specific rule.
for (const p of root.querySelectorAll(ALGORITHM_STEP_SELECTOR)) {
if (p.innerText === 'Return undefined.') {
error(`Unnecessary algorithm step: ${p.innerText}`);
errorHtml(`Unnecessary algorithm step: ${p.innerText}`, p);
}
}

Expand All @@ -517,7 +529,7 @@ for (const p of root.querySelectorAll(ALGORITHM_STEP_SELECTOR)) {
const match = text.match(/\bIf\b/);
const match2 = text.match(/, then\b/);
if (match && !match2) {
error(`Algorithm steps with 'If' should have ', then': ${format(match)}`);
errorHtml(`Algorithm steps with 'If' should have ', then': ${format(match)}`, p);
}
}

Expand All @@ -526,7 +538,7 @@ for (const p of root.querySelectorAll(ALGORITHM_STEP_SELECTOR)) {
const text = p.innerText;
const match = text.match(/^Otherwise[^,:]/);
if (match) {
error(`In algorithm steps 'Otherwise' should be followed by ':' or ',': ${format(match)}`);
errorHtml(`In algorithm steps 'Otherwise' should be followed by ':' or ',': ${format(match)}`, p);
}
}

Expand All @@ -544,23 +556,23 @@ const interfaces = new Set(
for (const dfn of root.querySelectorAll(METHOD_DFN_SELECTOR)) {
const dfnFor = dfn.getAttribute('data-dfn-for');
if (!dfnFor || !interfaces.has(dfnFor)) {
error(`Method definition '${dfn.innerText}' for undefined '${dfnFor}'`);
errorHtml(`Method definition '${dfn.innerText}' for undefined '${dfnFor}'`, dfn);
}
}

// [Generic] Ensure every IDL argument is linked to a definition.
for (const dfn of root.querySelectorAll(
IDL_BLOCK_SELECTOR + DESCENDANT_COMBINATOR + ARGUMENT_DFN_SELECTOR)) {
const dfnFor = dfn.getAttribute('data-dfn-for');
error(`Missing <dfn argument for="${dfnFor}">${dfn.innerText}</dfn> (or equivalent)`);
errorHtml(`Missing <dfn argument for="${dfnFor}">${dfn.innerText}</dfn> (or equivalent)`, dfn);
}

// [Generic] Ensure every argument <dfn> is correctly associated with a method.
// This tries to catch extraneous definitions, e.g. after an arg is removed.
for (const dfn of root.querySelectorAll(ARGUMENT_DFN_SELECTOR)) {
const dfnFor = dfn.getAttribute('data-dfn-for');
if (!dfnFor.split(/\b/).includes(dfn.innerText)) {
error(`Argument definition '${dfn.innerText}' doesn't appear in '${dfnFor}'`);
errorHtml(`Argument definition '${dfn.innerText}' doesn't appear in '${dfnFor}'`, dfn);
}
}

Expand Down Expand Up @@ -602,7 +614,7 @@ for (const algorithm of root.querySelectorAll(
if (
href !== '#constraints-' + method &&
href !== '#constraints-' + method.toLowerCase()) {
error(`Steps for ${method}() link to ${href}`);
errorHtml(`Steps for ${method}() link to ${href}`, href);
}
}
}
Expand All @@ -612,7 +624,7 @@ for (const algorithm of root.querySelectorAll(
for (const table of root.querySelectorAll('table.data').filter(e => e.id.startsWith('constraints-'))) {
// Look for `<em>identifier</em>` which is wrong, except for `output`.
for (const match of table.innerHTML.matchAll(/<em>(?!output)(\w+)<\/em>/ig)) {
error(`Constraints table should link not style args: ${format(match)}`);
errorHtml(`Constraints table should link not style args: ${format(match)}`, table);
}
}

Expand All @@ -634,4 +646,8 @@ for (const match of text.matchAll(/ be a new ([A-Z]\w+)\b(?! in realm)/g)) {
error(`Object creation must specify realm: ${format(match)}`);
}

if (exitCode == 0) {
console.log("Linting succeeded.");
}

globalThis.process.exit(exitCode);