wasm-module-builder.js 17.2 KB
Newer Older
1 2 3 4
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6 7 8 9 10
// Used for encoding f32 and double constants to bits.
let __buffer = new ArrayBuffer(8);
let byte_view = new Int8Array(__buffer);
let f32_view = new Float32Array(__buffer);
let f64_view = new Float64Array(__buffer);

rossberg's avatar
rossberg committed
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
class Binary extends Array {
  emit_u8(val) {
    this.push(val);
  }

  emit_u16(val) {
    this.push(val & 0xff);
    this.push((val >> 8) & 0xff);
  }

  emit_u32(val) {
    this.push(val & 0xff);
    this.push((val >> 8) & 0xff);
    this.push((val >> 16) & 0xff);
    this.push((val >> 24) & 0xff);
  }

28
  emit_u32v(val) {
rossberg's avatar
rossberg committed
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
    while (true) {
      let v = val & 0xff;
      val = val >>> 7;
      if (val == 0) {
        this.push(v);
        break;
      }
      this.push(v | 0x80);
    }
  }

  emit_bytes(data) {
    for (let i = 0; i < data.length; i++) {
      this.push(data[i] & 0xff);
    }
  }

  emit_string(string) {
    // When testing illegal names, we pass a byte array directly.
    if (string instanceof Array) {
49
      this.emit_u32v(string.length);
rossberg's avatar
rossberg committed
50 51 52 53 54 55 56
      this.emit_bytes(string);
      return;
    }

    // This is the hacky way to convert a JavaScript string to a UTF8 encoded
    // string only containing single-byte characters.
    let string_utf8 = unescape(encodeURIComponent(string));
57
    this.emit_u32v(string_utf8.length);
rossberg's avatar
rossberg committed
58 59 60 61 62 63 64 65 66 67 68 69
    for (let i = 0; i < string_utf8.length; i++) {
      this.emit_u8(string_utf8.charCodeAt(i));
    }
  }

  emit_header() {
    this.push(kWasmH0, kWasmH1, kWasmH2, kWasmH3,
              kWasmV0, kWasmV1, kWasmV2, kWasmV3);
  }

  emit_section(section_code, content_generator) {
    // Emit section name.
70
    this.emit_u8(section_code);
rossberg's avatar
rossberg committed
71 72 73 74
    // Emit the section to a temporary buffer: its full length isn't know yet.
    let section = new Binary;
    content_generator(section);
    // Emit section length.
75
    this.emit_u32v(section.length);
rossberg's avatar
rossberg committed
76 77 78 79 80 81
    // Copy the temporary buffer.
    this.push(...section);
  }
}

class WasmFunctionBuilder {
82 83
  constructor(module, name, type_index) {
    this.module = module;
84
    this.name = name;
rossberg's avatar
rossberg committed
85
    this.type_index = type_index;
86
    this.body = [];
rossberg's avatar
rossberg committed
87
  }
88

rossberg's avatar
rossberg committed
89
  exportAs(name) {
90
    this.module.addExport(name, this.index);
91
    return this;
rossberg's avatar
rossberg committed
92
  }
93

rossberg's avatar
rossberg committed
94
  exportFunc() {
95
    this.exportAs(this.name);
rossberg's avatar
rossberg committed
96 97
    return this;
  }
98

rossberg's avatar
rossberg committed
99
  addBody(body) {
100
    for (let b of body) {
101 102
      if (typeof b != 'number')
        throw new Error('invalid body (entries have to be numbers): ' + body);
103
    }
104
    this.body = body.slice();
105
    // Automatically add the end for the function block to the body.
106
    this.body.push(kExprEnd);
107
    return this;
rossberg's avatar
rossberg committed
108
  }
109

110 111 112 113 114
  addBodyWithEnd(body) {
    this.body = body;
    return this;
  }

rossberg's avatar
rossberg committed
115
  addLocals(locals) {
116 117
    this.locals = locals;
    return this;
rossberg's avatar
rossberg committed
118
  }
119 120 121 122

  end() {
    return this.module;
  }
123 124
}

125 126 127 128 129 130 131 132 133
class WasmGlobalBuilder {
  constructor(module, type, mutable) {
    this.module = module;
    this.type = type;
    this.mutable = mutable;
    this.init = 0;
  }

  exportAs(name) {
134 135
    this.module.exports.push({name: name, kind: kExternalGlobal,
                              index: this.index});
136 137 138 139
    return this;
  }
}

