Remove unnecessary Test262 harness file and implement $262.IsHTMLDDA
[WebKit-https.git] / JSTests / wasm / Builder.js
1 /*
2  * Copyright (C) 2016 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 import * as assert from 'assert.js';
27 import * as BuildWebAssembly from 'Builder_WebAssemblyBinary.js';
28 import * as LLB from 'LowLevelBinary.js';
29 import * as WASM from 'WASM.js';
30 import * as util from 'utilities.js';
31
32 const _isValidValue = (value, type) => {
33     switch (type) {
34     // We allow both signed and unsigned numbers.
35     case "i32": return Math.round(value) === value && LLB.varint32Min <= value && value <= LLB.varuint32Max;
36     case "anyref": return true;
37     case "i64": return true; // FIXME https://bugs.webkit.org/show_bug.cgi?id=163420 64-bit values
38     case "f32": return typeof(value) === "number" && isFinite(value);
39     case "f64": return typeof(value) === "number" && isFinite(value);
40     default: throw new Error(`Implementation problem: unknown type ${type}`);
41     }
42 };
43 const _unknownSectionId = 0;
44
45 const _normalizeFunctionSignature = (params, ret) => {
46     assert.isArray(params);
47     for (const p of params)
48         assert.truthy(WASM.isValidValueType(p) || p === "void", `Type parameter ${p} needs a valid value type`);
49     if (typeof(ret) === "undefined")
50         ret = "void";
51     assert.isNotArray(ret, `Multiple return values not supported by WebAssembly yet`);
52     assert.truthy(WASM.isValidBlockType(ret), `Type return ${ret} must be valid block type`);
53     return [params, ret];
54 };
55
56 const _errorHandlingProxyFor = builder => builder["__isProxy"] ? builder : new Proxy(builder, {
57     get: (target, property, receiver) => {
58         if (property === "__isProxy")
59             return true;
60         if (target[property] === undefined)
61             throw new Error(`WebAssembly builder received unknown property '${property}'`);
62         return target[property];
63     }
64 });
65
66 const _maybeRegisterType = (builder, type) => {
67     const typeSection = builder._getSection("Type");
68     if (typeof(type) === "number") {
69         // Type numbers already refer to the type section, no need to register them.
70         if (builder._checked) {
71             assert.isNotUndef(typeSection, `Can not use type ${type} if a type section is not present`);
72             assert.isNotUndef(typeSection.data[type], `Type ${type} does not exist in type section`);
73         }
74         return type;
75     }
76     assert.hasObjectProperty(type, "params", `Expected type to be a number or object with 'params' and optionally 'ret' fields`);
77     const [params, ret] = _normalizeFunctionSignature(type.params, type.ret);
78     assert.isNotUndef(typeSection, `Can not add type if a type section is not present`);
79     // Try reusing an equivalent type from the type section.
80     types:
81     for (let i = 0; i !== typeSection.data.length; ++i) {
82         const t = typeSection.data[i];
83         if (t.ret === ret && params.length === t.params.length) {
84             for (let j = 0; j !== t.params.length; ++j) {
85                 if (params[j] !== t.params[j])
86                     continue types;
87             }
88             type = i;
89             break;
90         }
91     }
92     if (typeof(type) !== "number") {
93         // Couldn't reuse a pre-existing type, register this type in the type section.
94         typeSection.data.push({ params: params, ret: ret });
95         type = typeSection.data.length - 1;
96     }
97     return type;
98 };
99
100 const _importFunctionContinuation = (builder, section, nextBuilder) => {
101     return (module, field, type) => {
102         assert.isString(module, `Import function module should be a string, got "${module}"`);
103         assert.isString(field, `Import function field should be a string, got "${field}"`);
104         const typeSection = builder._getSection("Type");
105         type = _maybeRegisterType(builder, type);
106         section.data.push({ field: field, type: type, kind: "Function", module: module });
107         // Imports also count in the function index space. Map them as objects to avoid clashing with Code functions' names.
108         builder._registerFunctionToIndexSpace({ module: module, field: field });
109         return _errorHandlingProxyFor(nextBuilder);
110     };
111 };
112
113 const _importMemoryContinuation = (builder, section, nextBuilder) => {
114     return (module, field, {initial, maximum}) => {
115         assert.isString(module, `Import Memory module should be a string, got "${module}"`);
116         assert.isString(field, `Import Memory field should be a string, got "${field}"`);
117         section.data.push({module, field, kind: "Memory", memoryDescription: {initial, maximum}});
118         return _errorHandlingProxyFor(nextBuilder);
119     };
120 };
121
122 const _importTableContinuation = (builder, section, nextBuilder) => {
123     return (module, field, {initial, maximum, element}) => {
124         assert.isString(module, `Import Table module should be a string, got "${module}"`);
125         assert.isString(field, `Import Table field should be a string, got "${field}"`);
126         section.data.push({module, field, kind: "Table", tableDescription: {initial, maximum, element}});
127         return _errorHandlingProxyFor(nextBuilder);
128     };
129 };
130
131 const _exportFunctionContinuation = (builder, section, nextBuilder) => {
132     return (field, index, type) => {
133         assert.isString(field, `Export function field should be a string, got "${field}"`);
134         const typeSection = builder._getSection("Type");
135         if (typeof(type) !== "undefined") {
136             // Exports can leave the type unspecified, letting the Code builder patch them up later.
137             type = _maybeRegisterType(builder, type);
138         }
139
140         // We can't check much about "index" here because the Code section succeeds the Export section. More work is done at Code().End() time.
141         switch (typeof(index)) {
142         case "string": break; // Assume it's a function name which will be revealed in the Code section.
143         case "number": break; // Assume it's a number in the "function index space".
144         case "object":
145             // Re-exporting an import.
146             assert.hasObjectProperty(index, "module", `Re-exporting "${field}" from an import`);
147             assert.hasObjectProperty(index, "field", `Re-exporting "${field}" from an import`);
148             break;
149         case "undefined":
150             // Assume it's the same as the field (i.e. it's not being renamed).
151             index = field;
152             break;
153         default: throw new Error(`Export section's index must be a string or a number, got ${index}`);
154         }
155
156         const correspondingImport = builder._getFunctionFromIndexSpace(index);
157         const importSection = builder._getSection("Import");
158         if (typeof(index) === "object") {
159             // Re-exporting an import using its module+field name.
160             assert.isNotUndef(correspondingImport, `Re-exporting "${field}" couldn't find import from module "${index.module}" field "${index.field}"`);
161             index = correspondingImport;
162             if (typeof(type) === "undefined")
163                 type = importSection.data[index].type;
164             if (builder._checked)
165                 assert.eq(type, importSection.data[index].type, `Re-exporting import "${importSection.data[index].field}" as "${field}" has mismatching type`);
166         } else if (typeof(correspondingImport) !== "undefined") {
167             // Re-exporting an import using its index.
168             let exportedImport;
169             for (const i of importSection.data) {
170                 if (i.module === correspondingImport.module && i.field === correspondingImport.field) {
171                     exportedImport = i;
172                     break;
173                 }
174             }
175             if (typeof(type) === "undefined")
176                 type = exportedImport.type;
177             if (builder._checked)
178                 assert.eq(type, exportedImport.type, `Re-exporting import "${exportedImport.field}" as "${field}" has mismatching type`);
179         }
180         section.data.push({ field: field, type: type, kind: "Function", index: index });
181         return _errorHandlingProxyFor(nextBuilder);
182     };
183 };
184
185 const _normalizeMutability = (mutability) => {
186     if (mutability === "mutable")
187         return 1;
188     else if (mutability === "immutable")
189         return 0;
190     else
191         throw new Error(`mutability should be either "mutable" or "immutable", but got ${mutability}`);
192 };
193
194 const _exportGlobalContinuation = (builder, section, nextBuilder) => {
195     return (field, index) => {
196         assert.isNumber(index, `Global exports only support number indices right now`);
197         section.data.push({ field, kind: "Global", index });
198         return _errorHandlingProxyFor(nextBuilder);
199     }
200 };
201
202 const _exportMemoryContinuation = (builder, section, nextBuilder) => {
203     return (field, index) => {
204         assert.isNumber(index, `Memory exports only support number indices`);
205         section.data.push({field, kind: "Memory", index});
206         return _errorHandlingProxyFor(nextBuilder);
207     }
208 };
209
210 const _exportTableContinuation = (builder, section, nextBuilder) => {
211     return (field, index) => {
212         assert.isNumber(index, `Table exports only support number indices`);
213         section.data.push({field, kind: "Table", index});
214         return _errorHandlingProxyFor(nextBuilder);
215     }
216 };
217
218 const _importGlobalContinuation = (builder, section, nextBuilder) => {
219     return () => {
220         const globalBuilder = {
221             End: () => nextBuilder
222         };
223         for (let op of WASM.description.value_type) {
224             globalBuilder[util.toJavaScriptName(op)] = (module, field, mutability) => {
225                 assert.isString(module, `Import global module should be a string, got "${module}"`);
226                 assert.isString(field, `Import global field should be a string, got "${field}"`);
227                 assert.isString(mutability, `Import global mutability should be a string, got "${mutability}"`);
228                 section.data.push({ globalDescription: { type: op, mutability: _normalizeMutability(mutability) }, module, field, kind: "Global" });
229                 return _errorHandlingProxyFor(globalBuilder);
230             };
231         }
232         return _errorHandlingProxyFor(globalBuilder);
233     };
234 };
235
236 const _checkStackArgs = (op, param) => {
237     for (let expect of param) {
238         if (WASM.isValidType(expect)) {
239             // FIXME implement stack checks for arguments. https://bugs.webkit.org/show_bug.cgi?id=163421
240         } else {
241             // Handle our own meta-types.
242             switch (expect) {
243             case "addr": break; // FIXME implement addr. https://bugs.webkit.org/show_bug.cgi?id=163421
244             case "any": break; // FIXME implement any. https://bugs.webkit.org/show_bug.cgi?id=163421
245             case "bool": break; // FIXME implement bool. https://bugs.webkit.org/show_bug.cgi?id=163421
246             case "call": break; // FIXME implement call stack argument checks based on function signature. https://bugs.webkit.org/show_bug.cgi?id=163421
247             case "global": break; // FIXME implement global. https://bugs.webkit.org/show_bug.cgi?id=163421
248             case "local": break; // FIXME implement local. https://bugs.webkit.org/show_bug.cgi?id=163421
249             case "prev": break; // FIXME implement prev, checking for whetever the previous value was. https://bugs.webkit.org/show_bug.cgi?id=163421
250             case "size": break; // FIXME implement size. https://bugs.webkit.org/show_bug.cgi?id=163421
251             default: throw new Error(`Implementation problem: unhandled meta-type "${expect}" on "${op}"`);
252             }
253         }
254     }
255 }
256
257 const _checkStackReturn = (op, ret) => {
258     for (let expect of ret) {
259         if (WASM.isValidType(expect)) {
260             // FIXME implement stack checks for return. https://bugs.webkit.org/show_bug.cgi?id=163421
261         } else {
262             // Handle our own meta-types.
263             switch (expect) {
264             case "any": break;
265             case "bool": break; // FIXME implement bool. https://bugs.webkit.org/show_bug.cgi?id=163421
266             case "call": break; // FIXME implement call stack return check based on function signature. https://bugs.webkit.org/show_bug.cgi?id=163421
267             case "control": break; // FIXME implement control. https://bugs.webkit.org/show_bug.cgi?id=163421
268             case "global": break; // FIXME implement global. https://bugs.webkit.org/show_bug.cgi?id=163421
269             case "local": break; // FIXME implement local. https://bugs.webkit.org/show_bug.cgi?id=163421
270             case "prev": break; // FIXME implement prev, checking for whetever the parameter type was. https://bugs.webkit.org/show_bug.cgi?id=163421
271             case "size": break; // FIXME implement size. https://bugs.webkit.org/show_bug.cgi?id=163421
272             default: throw new Error(`Implementation problem: unhandled meta-type "${expect}" on "${op}"`);
273             }
274         }
275     }
276 };
277
278 const _checkImms = (op, imms, expectedImms, ret) => {
279     const minExpectedImms = expectedImms.filter(i => i.type.slice(-1) !== '*').length;
280     if (minExpectedImms !== expectedImms.length)
281         assert.ge(imms.length, minExpectedImms, `"${op}" expects at least ${minExpectedImms} immediate${minExpectedImms !== 1 ? 's' : ''}, got ${imms.length}`);
282     else
283          assert.eq(imms.length, minExpectedImms, `"${op}" expects exactly ${minExpectedImms} immediate${minExpectedImms !== 1 ? 's' : ''}, got ${imms.length}`);
284     for (let idx = 0; idx !== expectedImms.length; ++idx) {
285         const got = imms[idx];
286         const expect = expectedImms[idx];
287         switch (expect.name) {
288         case "function_index":
289             assert.truthy(_isValidValue(got, "i32") && got >= 0, `Invalid value on ${op}: got "${got}", expected non-negative i32`);
290             // FIXME check function indices. https://bugs.webkit.org/show_bug.cgi?id=163421
291             break;
292         case "local_index": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421
293         case "global_index": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421
294         case "type_index": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421
295         case "value":
296             assert.truthy(_isValidValue(got, ret[0]), `Invalid value on ${op}: got "${got}", expected ${ret[0]}`);
297             break;
298         case "flags": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421
299         case "offset": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421
300             // Control:
301         case "default_target": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421
302         case "relative_depth": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421
303         case "sig":
304             assert.truthy(WASM.isValidBlockType(imms[idx]), `Invalid block type on ${op}: "${imms[idx]}"`);
305             break;
306         case "target_count": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421
307         case "target_table": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421
308         case "reserved": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421
309         case "table_index": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421
310         default: throw new Error(`Implementation problem: unhandled immediate "${expect.name}" on "${op}"`);
311         }
312     }
313 };
314
315 const _createFunctionBuilder = (func, builder, previousBuilder) => {
316     let functionBuilder = {};
317     for (const op in WASM.description.opcode) {
318         const name = util.toJavaScriptName(op);
319         const value = WASM.description.opcode[op].value;
320         const ret = WASM.description.opcode[op]["return"];
321         const param = WASM.description.opcode[op].parameter;
322         const imm = WASM.description.opcode[op].immediate;
323
324         const checkStackArgs = builder._checked ? _checkStackArgs : () => {};
325         const checkStackReturn = builder._checked ? _checkStackReturn : () => {};
326         const checkImms = builder._checked ? _checkImms : () => {};
327
328         functionBuilder[name] = (...args) => {
329             let nextBuilder;
330             switch (name) {
331             default:
332                 nextBuilder = functionBuilder;
333                 break;
334             case "End":
335                 nextBuilder = previousBuilder;
336                 break;
337             case "Block":
338             case "Loop":
339             case "If":
340                 nextBuilder = _createFunctionBuilder(func, builder, functionBuilder);
341                 break;
342             }
343
344             // Passing a function as the argument is a way to nest blocks lexically.
345             const continuation = args[args.length - 1];
346             const hasContinuation = typeof(continuation) === "function";
347             const imms = hasContinuation ? args.slice(0, -1) : args; // FIXME: allow passing in stack values, as-if code were a stack machine. Just check for a builder to this function, and drop. https://bugs.webkit.org/show_bug.cgi?id=163422
348             checkImms(op, imms, imm, ret);
349             checkStackArgs(op, param);
350             checkStackReturn(op, ret);
351             const stackArgs = []; // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706
352             func.code.push({ name: op, value: value, arguments: stackArgs, immediates: imms });
353             if (hasContinuation)
354                 return _errorHandlingProxyFor(continuation(nextBuilder).End());
355             return _errorHandlingProxyFor(nextBuilder);
356         };
357     };
358
359     return _errorHandlingProxyFor(functionBuilder);
360 }
361
362 const _createFunction = (section, builder, previousBuilder) => {
363     return (...args) => {
364         const nameOffset = (typeof(args[0]) === "string") >>> 0;
365         const functionName = nameOffset ? args[0] : undefined;
366         let signature = args[0 + nameOffset];
367         const locals = args[1 + nameOffset] === undefined ? [] : args[1 + nameOffset];
368         for (const local of locals)
369             assert.truthy(WASM.isValidValueType(local), `Type of local: ${local} needs to be a valid value type`);
370
371         if (typeof(signature) === "undefined")
372             signature = { params: [] };
373
374         let type;
375         let params;
376         if (typeof signature === "object") {
377             assert.hasObjectProperty(signature, "params", `Expect function signature to be an object with a "params" field, got "${signature}"`);
378             let ret;
379             ([params, ret] = _normalizeFunctionSignature(signature.params, signature.ret));
380             signature = {params, ret};
381             type = _maybeRegisterType(builder, signature);
382         } else {
383             assert.truthy(typeof signature === "number");
384             const typeSection = builder._getSection("Type");
385             assert.truthy(!!typeSection);
386             // FIXME: we should allow indices that exceed this to be able to
387             // test JSCs validator is correct. https://bugs.webkit.org/show_bug.cgi?id=165786
388             assert.truthy(signature < typeSection.data.length);
389             type = signature;
390             signature = typeSection.data[signature];
391             assert.hasObjectProperty(signature, "params", `Expect function signature to be an object with a "params" field, got "${signature}"`);
392             params = signature.params;
393         }
394
395         const func = {
396             name: functionName,
397             type,
398             signature: signature,
399             locals: params.concat(locals), // Parameters are the first locals.
400             parameterCount: params.length,
401             code: []
402         };
403
404         const functionSection = builder._getSection("Function");
405         if (functionSection)
406             functionSection.data.push(func.type);
407
408         section.data.push(func);
409         builder._registerFunctionToIndexSpace(functionName);
410
411         return _createFunctionBuilder(func, builder, previousBuilder);
412     }
413 };
414
415 export default class Builder {
416     constructor() {
417         this.setChecked(true);
418         let preamble = {};
419         for (const p of WASM.description.preamble)
420             preamble[p.name] = p.value;
421         this.setPreamble(preamble);
422         this._sections = [];
423         this._functionIndexSpace = new Map();
424         this._functionIndexSpaceCount = 0;
425         this._registerSectionBuilders();
426     }
427     setChecked(checked) {
428         this._checked = checked;
429         return this;
430     }
431     setPreamble(p) {
432         this._preamble = Object.assign(this._preamble || {}, p);
433         return this;
434     }
435     _functionIndexSpaceKeyHash(obj) {
436         // We don't need a full hash, just something that has a defined order for Map. Objects we insert aren't nested.
437         if (typeof(obj) !== 'object')
438             return obj;
439         const keys = Object.keys(obj).sort();
440         let entries = [];
441         for (let k in keys)
442             entries.push([k, obj[k]]);
443         return JSON.stringify(entries);
444     }
445     _registerFunctionToIndexSpace(name) {
446         if (typeof(name) === "undefined") {
447             // Registering a nameless function still adds it to the function index space. Register it as something that can't normally be registered.
448             name = {};
449         }
450         const value = this._functionIndexSpaceCount++;
451         // Collisions are fine: we'll simply count the function and forget the previous one.
452         this._functionIndexSpace.set(this._functionIndexSpaceKeyHash(name), value);
453         // Map it both ways, the number space is distinct from the name space.
454         this._functionIndexSpace.set(this._functionIndexSpaceKeyHash(value), name);
455     }
456     _getFunctionFromIndexSpace(name) {
457         return this._functionIndexSpace.get(this._functionIndexSpaceKeyHash(name));
458     }
459     _registerSectionBuilders() {
460         for (const section in WASM.description.section) {
461             switch (section) {
462             case "Type":
463                 this[section] = function() {
464                     const s = this._addSection(section);
465                     const builder = this;
466                     const typeBuilder = {
467                         End: () => builder,
468                         Func: (params, ret) => {
469                             [params, ret] = _normalizeFunctionSignature(params, ret);
470                             s.data.push({ params: params, ret: ret });
471                             return _errorHandlingProxyFor(typeBuilder);
472                         },
473                     };
474                     return _errorHandlingProxyFor(typeBuilder);
475                 };
476                 break;
477
478             case "Import":
479                 this[section] = function() {
480                     const s = this._addSection(section);
481                     const importBuilder = {
482                         End: () => this,
483                     };
484                     importBuilder.Global = _importGlobalContinuation(this, s, importBuilder);
485                     importBuilder.Function = _importFunctionContinuation(this, s, importBuilder);
486                     importBuilder.Memory = _importMemoryContinuation(this, s, importBuilder);
487                     importBuilder.Table = _importTableContinuation(this, s, importBuilder);
488                     return _errorHandlingProxyFor(importBuilder);
489                 };
490                 break;
491
492             case "Function":
493                 this[section] = function() {
494                     const s = this._addSection(section);
495                     const functionBuilder = {
496                         End: () => this
497                         // FIXME: add ability to add this with whatever.
498                     };
499                     return _errorHandlingProxyFor(functionBuilder);
500                 };
501                 break;
502
503             case "Table":
504                 this[section] = function() {
505                     const s = this._addSection(section);
506                     const tableBuilder = {
507                         End: () => this,
508                         Table: ({initial, maximum, element}) => {
509                             s.data.push({tableDescription: {initial, maximum, element}});
510                             return _errorHandlingProxyFor(tableBuilder);
511                         }
512                     };
513                     return _errorHandlingProxyFor(tableBuilder);
514                 };
515                 break;
516
517             case "Memory":
518                 this[section] = function() {
519                     const s = this._addSection(section);
520                     const memoryBuilder = {
521                         End: () => this,
522                         InitialMaxPages: (initial, maximum) => {
523                             s.data.push({ initial, maximum });
524                             return _errorHandlingProxyFor(memoryBuilder);
525                         }
526                     };
527                     return _errorHandlingProxyFor(memoryBuilder);
528                 };
529                 break;
530
531             case "Global":
532                 this[section] = function() {
533                     const s = this._addSection(section);
534                     const globalBuilder = {
535                         End: () => this,
536                         GetGlobal: (type, initValue, mutability) => {
537                             s.data.push({ type, op: "get_global", mutability: _normalizeMutability(mutability), initValue });
538                             return _errorHandlingProxyFor(globalBuilder);
539                         },
540                         RefFunc: (type, initValue, mutability) => {
541                             s.data.push({ type, op: "ref.func", mutability: _normalizeMutability(mutability), initValue });
542                             return _errorHandlingProxyFor(globalBuilder);
543                         },
544                         RefNull: (type, mutability) => {
545                             s.data.push({ type, op: "ref.null", mutability: _normalizeMutability(mutability) });
546                             return _errorHandlingProxyFor(globalBuilder);
547                         }
548                     };
549                     for (let op of WASM.description.value_type) {
550                         globalBuilder[util.toJavaScriptName(op)] = (initValue, mutability) => {
551                             s.data.push({ type: op, op: op + ".const", mutability: _normalizeMutability(mutability), initValue });
552                             return _errorHandlingProxyFor(globalBuilder);
553                         };
554                     }
555                     return _errorHandlingProxyFor(globalBuilder);
556                 };
557                 break;
558
559             case "Export":
560                 this[section] = function() {
561                     const s = this._addSection(section);
562                     const exportBuilder = {
563                         End: () => this,
564                     };
565                     exportBuilder.Global = _exportGlobalContinuation(this, s, exportBuilder);
566                     exportBuilder.Function = _exportFunctionContinuation(this, s, exportBuilder);
567                     exportBuilder.Memory = _exportMemoryContinuation(this, s, exportBuilder);
568                     exportBuilder.Table = _exportTableContinuation(this, s, exportBuilder);
569                     return _errorHandlingProxyFor(exportBuilder);
570                 };
571                 break;
572
573             case "Start":
574                 this[section] = function(functionIndexOrName) {
575                     const s = this._addSection(section);
576                     const startBuilder = {
577                         End: () => this,
578                     };
579                     if (typeof(functionIndexOrName) !== "number" && typeof(functionIndexOrName) !== "string")
580                         throw new Error(`Start section's function index  must either be a number or a string`);
581                     s.data.push(functionIndexOrName);
582                     return _errorHandlingProxyFor(startBuilder);
583                 };
584                 break;
585
586             case "Element":
587                 this[section] = function(...args) {
588                     if (args.length !== 0 && this._checked)
589                         throw new Error("You're doing it wrong. This element does not take arguments. You must chain the call with another Element()");
590
591                     const s = this._addSection(section);
592                     const elementBuilder = {
593                         End: () => this,
594                         Element: ({tableIndex = 0, offset, functionIndices}) => {
595                             s.data.push({tableIndex, offset, functionIndices});
596                             return _errorHandlingProxyFor(elementBuilder);
597                         }
598                     };
599
600                     return _errorHandlingProxyFor(elementBuilder);
601                 };
602                 break;
603
604             case "Code":
605                 this[section] = function() {
606                     const s = this._addSection(section);
607                     const builder = this;
608                     const codeBuilder =  {
609                         End: () => {
610                             // We now have enough information to remap the export section's "type" and "index" according to the Code section we are currently ending.
611                             const typeSection = builder._getSection("Type");
612                             const importSection = builder._getSection("Import");
613                             const exportSection = builder._getSection("Export");
614                             const startSection = builder._getSection("Start");
615                             const codeSection = s;
616                             if (exportSection) {
617                                 for (const e of exportSection.data) {
618                                     if (e.kind !== "Function" || typeof(e.type) !== "undefined")
619                                         continue;
620                                     switch (typeof(e.index)) {
621                                     default: throw new Error(`Unexpected export index "${e.index}"`);
622                                     case "string": {
623                                         const index = builder._getFunctionFromIndexSpace(e.index);
624                                         assert.isNumber(index, `Export section contains undefined function "${e.index}"`);
625                                         e.index = index;
626                                     } // Fallthrough.
627                                     case "number": {
628                                         const index = builder._getFunctionFromIndexSpace(e.index);
629                                         if (builder._checked)
630                                             assert.isNotUndef(index, `Export "${e.field}" does not correspond to a defined value in the function index space`);
631                                     } break;
632                                     case "undefined":
633                                         throw new Error(`Unimplemented: Function().End() with undefined export index`); // FIXME
634                                     }
635                                     if (typeof(e.type) === "undefined") {
636                                         // This must be a function export from the Code section (re-exports were handled earlier).
637                                         let functionIndexSpaceOffset = 0;
638                                         if (importSection) {
639                                             for (const {kind} of importSection.data) {
640                                                 if (kind === "Function")
641                                                     ++functionIndexSpaceOffset;
642                                             }
643                                         }
644                                         const functionIndex = e.index - functionIndexSpaceOffset;
645                                         e.type = codeSection.data[functionIndex].type;
646                                     }
647                                 }
648                             }
649                             if (startSection) {
650                                 const start = startSection.data[0];
651                                 let mapped = builder._getFunctionFromIndexSpace(start);
652                                 if (!builder._checked) {
653                                     if (typeof(mapped) === "undefined")
654                                         mapped = start; // In unchecked mode, simply use what was provided if it's nonsensical.
655                                     assert.isA(start, "number"); // It can't be too nonsensical, otherwise we can't create a binary.
656                                     startSection.data[0] = start;
657                                 } else {
658                                     if (typeof(mapped) === "undefined")
659                                         throw new Error(`Start section refers to non-existant function '${start}'`);
660                                     if (typeof(start) === "string" || typeof(start) === "object")
661                                         startSection.data[0] = mapped;
662                                     // FIXME in checked mode, test that the type is acceptable for start function. We probably want _registerFunctionToIndexSpace to also register types per index. https://bugs.webkit.org/show_bug.cgi?id=165658
663                                 }
664                             }
665                             return _errorHandlingProxyFor(builder);
666                         },
667
668                     };
669                     codeBuilder.Function = _createFunction(s, builder, codeBuilder);
670                     return _errorHandlingProxyFor(codeBuilder);
671                 };
672                 break;
673
674             case "Data":
675                 this[section] = function() {
676                     const s = this._addSection(section);
677                     const dataBuilder = {
678                         End: () => this,
679                         Segment: data => {
680                             assert.isArray(data);
681                             for (const datum of data) {
682                                 assert.isNumber(datum);
683                                 assert.ge(datum, 0);
684                                 assert.le(datum, 0xff);
685                             }
686                             s.data.push({ data: data, index: 0, offset: 0 });
687                             let thisSegment = s.data[s.data.length - 1];
688                             const segmentBuilder = {
689                                 End: () => dataBuilder,
690                                 Index: index => {
691                                     assert.eq(index, 0); // Linear memory index must be zero in MVP.
692                                     thisSegment.index = index;
693                                     return _errorHandlingProxyFor(segmentBuilder);
694                                 },
695                                 Offset: offset => {
696                                     // FIXME allow complex init_expr here. https://bugs.webkit.org/show_bug.cgi?id=165700
697                                     assert.isNumber(offset);
698                                     thisSegment.offset = offset;
699                                     return _errorHandlingProxyFor(segmentBuilder);
700                                 },
701                             };
702                             return _errorHandlingProxyFor(segmentBuilder);
703                         },
704                     };
705                     return _errorHandlingProxyFor(dataBuilder);
706                 };
707                 break;
708
709             default:
710                 this[section] = () => { throw new Error(`Unknown section type "${section}"`); };
711                 break;
712             }
713         }
714
715         this.Unknown = function(name) {
716             const s = this._addSection(name);
717             const builder = this;
718             const unknownBuilder =  {
719                 End: () => builder,
720                 Byte: b => {
721                     assert.eq(b & 0xFF, b, `Unknown section expected byte, got: "${b}"`);
722                     s.data.push(b);
723                     return _errorHandlingProxyFor(unknownBuilder);
724                 }
725             };
726             return _errorHandlingProxyFor(unknownBuilder);
727         };
728     }
729     _addSection(nameOrNumber, extraObject) {
730         const name = typeof(nameOrNumber) === "string" ? nameOrNumber : "";
731         const number = typeof(nameOrNumber) === "number" ? nameOrNumber : (WASM.description.section[name] ? WASM.description.section[name].value : _unknownSectionId);
732         if (this._checked) {
733             // Check uniqueness.
734             for (const s of this._sections)
735                 if (number !== _unknownSectionId)
736                     assert.falsy(s.name === name && s.id === number, `Cannot have two sections with the same name "${name}" and ID ${number}`);
737             // Check ordering.
738             if ((number !== _unknownSectionId) && (this._sections.length !== 0)) {
739                 for (let i = this._sections.length - 1; i >= 0; --i) {
740                     if (this._sections[i].id === _unknownSectionId)
741                         continue;
742                     assert.le(this._sections[i].id, number, `Bad section ordering: "${this._sections[i].name}" cannot precede "${name}"`);
743                     break;
744                 }
745             }
746         }
747         const s = Object.assign({ name: name, id: number, data: [] }, extraObject || {});
748         this._sections.push(s);
749         return s;
750     }
751     _getSection(nameOrNumber) {
752         switch (typeof(nameOrNumber)) {
753         default: throw new Error(`Implementation problem: can not get section "${nameOrNumber}"`);
754         case "string":
755             for (const s of this._sections)
756                 if (s.name === nameOrNumber)
757                     return s;
758             return undefined;
759         case "number":
760             for (const s of this._sections)
761                 if (s.id === nameOrNumber)
762                     return s;
763             return undefined;
764         }
765     }
766     optimize() {
767         // FIXME Add more optimizations. https://bugs.webkit.org/show_bug.cgi?id=163424
768         return this;
769     }
770     json() {
771         const obj = {
772             preamble: this._preamble,
773             section: this._sections
774         };
775         // JSON.stringify serializes -0.0 as 0.0.
776         const replacer = (key, value) => {
777             if (value === 0.0 && 1.0 / value === -Infinity)
778                 return "NEGATIVE_ZERO";
779             return value;
780         };
781         return JSON.stringify(obj, replacer);
782     }
783     AsmJS() {
784         "use asm"; // For speed.
785         // FIXME Create an asm.js equivalent string which can be eval'd. https://bugs.webkit.org/show_bug.cgi?id=163425
786         throw new Error("asm.js not implemented yet");
787     }
788     WebAssembly() { return BuildWebAssembly.Binary(this._preamble, this._sections); }
789 };