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