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