rossberg's avatar
rossberg committed
140 141 142
class WasmModuleBuilder {
  constructor() {
    this.types = [];
143
    this.imports = [];
144
    this.exports = [];
145
    this.globals = [];
146
    this.functions = [];
147 148 149
    this.function_table = [];
    this.function_table_length = 0;
    this.function_table_inits = [];
rossberg's avatar
rossberg committed
150
    this.segments = [];
151
    this.explicit = [];
152 153
    this.num_imported_funcs = 0;
    this.num_imported_globals = 0;
154
    return this;
rossberg's avatar
rossberg committed
155
  }
156

rossberg's avatar
rossberg committed
157
  addStart(start_index) {
158
    this.start_index = start_index;
159
    return this;
rossberg's avatar
rossberg committed
160
  }
161

rossberg's avatar
rossberg committed
162
  addMemory(min, max, exp) {
163 164
    this.memory = {min: min, max: max, exp: exp};
    return this;
rossberg's avatar
rossberg committed
165
  }
166

rossberg's avatar
rossberg committed
167 168 169 170
  addExplicitSection(bytes) {
    this.explicit.push(bytes);
    return this;
  }
171

172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
  stringToBytes(name) {
    var result = new Binary();
    result.emit_u32v(name.length);
    for (var i = 0; i < name.length; i++) {
      result.emit_u8(name.charCodeAt(i));
    }
    return result;
  }

  addCustomSection(name, bytes) {
    name = this.stringToBytes(name);
    var length = new Binary();
    length.emit_u32v(name.length + bytes.length);
    this.explicit.push([0, ...length, ...name, ...bytes]);
  }

rossberg's avatar
rossberg committed
188 189 190 191 192
  addType(type) {
    // TODO: canonicalize types?
    this.types.push(type);
    return this.types.length - 1;
  }
193

194 195 196 197 198
  addGlobal(local_type, mutable) {
    let glob = new WasmGlobalBuilder(this, local_type, mutable);
    glob.index = this.globals.length + this.num_imported_globals;
    this.globals.push(glob);
    return glob;
199 200
  }

rossberg's avatar
rossberg committed
201 202
  addFunction(name, type) {
    let type_index = (typeof type) == "number" ? type : this.addType(type);
203 204
    let func = new WasmFunctionBuilder(this, name, type_index);
    func.index = this.functions.length + this.num_imported_funcs;
205 206
    this.functions.push(func);
    return func;
rossberg's avatar
rossberg committed
207
  }
208

209
  addImport(module = "", name, type) {
rossberg's avatar
rossberg committed
210
    let type_index = (typeof type) == "number" ? type : this.addType(type);
211 212 213
    this.imports.push({module: module, name: name, kind: kExternalFunction,
                       type: type_index});
    return this.num_imported_funcs++;
rossberg's avatar
rossberg committed
214
  }
215

216
  addImportedGlobal(module = "", name, type) {
217 218 219 220 221 222
    let o = {module: module, name: name, kind: kExternalGlobal, type: type,
             mutable: false}
    this.imports.push(o);
    return this.num_imported_globals++;
  }

223
  addImportedMemory(module = "", name, initial = 0, maximum) {
224 225
    let o = {module: module, name: name, kind: kExternalMemory,
             initial: initial, maximum: maximum};
226
    this.imports.push(o);
227
    return this;
228 229
  }

230
  addImportedTable(module = "", name, initial, maximum) {
231 232 233 234 235
    let o = {module: module, name: name, kind: kExternalTable, initial: initial,
             maximum: maximum};
    this.imports.push(o);
  }

236 237 238 239 240
  addExport(name, index) {
    this.exports.push({name: name, kind: kExternalFunction, index: index});
    return this;
  }

241 242 243 244 245
  addExportOfKind(name, kind, index) {
    this.exports.push({name: name, kind: kind, index: index});
    return this;
  }

246 247
  addDataSegment(addr, data, is_global = false) {
    this.segments.push({addr: addr, data: data, is_global: is_global});
rossberg's avatar
rossberg committed
248 249
    return this.segments.length - 1;
  }
250

251 252 253 254
  exportMemoryAs(name) {
    this.exports.push({name: name, kind: kExternalMemory, index: 0});
  }

255
  addFunctionTableInit(base, is_global, array, is_import = false) {
256 257
    this.function_table_inits.push({base: base, is_global: is_global,
                                    array: array});
258 259
    if (!is_global) {
      var length = base + array.length;
260
      if (length > this.function_table_length && !is_import) {
261 262
        this.function_table_length = length;
      }
263 264 265 266
    }
    return this;
  }

rossberg's avatar
rossberg committed
267
  appendToTable(array) {
268 269 270 271
    for (let n of array) {
      if (typeof n != 'number')
        throw new Error('invalid table (entries have to be numbers): ' + array);
    }
272 273 274 275 276
    return this.addFunctionTableInit(this.function_table.length, false, array);
  }

