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