wasm-module-builder.cc 21.1 KB
Newer Older
1 2 3 4 5 6 7
// Copyright 2015 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.

#include "src/signature.h"

#include "src/handles.h"
8
#include "src/objects-inl.h"
9
#include "src/v8.h"
10
#include "src/zone/zone-containers.h"
11

12
#include "src/wasm/function-body-decoder.h"
13
#include "src/wasm/leb-helper.h"
14
#include "src/wasm/wasm-constants.h"
15
#include "src/wasm/wasm-module-builder.h"
16 17 18 19 20 21 22 23 24
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-opcodes.h"

#include "src/v8memory.h"

namespace v8 {
namespace internal {
namespace wasm {

25 26
namespace {

27
// Emit a section code and the size as a padded varint that can be patched
28
// later.
29
size_t EmitSection(SectionCode code, ZoneBuffer& buffer) {
30 31
  // Emit the section code.
  buffer.write_u8(code);
32

33
  // Emit a placeholder for the length.
34 35
  return buffer.reserve_u32v();
}
36

37 38 39 40
// Patch the size of a section after it's finished.
void FixupSection(ZoneBuffer& buffer, size_t start) {
  buffer.patch_u32v(start, static_cast<uint32_t>(buffer.offset() - start -
                                                 kPaddedVarInt32Size));
41
}
42

43 44
}  // namespace

45 46 47 48
WasmFunctionBuilder::WasmFunctionBuilder(WasmModuleBuilder* builder)
    : builder_(builder),
      locals_(builder->zone()),
      signature_index_(0),
49
      func_index_(static_cast<uint32_t>(builder->functions_.size())),
50
      body_(builder->zone(), 256),
51 52 53
      i32_temps_(builder->zone()),
      i64_temps_(builder->zone()),
      f32_temps_(builder->zone()),
54
      f64_temps_(builder->zone()),
55 56
      direct_calls_(builder->zone()),
      asm_offsets_(builder->zone(), 8) {}
57

58
void WasmFunctionBuilder::EmitI32V(int32_t val) { body_.write_i32v(val); }
59

60
void WasmFunctionBuilder::EmitU32V(uint32_t val) { body_.write_u32v(val); }
61

62 63 64
void WasmFunctionBuilder::SetSignature(FunctionSig* sig) {
  DCHECK(!locals_.has_sig());
  locals_.set_sig(sig);
65
  signature_index_ = builder_->AddSignature(sig);
66 67
}

68
uint32_t WasmFunctionBuilder::AddLocal(ValueType type) {
69 70
  DCHECK(locals_.has_sig());
  return locals_.AddLocals(1, type);
71 72
}

73
void WasmFunctionBuilder::EmitGetLocal(uint32_t local_index) {
74
  EmitWithU32V(kExprGetLocal, local_index);
75 76 77
}

void WasmFunctionBuilder::EmitSetLocal(uint32_t local_index) {
78
  EmitWithU32V(kExprSetLocal, local_index);
79
}
80

81
void WasmFunctionBuilder::EmitTeeLocal(uint32_t local_index) {
82
  EmitWithU32V(kExprTeeLocal, local_index);
83 84
}

85
void WasmFunctionBuilder::EmitCode(const byte* code, uint32_t code_size) {
86
  body_.write(code, code_size);
87 88
}

89
void WasmFunctionBuilder::Emit(WasmOpcode opcode) { body_.write_u8(opcode); }
90 91

void WasmFunctionBuilder::EmitWithU8(WasmOpcode opcode, const byte immediate) {
92 93
  body_.write_u8(opcode);
  body_.write_u8(immediate);
94 95
}

96 97
void WasmFunctionBuilder::EmitWithU8U8(WasmOpcode opcode, const byte imm1,
                                       const byte imm2) {
98 99 100
  body_.write_u8(opcode);
  body_.write_u8(imm1);
  body_.write_u8(imm2);
101 102
}

103 104 105
void WasmFunctionBuilder::EmitWithI32V(WasmOpcode opcode, int32_t immediate) {
  body_.write_u8(opcode);
  body_.write_i32v(immediate);
106 107
}

108 109 110
void WasmFunctionBuilder::EmitWithU32V(WasmOpcode opcode, uint32_t immediate) {
  body_.write_u8(opcode);
  body_.write_u32v(immediate);
111
}
112

113
void WasmFunctionBuilder::EmitI32Const(int32_t value) {
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
  EmitWithI32V(kExprI32Const, value);
}

void WasmFunctionBuilder::EmitI64Const(int64_t value) {
  body_.write_u8(kExprI64Const);
  body_.write_i64v(value);
}

void WasmFunctionBuilder::EmitF32Const(float value) {
  body_.write_u8(kExprF32Const);
  body_.write_f32(value);
}

void WasmFunctionBuilder::EmitF64Const(double value) {
  body_.write_u8(kExprF64Const);
  body_.write_f64(value);
130 131
}

132 133 134 135 136
void WasmFunctionBuilder::EmitDirectCallIndex(uint32_t index) {
  DirectCallIndex call;
  call.offset = body_.size();
  call.direct_index = index;
  direct_calls_.push_back(call);
137 138
  byte placeholder_bytes[kMaxVarInt32Size] = {0};
  EmitCode(placeholder_bytes, arraysize(placeholder_bytes));
139 140
}

141
void WasmFunctionBuilder::SetName(Vector<const char> name) { name_ = name; }
142

143 144
void WasmFunctionBuilder::AddAsmWasmOffset(size_t call_position,
                                           size_t to_number_position) {
145
  // We only want to emit one mapping per byte offset.
146 147 148 149 150 151 152
  DCHECK(asm_offsets_.size() == 0 || body_.size() > last_asm_byte_offset_);

  DCHECK_LE(body_.size(), kMaxUInt32);
  uint32_t byte_offset = static_cast<uint32_t>(body_.size());
  asm_offsets_.write_u32v(byte_offset - last_asm_byte_offset_);
  last_asm_byte_offset_ = byte_offset;

153 154 155
  DCHECK_GE(std::numeric_limits<uint32_t>::max(), call_position);
  uint32_t call_position_u32 = static_cast<uint32_t>(call_position);
  asm_offsets_.write_i32v(call_position_u32 - last_asm_source_position_);
156

157 158 159 160
  DCHECK_GE(std::numeric_limits<uint32_t>::max(), to_number_position);
  uint32_t to_number_position_u32 = static_cast<uint32_t>(to_number_position);
  asm_offsets_.write_i32v(to_number_position_u32 - call_position_u32);
  last_asm_source_position_ = to_number_position_u32;
161 162
}

163 164
void WasmFunctionBuilder::SetAsmFunctionStartPosition(
    size_t function_position) {
165
  DCHECK_EQ(0, asm_func_start_source_position_);
166 167
  DCHECK_GE(std::numeric_limits<uint32_t>::max(), function_position);
  uint32_t function_position_u32 = static_cast<uint32_t>(function_position);
168 169
  // Must be called before emitting any asm.js source position.
  DCHECK_EQ(0, asm_offsets_.size());
170 171
  asm_func_start_source_position_ = function_position_u32;
  last_asm_source_position_ = function_position_u32;
172 173
}

174 175 176 177 178 179 180 181 182 183
void WasmFunctionBuilder::SetCompilationHint(
    WasmCompilationHintStrategy strategy, WasmCompilationHintTier baseline,
    WasmCompilationHintTier top_tier) {
  uint8_t hint_byte = static_cast<uint8_t>(strategy) |
                      static_cast<uint8_t>(baseline) << 2 |
                      static_cast<uint8_t>(top_tier) << 4;
  DCHECK_NE(hint_byte, kNoCompilationHint);
  hint_ = hint_byte;
}

184
void WasmFunctionBuilder::DeleteCodeAfter(size_t position) {
185
  DCHECK_LE(position, body_.size());
186
  body_.Truncate(position);
187 188
}

189
void WasmFunctionBuilder::WriteSignature(ZoneBuffer& buffer) const {
190
  buffer.write_u32v(signature_index_);
191 192
}

193
void WasmFunctionBuilder::WriteBody(ZoneBuffer& buffer) const {
194 195 196 197 198 199
  size_t locals_size = locals_.Size();
  buffer.write_size(locals_size + body_.size());
  buffer.EnsureSpace(locals_size);
  byte** ptr = buffer.pos_ptr();
  locals_.Emit(*ptr);
  (*ptr) += locals_size;  // UGLY: manual bump of position pointer
200
  if (body_.size() > 0) {
201
    size_t base = buffer.offset();
202
    buffer.write(body_.begin(), body_.size());
203 204 205
    for (DirectCallIndex call : direct_calls_) {
      buffer.patch_u32v(
          base + call.offset,
206 207
          call.direct_index +
              static_cast<uint32_t>(builder_->function_imports_.size()));
208
    }
209 210 211
  }
}

212
void WasmFunctionBuilder::WriteAsmWasmOffsetTable(ZoneBuffer& buffer) const {
213
  if (asm_func_start_source_position_ == 0 && asm_offsets_.size() == 0) {
214 215 216
    buffer.write_size(0);
    return;
  }
217 218 219 220
  size_t locals_enc_size = LEBHelper::sizeof_u32v(locals_.Size());
  size_t func_start_size =
      LEBHelper::sizeof_u32v(asm_func_start_source_position_);
  buffer.write_size(asm_offsets_.size() + locals_enc_size + func_start_size);
221 222
  // Offset of the recorded byte offsets.
  DCHECK_GE(kMaxUInt32, locals_.Size());
223 224 225
  buffer.write_u32v(static_cast<uint32_t>(locals_.Size()));
  // Start position of the function.
  buffer.write_u32v(asm_func_start_source_position_);
226 227 228
  buffer.write(asm_offsets_.begin(), asm_offsets_.size());
}

229 230 231
WasmModuleBuilder::WasmModuleBuilder(Zone* zone)
    : zone_(zone),
      signatures_(zone),
232
      function_imports_(zone),
233
      function_exports_(zone),
234
      global_imports_(zone),
235 236 237 238
      functions_(zone),
      data_segments_(zone),
      indirect_functions_(zone),
      globals_(zone),
239
      signature_map_(zone),
240
      start_function_index_(-1),
241 242
      min_memory_size_(16),
      max_memory_size_(0),
243 244
      has_max_memory_size_(false),
      has_shared_memory_(false) {}
245

246
WasmFunctionBuilder* WasmModuleBuilder::AddFunction(FunctionSig* sig) {
247
  functions_.push_back(new (zone_) WasmFunctionBuilder(this));
248 249 250
  // Add the signature if one was provided here.
  if (sig) functions_.back()->SetSignature(sig);
  return functions_.back();
251 252
}

253 254 255 256 257 258 259
void WasmModuleBuilder::AddDataSegment(const byte* data, uint32_t size,
                                       uint32_t dest) {
  data_segments_.push_back({ZoneVector<byte>(zone()), dest});
  ZoneVector<byte>& vec = data_segments_.back().data;
  for (uint32_t i = 0; i < size; i++) {
    vec.push_back(data[i]);
  }
260 261
}

262
uint32_t WasmModuleBuilder::AddSignature(FunctionSig* sig) {
263 264 265 266 267 268
  auto sig_entry = signature_map_.find(*sig);
  if (sig_entry != signature_map_.end()) return sig_entry->second;
  uint32_t index = static_cast<uint32_t>(signatures_.size());
  signature_map_.emplace(*sig, index);
  signatures_.push_back(sig);
  return index;
269 270
}

271
uint32_t WasmModuleBuilder::AllocateIndirectFunctions(uint32_t count) {
272 273 274 275 276
  uint32_t index = static_cast<uint32_t>(indirect_functions_.size());
  DCHECK_GE(FLAG_wasm_max_table_size, index);
  if (count > FLAG_wasm_max_table_size - index) {
    return std::numeric_limits<uint32_t>::max();
  }
277
  indirect_functions_.resize(indirect_functions_.size() + count);
278
  return index;
279 280 281 282 283
}

void WasmModuleBuilder::SetIndirectFunction(uint32_t indirect,
                                            uint32_t direct) {
  indirect_functions_[indirect] = direct;
284 285
}

286
uint32_t WasmModuleBuilder::AddImport(Vector<const char> name,
287
                                      FunctionSig* sig) {
288
  function_imports_.push_back({name, AddSignature(sig)});
289 290 291
  return static_cast<uint32_t>(function_imports_.size() - 1);
}

292
uint32_t WasmModuleBuilder::AddGlobalImport(Vector<const char> name,
293
                                            ValueType type) {
294
  global_imports_.push_back({name, ValueTypes::ValueTypeCodeFor(type)});
295
  return static_cast<uint32_t>(global_imports_.size() - 1);
296 297
}

298 299
void WasmModuleBuilder::MarkStartFunction(WasmFunctionBuilder* function) {
  start_function_index_ = function->func_index();
300
}
301

302 303 304 305 306
void WasmModuleBuilder::AddExport(Vector<const char> name,
                                  WasmFunctionBuilder* function) {
  function_exports_.push_back({name, function->func_index()});
}

307
uint32_t WasmModuleBuilder::AddGlobal(ValueType type, bool exported,
308 309 310
                                      bool mutability,
                                      const WasmInitExpr& init) {
  globals_.push_back({type, exported, mutability, init});
311 312 313
  return static_cast<uint32_t>(globals_.size() - 1);
}

314 315 316 317
void WasmModuleBuilder::SetMinMemorySize(uint32_t value) {
  min_memory_size_ = value;
}

318 319 320 321 322
void WasmModuleBuilder::SetMaxMemorySize(uint32_t value) {
  has_max_memory_size_ = true;
  max_memory_size_ = value;
}

323 324
void WasmModuleBuilder::SetHasSharedMemory() { has_shared_memory_ = true; }

325
void WasmModuleBuilder::WriteTo(ZoneBuffer& buffer) const {
326 327 328
  // == Emit magic =============================================================
  buffer.write_u32(kWasmMagic);
  buffer.write_u32(kWasmVersion);
329

330
  // == Emit signatures ========================================================
331
  if (signatures_.size() > 0) {
332
    size_t start = EmitSection(kTypeSectionCode, buffer);
333
    buffer.write_size(signatures_.size());
334 335

    for (FunctionSig* sig : signatures_) {
336
      buffer.write_u8(kWasmFunctionTypeCode);
337
      buffer.write_size(sig->parameter_count());
338
      for (auto param : sig->parameters()) {
339
        buffer.write_u8(ValueTypes::ValueTypeCodeFor(param));
340
      }
341
      buffer.write_size(sig->return_count());
342
      for (auto ret : sig->returns()) {
343
        buffer.write_u8(ValueTypes::ValueTypeCodeFor(ret));
344
      }
345
    }
346 347 348 349
    FixupSection(buffer, start);
  }

  // == Emit imports ===========================================================
350
  if (global_imports_.size() + function_imports_.size() > 0) {
351
    size_t start = EmitSection(kImportSectionCode, buffer);
352 353
    buffer.write_size(global_imports_.size() + function_imports_.size());
    for (auto import : global_imports_) {
354 355
      buffer.write_u32v(0);              // module name (length)
      buffer.write_string(import.name);  // field name
356 357 358 359 360
      buffer.write_u8(kExternalGlobal);
      buffer.write_u8(import.type_code);
      buffer.write_u8(0);  // immutable
    }
    for (auto import : function_imports_) {
361 362
      buffer.write_u32v(0);              // module name (length)
      buffer.write_string(import.name);  // field name
363 364
      buffer.write_u8(kExternalFunction);
      buffer.write_u32v(import.sig_index);
365
    }
366
    FixupSection(buffer, start);
367 368
  }

369
  // == Emit function signatures ===============================================
370
  uint32_t num_function_names = 0;
371
  if (functions_.size() > 0) {
372
    size_t start = EmitSection(kFunctionSectionCode, buffer);
373
    buffer.write_size(functions_.size());
374
    for (auto* function : functions_) {
375
      function->WriteSignature(buffer);
376
      if (!function->name_.empty()) ++num_function_names;
377
    }
378
    FixupSection(buffer, start);
379 380
  }

381
  // == emit function table ====================================================
382
  if (indirect_functions_.size() > 0) {
383 384
    size_t start = EmitSection(kTableSectionCode, buffer);
    buffer.write_u8(1);  // table count
385
    buffer.write_u8(kLocalAnyFunc);
386
    buffer.write_u8(kHasMaximumFlag);
387
    buffer.write_size(indirect_functions_.size());
388 389
    buffer.write_size(indirect_functions_.size());
    FixupSection(buffer, start);
390 391
  }

392
  // == emit memory declaration ================================================
393
  {
394
    size_t start = EmitSection(kMemorySectionCode, buffer);
395
    buffer.write_u8(1);  // memory count
396 397 398 399 400 401 402
    if (has_shared_memory_) {
      buffer.write_u8(has_max_memory_size_ ? MemoryFlags::kSharedAndMaximum
                                           : MemoryFlags::kSharedNoMaximum);
    } else {
      buffer.write_u8(has_max_memory_size_ ? MemoryFlags::kMaximum
                                           : MemoryFlags::kNoMaximum);
    }
403 404 405 406
    buffer.write_u32v(min_memory_size_);
    if (has_max_memory_size_) {
      buffer.write_u32v(max_memory_size_);
    }
407 408 409 410 411 412 413 414 415
    FixupSection(buffer, start);
  }

  // == Emit globals ===========================================================
  if (globals_.size() > 0) {
    size_t start = EmitSection(kGlobalSectionCode, buffer);
    buffer.write_size(globals_.size());

    for (auto global : globals_) {
416
      buffer.write_u8(ValueTypes::ValueTypeCodeFor(global.type));
417
      buffer.write_u8(global.mutability ? 1 : 0);
418
      switch (global.init.kind) {
419
        case WasmInitExpr::kI32Const:
420
          DCHECK_EQ(kWasmI32, global.type);
421 422
          buffer.write_u8(kExprI32Const);
          buffer.write_i32v(global.init.val.i32_const);
423
          break;
424
        case WasmInitExpr::kI64Const:
425
          DCHECK_EQ(kWasmI64, global.type);
426 427
          buffer.write_u8(kExprI64Const);
          buffer.write_i64v(global.init.val.i64_const);
428
          break;
429
        case WasmInitExpr::kF32Const:
430
          DCHECK_EQ(kWasmF32, global.type);
431 432
          buffer.write_u8(kExprF32Const);
          buffer.write_f32(global.init.val.f32_const);
433
          break;
434
        case WasmInitExpr::kF64Const:
435
          DCHECK_EQ(kWasmF64, global.type);
436 437
          buffer.write_u8(kExprF64Const);
          buffer.write_f64(global.init.val.f64_const);
438
          break;
439 440 441
        case WasmInitExpr::kGlobalIndex:
          buffer.write_u8(kExprGetGlobal);
          buffer.write_u32v(global.init.val.global_index);
442 443 444 445
          break;
        default: {
          // No initializer, emit a default value.
          switch (global.type) {
446 447 448 449
            case kWasmI32:
              buffer.write_u8(kExprI32Const);
              // LEB encoding of 0.
              buffer.write_u8(0);
450
              break;
451 452 453 454
            case kWasmI64:
              buffer.write_u8(kExprI64Const);
              // LEB encoding of 0.
              buffer.write_u8(0);
455
              break;
456 457 458
            case kWasmF32:
              buffer.write_u8(kExprF32Const);
              buffer.write_f32(0.f);
459
              break;
460 461 462
            case kWasmF64:
              buffer.write_u8(kExprF64Const);
              buffer.write_f64(0.);
463 464 465 466 467
              break;
            default:
              UNREACHABLE();
          }
        }
468 469 470
      }
      buffer.write_u8(kExprEnd);
    }
471
    FixupSection(buffer, start);
472 473
  }

474
  // == emit exports ===========================================================
475
  if (!function_exports_.empty()) {
476
    size_t start = EmitSection(kExportSectionCode, buffer);
477 478 479 480 481 482 483
    buffer.write_size(function_exports_.size());
    for (auto function_export : function_exports_) {
      buffer.write_string(function_export.name);
      buffer.write_u8(kExternalFunction);
      buffer.write_size(function_export.function_index +
                        function_imports_.size());
    }
484 485 486 487
    FixupSection(buffer, start);
  }

  // == emit start function index ==============================================
488
  if (start_function_index_ >= 0) {
489
    size_t start = EmitSection(kStartSectionCode, buffer);
490
    buffer.write_size(start_function_index_ + function_imports_.size());
491
    FixupSection(buffer, start);
492 493
  }

494 495 496 497 498 499 500 501 502 503 504
  // == emit function table elements ===========================================
  if (indirect_functions_.size() > 0) {
    size_t start = EmitSection(kElementSectionCode, buffer);
    buffer.write_u8(1);              // count of entries
    buffer.write_u8(0);              // table index
    buffer.write_u8(kExprI32Const);  // offset
    buffer.write_u32v(0);
    buffer.write_u8(kExprEnd);
    buffer.write_size(indirect_functions_.size());  // element count

    for (auto index : indirect_functions_) {
505
      buffer.write_size(index + function_imports_.size());
506 507 508 509 510
    }

    FixupSection(buffer, start);
  }

511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
  // == emit compilation hints section =========================================
  bool emit_compilation_hints = false;
  for (auto* fn : functions_) {
    if (fn->hint_ != kNoCompilationHint) {
      emit_compilation_hints = true;
      break;
    }
  }
  if (emit_compilation_hints) {
    // Emit the section code.
    buffer.write_u8(kUnknownSectionCode);
    // Emit a placeholder for section length.
    size_t start = buffer.reserve_u32v();
    // Emit custom section name.
    buffer.write_string(CStrVector("compilationHints"));
    // Emit hint count.
    buffer.write_size(functions_.size());
    // Emit hint bytes.
    for (auto* fn : functions_) {
      uint8_t hint_byte =
          fn->hint_ != kNoCompilationHint ? fn->hint_ : kDefaultCompilationHint;
      buffer.write_u8(hint_byte);
    }
    FixupSection(buffer, start);
  }

537 538
  // == emit code ==============================================================
  if (functions_.size() > 0) {
539
    size_t start = EmitSection(kCodeSectionCode, buffer);
540
    buffer.write_size(functions_.size());
541
    for (auto* function : functions_) {
542 543 544 545 546 547
      function->WriteBody(buffer);
    }
    FixupSection(buffer, start);
  }

  // == emit data segments =====================================================
548
  if (data_segments_.size() > 0) {
549
    size_t start = EmitSection(kDataSectionCode, buffer);
550
    buffer.write_size(data_segments_.size());
551 552

    for (auto segment : data_segments_) {
553 554 555 556 557 558
      buffer.write_u8(0);              // linear memory segment
      buffer.write_u8(kExprI32Const);  // initializer expression for dest
      buffer.write_u32v(segment.dest);
      buffer.write_u8(kExprEnd);
      buffer.write_u32v(static_cast<uint32_t>(segment.data.size()));
      buffer.write(&segment.data[0], segment.data.size());
559
    }
560
    FixupSection(buffer, start);
561
  }
562 563

  // == Emit names =============================================================
564
  if (num_function_names > 0 || !function_imports_.empty()) {
565 566 567 568 569
    // Emit the section code.
    buffer.write_u8(kUnknownSectionCode);
    // Emit a placeholder for the length.
    size_t start = buffer.reserve_u32v();
    // Emit the section string.
570
    buffer.write_string(CStrVector("name"));
571
    // Emit a subsection for the function names.
572
    buffer.write_u8(NameSectionKindCode::kFunction);
573 574 575 576 577 578 579 580 581
    // Emit a placeholder for the subsection length.
    size_t functions_start = buffer.reserve_u32v();
    // Emit the function names.
    // Imports are always named.
    uint32_t num_imports = static_cast<uint32_t>(function_imports_.size());
    buffer.write_size(num_imports + num_function_names);
    uint32_t function_index = 0;
    for (; function_index < num_imports; ++function_index) {
      const WasmFunctionImport* import = &function_imports_[function_index];
582
      DCHECK(!import->name.empty());
583
      buffer.write_u32v(function_index);
584
      buffer.write_string(import->name);
585
    }
586
    if (num_function_names > 0) {
587
      for (auto* function : functions_) {
588 589
        DCHECK_EQ(function_index,
                  function->func_index() + function_imports_.size());
590
        if (!function->name_.empty()) {
591
          buffer.write_u32v(function_index);
592
          buffer.write_string(function->name_);
593 594 595
        }
        ++function_index;
      }
596
    }
597
    FixupSection(buffer, functions_start);
598 599
    FixupSection(buffer, start);
  }
600
}
601 602 603 604 605

void WasmModuleBuilder::WriteAsmJsOffsetTable(ZoneBuffer& buffer) const {
  // == Emit asm.js offset table ===============================================
  buffer.write_size(functions_.size());
  // Emit the offset table per function.
606
  for (auto* function : functions_) {
607 608
    function->WriteAsmWasmOffsetTable(buffer);
  }
609 610
  // Append a 0 to indicate that this is an encoded table.
  buffer.write_u8(0);
611
}
612 613 614
}  // namespace wasm
}  // namespace internal
}  // namespace v8