  setFunctionTableLength(length) {
    this.function_table_length = length;
277
    return this;
278 279
  }

280
  toArray(debug = false) {
rossberg's avatar
rossberg committed
281 282 283 284 285 286 287 288 289
    let binary = new Binary;
    let wasm = this;

    // Add header
    binary.emit_header();

    // Add type section
    if (wasm.types.length > 0) {
      if (debug) print("emitting types @ " + binary.length);
290
      binary.emit_section(kTypeSectionCode, section => {
291
        section.emit_u32v(wasm.types.length);
rossberg's avatar
rossberg committed
292 293
        for (let type of wasm.types) {
          section.emit_u8(kWasmFunctionTypeForm);
294
          section.emit_u32v(type.params.length);
rossberg's avatar
rossberg committed
295 296 297
          for (let param of type.params) {
            section.emit_u8(param);
          }
298
          section.emit_u32v(type.results.length);
rossberg's avatar
rossberg committed
299 300 301 302 303
          for (let result of type.results) {
            section.emit_u8(result);
          }
        }
      });
304 305 306
    }

    // Add imports section
307
    if (wasm.imports.length > 0) {
rossberg's avatar
rossberg committed
308
      if (debug) print("emitting imports @ " + binary.length);
309
      binary.emit_section(kImportSectionCode, section => {
310
        section.emit_u32v(wasm.imports.length);
rossberg's avatar
rossberg committed
311 312 313
        for (let imp of wasm.imports) {
          section.emit_string(imp.module);
          section.emit_string(imp.name || '');
314 315 316 317 318 319
          section.emit_u8(imp.kind);
          if (imp.kind == kExternalFunction) {
            section.emit_u32v(imp.type);
          } else if (imp.kind == kExternalGlobal) {
            section.emit_u32v(imp.type);
            section.emit_u8(imp.mutable);
320 321 322 323 324
          } else if (imp.kind == kExternalMemory) {
            var has_max = (typeof imp.maximum) != "undefined";
            section.emit_u8(has_max ? 1 : 0); // flags
            section.emit_u32v(imp.initial); // initial
            if (has_max) section.emit_u32v(imp.maximum); // maximum
325 326 327 328 329 330
          } else if (imp.kind == kExternalTable) {
            section.emit_u8(kWasmAnyFunctionTypeForm);
            var has_max = (typeof imp.maximum) != "undefined";
            section.emit_u8(has_max ? 1 : 0); // flags
            section.emit_u32v(imp.initial); // initial
            if (has_max) section.emit_u32v(imp.maximum); // maximum
331 332 333
          } else {
            throw new Error("unknown/unsupported import kind " + imp.kind);
          }
rossberg's avatar
rossberg committed
334 335
        }
      });
336 337
    }

338
    // Add functions declarations
339
    let num_function_names = 0;
rossberg's avatar
rossberg committed
340
    let names = false;
341
    if (wasm.functions.length > 0) {
rossberg's avatar
rossberg committed
342
      if (debug) print("emitting function decls @ " + binary.length);
343
      binary.emit_section(kFunctionSectionCode, section => {
344
        section.emit_u32v(wasm.functions.length);
rossberg's avatar
rossberg committed
345
        for (let func of wasm.functions) {
346 347 348
          if (func.name !== undefined) {
            ++num_function_names;
          }
349
          section.emit_u32v(func.type_index);
rossberg's avatar
rossberg committed
350 351
        }
      });
352 353
    }

354 355
    // Add function_table.
    if (wasm.function_table_length > 0) {
rossberg's avatar
rossberg committed
356
      if (debug) print("emitting table @ " + binary.length);
357 358 359 360
      binary.emit_section(kTableSectionCode, section => {
        section.emit_u8(1);  // one table entry
        section.emit_u8(kWasmAnyFunctionTypeForm);
        section.emit_u8(1);
361 362
        section.emit_u32v(wasm.function_table_length);
        section.emit_u32v(wasm.function_table_length);
rossberg's avatar
rossberg committed
363
      });
364 365 366
    }

    // Add memory section
367
    if (wasm.memory !== undefined) {
rossberg's avatar
rossberg committed
368
      if (debug) print("emitting memory @ " + binary.length);
369 370
      binary.emit_section(kMemorySectionCode, section => {
        section.emit_u8(1);  // one memory entry
371 372 373
        section.emit_u32v(kResizableMaximumFlag);
        section.emit_u32v(wasm.memory.min);
        section.emit_u32v(wasm.memory.max);
rossberg's avatar
rossberg committed
374
      });
375 376
    }

377 378 379 380
    // Add global section.
    if (wasm.globals.length > 0) {
      if (debug) print ("emitting globals @ " + binary.length);
      binary.emit_section(kGlobalSectionCode, section => {
381 382 383 384 385 386 387
        section.emit_u32v(wasm.globals.length);
        for (let global of wasm.globals) {
          section.emit_u8(global.type);
          section.emit_u8(global.mutable);
          if ((typeof global.init_index) == "undefined") {
            // Emit a constant initializer.
            switch (global.type) {
388
            case kWasmI32:
389
              section.emit_u8(kExprI32Const);
390
              section.emit_u32v(global.init);
391
              break;
392
            case kWasmI64:
393
              section.emit_u8(kExprI64Const);
394
              section.emit_u32v(global.init);
395
              break;
396
            case kWasmF32:
397
              section.emit_u8(kExprF32Const);
398 399 400 401 402
              f32_view[0] = global.init;
              section.emit_u8(byte_view[0]);
              section.emit_u8(byte_view[1]);
              section.emit_u8(byte_view[2]);
              section.emit_u8(byte_view[3]);
403
              break;
404
            case kWasmF64:
405 406 407 408 409 410 411 412 413 414
              section.emit_u8(kExprF64Const);
              f64_view[0] = global.init;
              section.emit_u8(byte_view[0]);
              section.emit_u8(byte_view[1]);
              section.emit_u8(byte_view[2]);
              section.emit_u8(byte_view[3]);
              section.emit_u8(byte_view[4]);
              section.emit_u8(byte_view[5]);
              section.emit_u8(byte_view[6]);
              section.emit_u8(byte_view[7]);
415
              break;
416 417 418 419 420
            }
          } else {
            // Emit a global-index initializer.
            section.emit_u8(kExprGetGlobal);
            section.emit_u32v(global.init_index);
421 422 423 424 425
          }
          section.emit_u8(kExprEnd);  // end of init expression
        }
      });
    }
426 427

    // Add export table.
428
    var mem_export = (wasm.memory !== undefined && wasm.memory.exp);
429 430
    var exports_count = wasm.exports.length + (mem_export ? 1 : 0);
    if (exports_count > 0) {
rossberg's avatar
rossberg committed
431
      if (debug) print("emitting exports @ " + binary.length);
432
      binary.emit_section(kExportSectionCode, section => {
433 434 435 436 437
        section.emit_u32v(exports_count);
        for (let exp of wasm.exports) {
          section.emit_string(exp.name);
          section.emit_u8(exp.kind);
          section.emit_u32v(exp.index);
rossberg's avatar
rossberg committed
438
        }
439 440 441 442 443
        if (mem_export) {
          section.emit_string("memory");
          section.emit_u8(kExternalMemory);
          section.emit_u8(0);
        }
rossberg's avatar
rossberg committed
444
      });
445 446 447
    }

    // Add start function section.
448
    if (wasm.start_index !== undefined) {
rossberg's avatar
rossberg committed
449
      if (debug) print("emitting start function @ " + binary.length);
450
      binary.emit_section(kStartSectionCode, section => {
451
        section.emit_u32v(wasm.start_index);
rossberg's avatar
rossberg committed
452
      });
453 454
    }

455
    // Add table elements.
456
    if (wasm.function_table_inits.length > 0) {
457 458
      if (debug) print("emitting table @ " + binary.length);
      binary.emit_section(kElementSectionCode, section => {
459 460
        var inits = wasm.function_table_inits;
        section.emit_u32v(inits.length);
461
        section.emit_u8(0); // table index
462 463 464 465 466 467 468 469 470 471 472 473 474

        for (let init of inits) {
          if (init.is_global) {
            section.emit_u8(kExprGetGlobal);
          } else {
            section.emit_u8(kExprI32Const);
          }
          section.emit_u32v(init.base);
          section.emit_u8(kExprEnd);
          section.emit_u32v(init.array.length);
          for (let index of init.array) {
            section.emit_u32v(index);
          }
475 476 477 478
        }
      });
    }

479 480
    // Add function bodies.
    if (wasm.functions.length > 0) {
rossberg's avatar
rossberg committed
481 482
      // emit function bodies
      if (debug) print("emitting code @ " + binary.length);
483
      binary.emit_section(kCodeSectionCode, section => {
484
        section.emit_u32v(wasm.functions.length);
rossberg's avatar
rossberg committed
485 486 487 488
        for (let func of wasm.functions) {
          // Function body length will be patched later.
          let local_decls = [];
          let l = func.locals;
489
          if (l !== undefined) {
rossberg's avatar
rossberg committed
490 491
            let local_decls_count = 0;
            if (l.i32_count > 0) {
492
              local_decls.push({count: l.i32_count, type: kWasmI32});
493
            }
rossberg's avatar
rossberg committed
494
            if (l.i64_count > 0) {
495
              local_decls.push({count: l.i64_count, type: kWasmI64});
rossberg's avatar
rossberg committed
496 497
            }
            if (l.f32_count > 0) {
498
              local_decls.push({count: l.f32_count, type: kWasmF32});
rossberg's avatar
rossberg committed
499 500
            }
            if (l.f64_count > 0) {
501
              local_decls.push({count: l.f64_count, type: kWasmF64});
rossberg's avatar
rossberg committed
502 503 504 505
            }
          }

          let header = new Binary;
506
          header.emit_u32v(local_decls.length);
rossberg's avatar
rossberg committed
507
          for (let decl of local_decls) {
508
            header.emit_u32v(decl.count);
rossberg's avatar
rossberg committed
509 510 511
            header.emit_u8(decl.type);
          }

512
          section.emit_u32v(header.length + func.body.length);
rossberg's avatar
rossberg committed
513 514 515 516
          section.emit_bytes(header);
          section.emit_bytes(func.body);
        }
      });
517
    }
518

519
    // Add data segments.
rossberg's avatar
rossberg committed
520 521
    if (wasm.segments.length > 0) {
      if (debug) print("emitting data segments @ " + binary.length);
522
      binary.emit_section(kDataSectionCode, section => {
523
        section.emit_u32v(wasm.segments.length);
rossberg's avatar
rossberg committed
524
        for (let seg of wasm.segments) {
525
          section.emit_u8(0);  // linear memory index 0
526 527 528 529 530 531 532 533 534
          if (seg.is_global) {
            // initializer is a global variable
            section.emit_u8(kExprGetGlobal);
            section.emit_u32v(seg.addr);
          } else {
            // initializer is a constant
            section.emit_u8(kExprI32Const);
            section.emit_u32v(seg.addr);
          }
535
          section.emit_u8(kExprEnd);
536
          section.emit_u32v(seg.data.length);
rossberg's avatar
rossberg committed
537 538 539
          section.emit_bytes(seg.data);
        }
      });
540 541
    }

542
    // Add any explicitly added sections
rossberg's avatar
rossberg committed
543 544 545
    for (let exp of wasm.explicit) {
      if (debug) print("emitting explicit @ " + binary.length);
      binary.emit_bytes(exp);
546 547
    }

548
    // Add function names.
549 550
    if (num_function_names > 0) {
      if (debug) print('emitting names @ ' + binary.length);
551
      binary.emit_section(kUnknownSectionCode, section => {
552 553 554 555 556 557 558 559 560
        section.emit_string('name');
        section.emit_section(kFunctionNamesCode, name_section => {
          name_section.emit_u32v(num_function_names);
          for (let func of wasm.functions) {
            if (func.name === undefined) continue;
            name_section.emit_u32v(func.index);
            name_section.emit_string(func.name);
          }
        });
rossberg's avatar
rossberg committed
561
      });
562 563
    }

rossberg's avatar
rossberg committed
564 565
    return binary;
  }
566

567
  toBuffer(debug = false) {
rossberg's avatar
rossberg committed
568 569 570 571 572 573 574
    let bytes = this.toArray(debug);
    let buffer = new ArrayBuffer(bytes.length);
    let view = new Uint8Array(buffer);
    for (let i = 0; i < bytes.length; i++) {
      let val = bytes[i];
      if ((typeof val) == "string") val = val.charCodeAt(0);
      view[i] = val | 0;
575 576
    }
    return buffer;
rossberg's avatar
rossberg committed
577
  }
578

mtrofin's avatar
mtrofin committed
579
  instantiate(ffi) {
rossberg's avatar
rossberg committed
580
    let module = new WebAssembly.Module(this.toBuffer());
mtrofin's avatar
mtrofin committed
581
    let instance = new WebAssembly.Instance(module, ffi);
rossberg's avatar
rossberg committed
582 583
    return instance;
  }
584
}