056d72de6fa51d511ffba185f1897d8701856de9
[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 LowLevelBinary from 'LowLevelBinary.js';
27 import * as WASM from 'WASM.js';
28
29 const _toJavaScriptName = name => {
30     const camelCase = name.replace(/([^a-z0-9].)/g, c => c[1].toUpperCase());
31     const CamelCase = camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
32     return CamelCase;
33 };
34
35 const _isValidValue = (value, type) => {
36     switch (type) {
37     case "i32": return ((value & 0xFFFFFFFF) >>> 0) === value;
38     case "i64": throw new Error(`Unimplemented: value check for ${type}`); // FIXME https://bugs.webkit.org/show_bug.cgi?id=163420 64-bit values
39     case "f32": return typeof(value) === "number" && isFinite(value);
40     case "f64": return typeof(value) === "number" && isFinite(value);
41     default: throw new Error(`Implementation problem: unknown type ${type}`);
42     }
43 };
44 const _unknownSectionId = 0;
45
46 const _BuildWebAssemblyBinary = (preamble, sections) => {
47     let wasmBin = new LowLevelBinary();
48     const put = (bin, type, value) => bin[type](value);
49     for (const p of WASM.description.preamble)
50         put(wasmBin, p.type, preamble[p.name]);
51     for (const section of sections) {
52         put(wasmBin, WASM.sectionEncodingType, section.id);
53         let sectionBin = wasmBin.newPatchable("varuint");
54         switch (section.name) {
55         case "Type": throw new Error(`Unimplemented: section type "${section.name}"`);
56         case "Import": throw new Error(`Unimplemented: section type "${section.name}"`);
57         case "Function": throw new Error(`Unimplemented: section type "${section.name}"`);
58         case "Table": throw new Error(`Unimplemented: section type "${section.name}"`);
59         case "Memory": throw new Error(`Unimplemented: section type "${section.name}"`);
60         case "Global": throw new Error(`Unimplemented: section type "${section.name}"`);
61         case "Export": throw new Error(`Unimplemented: section type "${section.name}"`);
62         case "Start": throw new Error(`Unimplemented: section type "${section.name}"`);
63         case "Element": throw new Error(`Unimplemented: section type "${section.name}"`);
64         case "Code":
65             const numberOfFunctionBodies = section.data.length;
66             put(sectionBin, "varuint", numberOfFunctionBodies);
67             for (const func of section.data) {
68                 let funcBin = sectionBin.newPatchable("varuint");
69                 const localCount = func.locals.length;
70                 put(funcBin, "varuint", localCount);
71                 if (localCount !== 0) throw new Error(`Unimplemented: locals`); // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706
72                 for (const op of func.code) {
73                     put(funcBin, "uint8", op.value);
74                     if (op.arguments.length !== 0) throw new Error(`Unimplemented: arguments`); // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706
75                     if (op.immediates.length !== 0) throw new Error(`Unimplemented: immediates`); // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706
76                 }
77                 funcBin.apply();
78             }
79             break;
80         case "Data": throw new Error(`Unimplemented: section type "${section.name}"`);
81         default:
82             if (section.id !== _unknownSectionId) throw new Error(`Unknown section "${section.name}" with number ${section.id}`);
83             put(sectionBin, "string", section.name);
84             for (const byte of section.data)
85                 put(sectionBin, "uint8", byte);
86             break;
87         }
88         sectionBin.apply();
89     }
90     return wasmBin;
91 };
92
93 export default class Builder {
94     constructor() {
95         this.setChecked(true);
96         let preamble = {};
97         for (const p of WASM.description.preamble)
98             preamble[p.name] = p.value;
99         this.setPreamble(preamble);
100         this._sections = [];
101         this._registerSectionBuilders();
102     }
103     setChecked(checked) {
104         this._checked = checked;
105         return this;
106     }
107     setPreamble(p) {
108         this._preamble = Object.assign(this._preamble || {}, p);
109         return this;
110     }
111     _registerSectionBuilders() {
112         for (const section in WASM.description.section) {
113             switch (section) {
114             case "Code":
115                 this[section] = function() {
116                     const s = this._addSection(section);
117                     const builder = this;
118                     const codeBuilder =  {
119                         End: () => builder,
120                         Function: parameters => {
121                             parameters = parameters || [];
122                             const invalidParameterTypes = parameters.filter(p => !WASM.isValidValueType(p));
123                             if (invalidParameterTypes.length !== 0) throw new Error(`Function declared with parameters [${parameters}], invalid: [${invalidParameterTypes}]`);
124                             const func = {
125                                 locals: parameters, // Parameters are the first locals.
126                                 parameterCount: parameters.length,
127                                 code: []
128                             };
129                             s.data.push(func);
130                             let functionBuilder = {};
131                             for (const op in WASM.description.opcode) {
132                                 const name = _toJavaScriptName(op);
133                                 const value = WASM.description.opcode[op].value;
134                                 const ret = WASM.description.opcode[op]["return"];
135                                 const param = WASM.description.opcode[op].parameter;
136                                 const imm = WASM.description.opcode[op].immediate;
137                                 const checkStackArgs = builder._checked ? op => {
138                                     for (let expect of param) {
139                                         if (WASM.isValidValueType(expect)) {
140                                             // FIXME implement stack checks for arguments. https://bugs.webkit.org/show_bug.cgi?id=163421
141                                         } else {
142                                             // Handle our own meta-types.
143                                             switch (expect) {
144                                             case "addr": break; // FIXME implement addr. https://bugs.webkit.org/show_bug.cgi?id=163421
145                                             case "any": break; // FIXME implement any. https://bugs.webkit.org/show_bug.cgi?id=163421
146                                             case "bool": break; // FIXME implement bool. https://bugs.webkit.org/show_bug.cgi?id=163421
147                                             case "call": break; // FIXME implement call stack argument checks based on function signature. https://bugs.webkit.org/show_bug.cgi?id=163421
148                                             case "global": break; // FIXME implement global. https://bugs.webkit.org/show_bug.cgi?id=163421
149                                             case "local": break; // FIXME implement local. https://bugs.webkit.org/show_bug.cgi?id=163421
150                                             case "prev": break; // FIXME implement prev, checking for whetever the previous value was. https://bugs.webkit.org/show_bug.cgi?id=163421
151                                             case "size": break; // FIXME implement size. https://bugs.webkit.org/show_bug.cgi?id=163421
152                                             default: throw new Error(`Implementation problem: unhandled meta-type "${expect}" on "${op}"`);
153                                             }
154                                         }
155                                     }
156                                 } : () => {};
157                                 const checkStackReturn = builder._checked ? op => {
158                                     for (let expect of ret) {
159                                         if (WASM.isValidValueType(expect)) {
160                                             // FIXME implement stack checks for return. https://bugs.webkit.org/show_bug.cgi?id=163421
161                                         } else {
162                                             // Handle our own meta-types.
163                                             switch (expect) {
164                                             case "bool": break; // FIXME implement bool. https://bugs.webkit.org/show_bug.cgi?id=163421
165                                             case "call": break; // FIXME implement call stack return check based on function signature. https://bugs.webkit.org/show_bug.cgi?id=163421
166                                             case "control": break; // FIXME implement control. https://bugs.webkit.org/show_bug.cgi?id=163421
167                                             case "global": break; // FIXME implement global. https://bugs.webkit.org/show_bug.cgi?id=163421
168                                             case "local": break; // FIXME implement local. https://bugs.webkit.org/show_bug.cgi?id=163421
169                                             case "prev": break; // FIXME implement prev, checking for whetever the parameter type was. https://bugs.webkit.org/show_bug.cgi?id=163421
170                                             case "size": break; // FIXME implement size. https://bugs.webkit.org/show_bug.cgi?id=163421
171                                             default: throw new Error(`Implementation problem: unhandled meta-type "${expect}" on "${op}"`);
172                                             }
173                                         }
174                                     }
175                                 } : () => {};
176                                 const checkImms = builder._checked ? (op, imms) => {
177                                     if (imms.length != imm.length) throw new Error(`"${op}" expects ${imm.length} immediates, got ${imms.length}`);
178                                     for (let idx = 0; idx !== imm.length; ++idx) {
179                                         const got = imms[idx];
180                                         const expect = imm[idx];
181                                         switch (expect.name) {
182                                         case "function_index":
183                                             if (!_isValidValue(got, "i32")) throw new Error(`Invalid value on ${op}: got "${got}", expected i32`);
184                                             // FIXME check function indices. https://bugs.webkit.org/show_bug.cgi?id=163421
185                                             break;
186                                         case "local_index": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
187                                         case "global_index": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
188                                         case "type_index": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
189                                         case "value": if (!_isValidValue(got, ret[0])) throw new Error(`Invalid value on ${op}: got "${got}", expected ${ret[0]}`); break;
190                                         case "flags": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
191                                         case "offset": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
192                                         // Control:
193                                         case "default_target": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
194                                         case "relative_depth": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
195                                         case "sig": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
196                                         case "target_count": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
197                                         case "target_table": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
198                                         default: throw new Error(`Implementation problem: unhandled immediate "${expect.name}" on "${op}"`);
199                                         }
200                                     }
201                                 } : () => {};
202                                 const nextBuilder = name === "End" ? codeBuilder : functionBuilder;
203                                 functionBuilder[name] = (...args) => {
204                                     const imms = 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
205                                     checkImms(op, imms);
206                                     checkStackArgs(op);
207                                     checkStackReturn(op);
208                                     const stackArgs = []; // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706
209                                     func.code.push({ name: op, value: value, arguments: stackArgs, immediates: imms });
210                                     return nextBuilder;
211                                 };
212                             }
213                             return functionBuilder;
214                         }
215                     };
216                     return codeBuilder;
217                 };
218                 break;
219             default:
220                 this[section] = () => { throw new Error(`Unimplemented: section type "${section}"`); };
221                 break;
222             }
223         }
224         this.Unknown = function(name) {
225             const s = this._addSection(name);
226             const builder = this;
227             const unknownBuilder =  {
228                 End: () => builder,
229                 Byte: b => { if ((b & 0xFF) !== b) throw new RangeError(`Unknown section expected byte, got: "${b}"`); s.data.push(b); return unknownBuilder; }
230             };
231             return unknownBuilder;
232         };
233     }
234     _addSection(nameOrNumber, extraObject) {
235         const name = typeof(nameOrNumber) === "string" ? nameOrNumber : "";
236         const number = typeof(nameOrNumber) === "number" ? nameOrNumber : (WASM.description.section[name] ? WASM.description.section[name].value : _unknownSectionId);
237         const s = Object.assign({ name: name, id: number, data: [] }, extraObject || {});
238         this._sections.push(s);
239         return s;
240     }
241     optimize() {
242         // FIXME Add more optimizations. https://bugs.webkit.org/show_bug.cgi?id=163424
243         return this;
244     }
245     json() {
246         const obj = {
247             preamble: this._preamble,
248             section: this._sections
249         };
250         return JSON.stringify(obj);
251     }
252     AsmJS() {
253         "use asm"; // For speed.
254         // FIXME Create an asm.js equivalent string which can be eval'd. https://bugs.webkit.org/show_bug.cgi?id=163425
255         throw new Error("asm.js not implemented yet");
256     }
257     WebAssembly() { return _BuildWebAssemblyBinary(this._preamble, this._sections); }
258 };