• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/signature.h"
6 
7 #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 #include "src/wasm/wasm-module.h"
17 #include "src/wasm/wasm-opcodes.h"
18 
19 #include "src/v8memory.h"
20 
21 namespace v8 {
22 namespace internal {
23 namespace wasm {
24 
25 namespace {
26 
27 // Emit a section code and the size as a padded varint that can be patched
28 // later.
EmitSection(SectionCode code,ZoneBuffer & buffer)29 size_t EmitSection(SectionCode code, ZoneBuffer& buffer) {
30   // Emit the section code.
31   buffer.write_u8(code);
32 
33   // Emit a placeholder for the length.
34   return buffer.reserve_u32v();
35 }
36 
37 // Patch the size of a section after it's finished.
FixupSection(ZoneBuffer & buffer,size_t start)38 void FixupSection(ZoneBuffer& buffer, size_t start) {
39   buffer.patch_u32v(start, static_cast<uint32_t>(buffer.offset() - start -
40                                                  kPaddedVarInt32Size));
41 }
42 
43 }  // namespace
44 
WasmFunctionBuilder(WasmModuleBuilder * builder)45 WasmFunctionBuilder::WasmFunctionBuilder(WasmModuleBuilder* builder)
46     : builder_(builder),
47       locals_(builder->zone()),
48       signature_index_(0),
49       func_index_(static_cast<uint32_t>(builder->functions_.size())),
50       body_(builder->zone(), 256),
51       i32_temps_(builder->zone()),
52       i64_temps_(builder->zone()),
53       f32_temps_(builder->zone()),
54       f64_temps_(builder->zone()),
55       direct_calls_(builder->zone()),
56       asm_offsets_(builder->zone(), 8) {}
57 
EmitI32V(int32_t val)58 void WasmFunctionBuilder::EmitI32V(int32_t val) { body_.write_i32v(val); }
59 
EmitU32V(uint32_t val)60 void WasmFunctionBuilder::EmitU32V(uint32_t val) { body_.write_u32v(val); }
61 
SetSignature(FunctionSig * sig)62 void WasmFunctionBuilder::SetSignature(FunctionSig* sig) {
63   DCHECK(!locals_.has_sig());
64   locals_.set_sig(sig);
65   signature_index_ = builder_->AddSignature(sig);
66 }
67 
AddLocal(ValueType type)68 uint32_t WasmFunctionBuilder::AddLocal(ValueType type) {
69   DCHECK(locals_.has_sig());
70   return locals_.AddLocals(1, type);
71 }
72 
EmitGetLocal(uint32_t local_index)73 void WasmFunctionBuilder::EmitGetLocal(uint32_t local_index) {
74   EmitWithU32V(kExprGetLocal, local_index);
75 }
76 
EmitSetLocal(uint32_t local_index)77 void WasmFunctionBuilder::EmitSetLocal(uint32_t local_index) {
78   EmitWithU32V(kExprSetLocal, local_index);
79 }
80 
EmitTeeLocal(uint32_t local_index)81 void WasmFunctionBuilder::EmitTeeLocal(uint32_t local_index) {
82   EmitWithU32V(kExprTeeLocal, local_index);
83 }
84 
EmitCode(const byte * code,uint32_t code_size)85 void WasmFunctionBuilder::EmitCode(const byte* code, uint32_t code_size) {
86   body_.write(code, code_size);
87 }
88 
Emit(WasmOpcode opcode)89 void WasmFunctionBuilder::Emit(WasmOpcode opcode) { body_.write_u8(opcode); }
90 
EmitWithU8(WasmOpcode opcode,const byte immediate)91 void WasmFunctionBuilder::EmitWithU8(WasmOpcode opcode, const byte immediate) {
92   body_.write_u8(opcode);
93   body_.write_u8(immediate);
94 }
95 
EmitWithU8U8(WasmOpcode opcode,const byte imm1,const byte imm2)96 void WasmFunctionBuilder::EmitWithU8U8(WasmOpcode opcode, const byte imm1,
97                                        const byte imm2) {
98   body_.write_u8(opcode);
99   body_.write_u8(imm1);
100   body_.write_u8(imm2);
101 }
102 
EmitWithI32V(WasmOpcode opcode,int32_t immediate)103 void WasmFunctionBuilder::EmitWithI32V(WasmOpcode opcode, int32_t immediate) {
104   body_.write_u8(opcode);
105   body_.write_i32v(immediate);
106 }
107 
EmitWithU32V(WasmOpcode opcode,uint32_t immediate)108 void WasmFunctionBuilder::EmitWithU32V(WasmOpcode opcode, uint32_t immediate) {
109   body_.write_u8(opcode);
110   body_.write_u32v(immediate);
111 }
112 
EmitI32Const(int32_t value)113 void WasmFunctionBuilder::EmitI32Const(int32_t value) {
114   EmitWithI32V(kExprI32Const, value);
115 }
116 
EmitI64Const(int64_t value)117 void WasmFunctionBuilder::EmitI64Const(int64_t value) {
118   body_.write_u8(kExprI64Const);
119   body_.write_i64v(value);
120 }
121 
EmitF32Const(float value)122 void WasmFunctionBuilder::EmitF32Const(float value) {
123   body_.write_u8(kExprF32Const);
124   body_.write_f32(value);
125 }
126 
EmitF64Const(double value)127 void WasmFunctionBuilder::EmitF64Const(double value) {
128   body_.write_u8(kExprF64Const);
129   body_.write_f64(value);
130 }
131 
EmitDirectCallIndex(uint32_t index)132 void WasmFunctionBuilder::EmitDirectCallIndex(uint32_t index) {
133   DirectCallIndex call;
134   call.offset = body_.size();
135   call.direct_index = index;
136   direct_calls_.push_back(call);
137   byte placeholder_bytes[kMaxVarInt32Size] = {0};
138   EmitCode(placeholder_bytes, arraysize(placeholder_bytes));
139 }
140 
SetName(Vector<const char> name)141 void WasmFunctionBuilder::SetName(Vector<const char> name) { name_ = name; }
142 
AddAsmWasmOffset(size_t call_position,size_t to_number_position)143 void WasmFunctionBuilder::AddAsmWasmOffset(size_t call_position,
144                                            size_t to_number_position) {
145   // We only want to emit one mapping per byte offset.
146   DCHECK(asm_offsets_.size() == 0 || body_.size() > last_asm_byte_offset_);
147 
148   DCHECK_LE(body_.size(), kMaxUInt32);
149   uint32_t byte_offset = static_cast<uint32_t>(body_.size());
150   asm_offsets_.write_u32v(byte_offset - last_asm_byte_offset_);
151   last_asm_byte_offset_ = byte_offset;
152 
153   DCHECK_GE(std::numeric_limits<uint32_t>::max(), call_position);
154   uint32_t call_position_u32 = static_cast<uint32_t>(call_position);
155   asm_offsets_.write_i32v(call_position_u32 - last_asm_source_position_);
156 
157   DCHECK_GE(std::numeric_limits<uint32_t>::max(), to_number_position);
158   uint32_t to_number_position_u32 = static_cast<uint32_t>(to_number_position);
159   asm_offsets_.write_i32v(to_number_position_u32 - call_position_u32);
160   last_asm_source_position_ = to_number_position_u32;
161 }
162 
SetAsmFunctionStartPosition(size_t function_position)163 void WasmFunctionBuilder::SetAsmFunctionStartPosition(
164     size_t function_position) {
165   DCHECK_EQ(0, asm_func_start_source_position_);
166   DCHECK_GE(std::numeric_limits<uint32_t>::max(), function_position);
167   uint32_t function_position_u32 = static_cast<uint32_t>(function_position);
168   // Must be called before emitting any asm.js source position.
169   DCHECK_EQ(0, asm_offsets_.size());
170   asm_func_start_source_position_ = function_position_u32;
171   last_asm_source_position_ = function_position_u32;
172 }
173 
DeleteCodeAfter(size_t position)174 void WasmFunctionBuilder::DeleteCodeAfter(size_t position) {
175   DCHECK_LE(position, body_.size());
176   body_.Truncate(position);
177 }
178 
WriteSignature(ZoneBuffer & buffer) const179 void WasmFunctionBuilder::WriteSignature(ZoneBuffer& buffer) const {
180   buffer.write_u32v(signature_index_);
181 }
182 
WriteBody(ZoneBuffer & buffer) const183 void WasmFunctionBuilder::WriteBody(ZoneBuffer& buffer) const {
184   size_t locals_size = locals_.Size();
185   buffer.write_size(locals_size + body_.size());
186   buffer.EnsureSpace(locals_size);
187   byte** ptr = buffer.pos_ptr();
188   locals_.Emit(*ptr);
189   (*ptr) += locals_size;  // UGLY: manual bump of position pointer
190   if (body_.size() > 0) {
191     size_t base = buffer.offset();
192     buffer.write(body_.begin(), body_.size());
193     for (DirectCallIndex call : direct_calls_) {
194       buffer.patch_u32v(
195           base + call.offset,
196           call.direct_index +
197               static_cast<uint32_t>(builder_->function_imports_.size()));
198     }
199   }
200 }
201 
WriteAsmWasmOffsetTable(ZoneBuffer & buffer) const202 void WasmFunctionBuilder::WriteAsmWasmOffsetTable(ZoneBuffer& buffer) const {
203   if (asm_func_start_source_position_ == 0 && asm_offsets_.size() == 0) {
204     buffer.write_size(0);
205     return;
206   }
207   size_t locals_enc_size = LEBHelper::sizeof_u32v(locals_.Size());
208   size_t func_start_size =
209       LEBHelper::sizeof_u32v(asm_func_start_source_position_);
210   buffer.write_size(asm_offsets_.size() + locals_enc_size + func_start_size);
211   // Offset of the recorded byte offsets.
212   DCHECK_GE(kMaxUInt32, locals_.Size());
213   buffer.write_u32v(static_cast<uint32_t>(locals_.Size()));
214   // Start position of the function.
215   buffer.write_u32v(asm_func_start_source_position_);
216   buffer.write(asm_offsets_.begin(), asm_offsets_.size());
217 }
218 
WasmModuleBuilder(Zone * zone)219 WasmModuleBuilder::WasmModuleBuilder(Zone* zone)
220     : zone_(zone),
221       signatures_(zone),
222       function_imports_(zone),
223       function_exports_(zone),
224       global_imports_(zone),
225       functions_(zone),
226       data_segments_(zone),
227       indirect_functions_(zone),
228       globals_(zone),
229       signature_map_(zone),
230       start_function_index_(-1),
231       min_memory_size_(16),
232       max_memory_size_(0),
233       has_max_memory_size_(false),
234       has_shared_memory_(false) {}
235 
AddFunction(FunctionSig * sig)236 WasmFunctionBuilder* WasmModuleBuilder::AddFunction(FunctionSig* sig) {
237   functions_.push_back(new (zone_) WasmFunctionBuilder(this));
238   // Add the signature if one was provided here.
239   if (sig) functions_.back()->SetSignature(sig);
240   return functions_.back();
241 }
242 
AddDataSegment(const byte * data,uint32_t size,uint32_t dest)243 void WasmModuleBuilder::AddDataSegment(const byte* data, uint32_t size,
244                                        uint32_t dest) {
245   data_segments_.push_back({ZoneVector<byte>(zone()), dest});
246   ZoneVector<byte>& vec = data_segments_.back().data;
247   for (uint32_t i = 0; i < size; i++) {
248     vec.push_back(data[i]);
249   }
250 }
251 
AddSignature(FunctionSig * sig)252 uint32_t WasmModuleBuilder::AddSignature(FunctionSig* sig) {
253   auto sig_entry = signature_map_.find(*sig);
254   if (sig_entry != signature_map_.end()) return sig_entry->second;
255   uint32_t index = static_cast<uint32_t>(signatures_.size());
256   signature_map_.emplace(*sig, index);
257   signatures_.push_back(sig);
258   return index;
259 }
260 
AllocateIndirectFunctions(uint32_t count)261 uint32_t WasmModuleBuilder::AllocateIndirectFunctions(uint32_t count) {
262   uint32_t index = static_cast<uint32_t>(indirect_functions_.size());
263   DCHECK_GE(FLAG_wasm_max_table_size, index);
264   if (count > FLAG_wasm_max_table_size - index) {
265     return std::numeric_limits<uint32_t>::max();
266   }
267   indirect_functions_.resize(indirect_functions_.size() + count);
268   return index;
269 }
270 
SetIndirectFunction(uint32_t indirect,uint32_t direct)271 void WasmModuleBuilder::SetIndirectFunction(uint32_t indirect,
272                                             uint32_t direct) {
273   indirect_functions_[indirect] = direct;
274 }
275 
AddImport(Vector<const char> name,FunctionSig * sig)276 uint32_t WasmModuleBuilder::AddImport(Vector<const char> name,
277                                       FunctionSig* sig) {
278   function_imports_.push_back({name, AddSignature(sig)});
279   return static_cast<uint32_t>(function_imports_.size() - 1);
280 }
281 
AddGlobalImport(Vector<const char> name,ValueType type)282 uint32_t WasmModuleBuilder::AddGlobalImport(Vector<const char> name,
283                                             ValueType type) {
284   global_imports_.push_back({name, ValueTypes::ValueTypeCodeFor(type)});
285   return static_cast<uint32_t>(global_imports_.size() - 1);
286 }
287 
MarkStartFunction(WasmFunctionBuilder * function)288 void WasmModuleBuilder::MarkStartFunction(WasmFunctionBuilder* function) {
289   start_function_index_ = function->func_index();
290 }
291 
AddExport(Vector<const char> name,WasmFunctionBuilder * function)292 void WasmModuleBuilder::AddExport(Vector<const char> name,
293                                   WasmFunctionBuilder* function) {
294   function_exports_.push_back({name, function->func_index()});
295 }
296 
AddGlobal(ValueType type,bool exported,bool mutability,const WasmInitExpr & init)297 uint32_t WasmModuleBuilder::AddGlobal(ValueType type, bool exported,
298                                       bool mutability,
299                                       const WasmInitExpr& init) {
300   globals_.push_back({type, exported, mutability, init});
301   return static_cast<uint32_t>(globals_.size() - 1);
302 }
303 
SetMinMemorySize(uint32_t value)304 void WasmModuleBuilder::SetMinMemorySize(uint32_t value) {
305   min_memory_size_ = value;
306 }
307 
SetMaxMemorySize(uint32_t value)308 void WasmModuleBuilder::SetMaxMemorySize(uint32_t value) {
309   has_max_memory_size_ = true;
310   max_memory_size_ = value;
311 }
312 
SetHasSharedMemory()313 void WasmModuleBuilder::SetHasSharedMemory() { has_shared_memory_ = true; }
314 
WriteTo(ZoneBuffer & buffer) const315 void WasmModuleBuilder::WriteTo(ZoneBuffer& buffer) const {
316   // == Emit magic =============================================================
317   buffer.write_u32(kWasmMagic);
318   buffer.write_u32(kWasmVersion);
319 
320   // == Emit signatures ========================================================
321   if (signatures_.size() > 0) {
322     size_t start = EmitSection(kTypeSectionCode, buffer);
323     buffer.write_size(signatures_.size());
324 
325     for (FunctionSig* sig : signatures_) {
326       buffer.write_u8(kWasmFunctionTypeCode);
327       buffer.write_size(sig->parameter_count());
328       for (auto param : sig->parameters()) {
329         buffer.write_u8(ValueTypes::ValueTypeCodeFor(param));
330       }
331       buffer.write_size(sig->return_count());
332       for (auto ret : sig->returns()) {
333         buffer.write_u8(ValueTypes::ValueTypeCodeFor(ret));
334       }
335     }
336     FixupSection(buffer, start);
337   }
338 
339   // == Emit imports ===========================================================
340   if (global_imports_.size() + function_imports_.size() > 0) {
341     size_t start = EmitSection(kImportSectionCode, buffer);
342     buffer.write_size(global_imports_.size() + function_imports_.size());
343     for (auto import : global_imports_) {
344       buffer.write_u32v(0);              // module name (length)
345       buffer.write_string(import.name);  // field name
346       buffer.write_u8(kExternalGlobal);
347       buffer.write_u8(import.type_code);
348       buffer.write_u8(0);  // immutable
349     }
350     for (auto import : function_imports_) {
351       buffer.write_u32v(0);              // module name (length)
352       buffer.write_string(import.name);  // field name
353       buffer.write_u8(kExternalFunction);
354       buffer.write_u32v(import.sig_index);
355     }
356     FixupSection(buffer, start);
357   }
358 
359   // == Emit function signatures ===============================================
360   uint32_t num_function_names = 0;
361   if (functions_.size() > 0) {
362     size_t start = EmitSection(kFunctionSectionCode, buffer);
363     buffer.write_size(functions_.size());
364     for (auto function : functions_) {
365       function->WriteSignature(buffer);
366       if (!function->name_.is_empty()) ++num_function_names;
367     }
368     FixupSection(buffer, start);
369   }
370 
371   // == emit function table ====================================================
372   if (indirect_functions_.size() > 0) {
373     size_t start = EmitSection(kTableSectionCode, buffer);
374     buffer.write_u8(1);  // table count
375     buffer.write_u8(kLocalAnyFunc);
376     buffer.write_u8(kHasMaximumFlag);
377     buffer.write_size(indirect_functions_.size());
378     buffer.write_size(indirect_functions_.size());
379     FixupSection(buffer, start);
380   }
381 
382   // == emit memory declaration ================================================
383   {
384     size_t start = EmitSection(kMemorySectionCode, buffer);
385     buffer.write_u8(1);  // memory count
386     if (has_shared_memory_) {
387       buffer.write_u8(has_max_memory_size_ ? MemoryFlags::kSharedAndMaximum
388                                            : MemoryFlags::kSharedNoMaximum);
389     } else {
390       buffer.write_u8(has_max_memory_size_ ? MemoryFlags::kMaximum
391                                            : MemoryFlags::kNoMaximum);
392     }
393     buffer.write_u32v(min_memory_size_);
394     if (has_max_memory_size_) {
395       buffer.write_u32v(max_memory_size_);
396     }
397     FixupSection(buffer, start);
398   }
399 
400   // == Emit globals ===========================================================
401   if (globals_.size() > 0) {
402     size_t start = EmitSection(kGlobalSectionCode, buffer);
403     buffer.write_size(globals_.size());
404 
405     for (auto global : globals_) {
406       buffer.write_u8(ValueTypes::ValueTypeCodeFor(global.type));
407       buffer.write_u8(global.mutability ? 1 : 0);
408       switch (global.init.kind) {
409         case WasmInitExpr::kI32Const:
410           DCHECK_EQ(kWasmI32, global.type);
411           buffer.write_u8(kExprI32Const);
412           buffer.write_i32v(global.init.val.i32_const);
413           break;
414         case WasmInitExpr::kI64Const:
415           DCHECK_EQ(kWasmI64, global.type);
416           buffer.write_u8(kExprI64Const);
417           buffer.write_i64v(global.init.val.i64_const);
418           break;
419         case WasmInitExpr::kF32Const:
420           DCHECK_EQ(kWasmF32, global.type);
421           buffer.write_u8(kExprF32Const);
422           buffer.write_f32(global.init.val.f32_const);
423           break;
424         case WasmInitExpr::kF64Const:
425           DCHECK_EQ(kWasmF64, global.type);
426           buffer.write_u8(kExprF64Const);
427           buffer.write_f64(global.init.val.f64_const);
428           break;
429         case WasmInitExpr::kGlobalIndex:
430           buffer.write_u8(kExprGetGlobal);
431           buffer.write_u32v(global.init.val.global_index);
432           break;
433         default: {
434           // No initializer, emit a default value.
435           switch (global.type) {
436             case kWasmI32:
437               buffer.write_u8(kExprI32Const);
438               // LEB encoding of 0.
439               buffer.write_u8(0);
440               break;
441             case kWasmI64:
442               buffer.write_u8(kExprI64Const);
443               // LEB encoding of 0.
444               buffer.write_u8(0);
445               break;
446             case kWasmF32:
447               buffer.write_u8(kExprF32Const);
448               buffer.write_f32(0.f);
449               break;
450             case kWasmF64:
451               buffer.write_u8(kExprF64Const);
452               buffer.write_f64(0.);
453               break;
454             default:
455               UNREACHABLE();
456           }
457         }
458       }
459       buffer.write_u8(kExprEnd);
460     }
461     FixupSection(buffer, start);
462   }
463 
464   // == emit exports ===========================================================
465   if (!function_exports_.empty()) {
466     size_t start = EmitSection(kExportSectionCode, buffer);
467     buffer.write_size(function_exports_.size());
468     for (auto function_export : function_exports_) {
469       buffer.write_string(function_export.name);
470       buffer.write_u8(kExternalFunction);
471       buffer.write_size(function_export.function_index +
472                         function_imports_.size());
473     }
474     FixupSection(buffer, start);
475   }
476 
477   // == emit start function index ==============================================
478   if (start_function_index_ >= 0) {
479     size_t start = EmitSection(kStartSectionCode, buffer);
480     buffer.write_size(start_function_index_ + function_imports_.size());
481     FixupSection(buffer, start);
482   }
483 
484   // == emit function table elements ===========================================
485   if (indirect_functions_.size() > 0) {
486     size_t start = EmitSection(kElementSectionCode, buffer);
487     buffer.write_u8(1);              // count of entries
488     buffer.write_u8(0);              // table index
489     buffer.write_u8(kExprI32Const);  // offset
490     buffer.write_u32v(0);
491     buffer.write_u8(kExprEnd);
492     buffer.write_size(indirect_functions_.size());  // element count
493 
494     for (auto index : indirect_functions_) {
495       buffer.write_size(index + function_imports_.size());
496     }
497 
498     FixupSection(buffer, start);
499   }
500 
501   // == emit code ==============================================================
502   if (functions_.size() > 0) {
503     size_t start = EmitSection(kCodeSectionCode, buffer);
504     buffer.write_size(functions_.size());
505     for (auto function : functions_) {
506       function->WriteBody(buffer);
507     }
508     FixupSection(buffer, start);
509   }
510 
511   // == emit data segments =====================================================
512   if (data_segments_.size() > 0) {
513     size_t start = EmitSection(kDataSectionCode, buffer);
514     buffer.write_size(data_segments_.size());
515 
516     for (auto segment : data_segments_) {
517       buffer.write_u8(0);              // linear memory segment
518       buffer.write_u8(kExprI32Const);  // initializer expression for dest
519       buffer.write_u32v(segment.dest);
520       buffer.write_u8(kExprEnd);
521       buffer.write_u32v(static_cast<uint32_t>(segment.data.size()));
522       buffer.write(&segment.data[0], segment.data.size());
523     }
524     FixupSection(buffer, start);
525   }
526 
527   // == Emit names =============================================================
528   if (num_function_names > 0 || !function_imports_.empty()) {
529     // Emit the section code.
530     buffer.write_u8(kUnknownSectionCode);
531     // Emit a placeholder for the length.
532     size_t start = buffer.reserve_u32v();
533     // Emit the section string.
534     buffer.write_size(4);
535     buffer.write(reinterpret_cast<const byte*>("name"), 4);
536     // Emit a subsection for the function names.
537     buffer.write_u8(NameSectionKindCode::kFunction);
538     // Emit a placeholder for the subsection length.
539     size_t functions_start = buffer.reserve_u32v();
540     // Emit the function names.
541     // Imports are always named.
542     uint32_t num_imports = static_cast<uint32_t>(function_imports_.size());
543     buffer.write_size(num_imports + num_function_names);
544     uint32_t function_index = 0;
545     for (; function_index < num_imports; ++function_index) {
546       const WasmFunctionImport* import = &function_imports_[function_index];
547       DCHECK(!import->name.is_empty());
548       buffer.write_u32v(function_index);
549       buffer.write_string(import->name);
550     }
551     if (num_function_names > 0) {
552       for (auto function : functions_) {
553         DCHECK_EQ(function_index,
554                   function->func_index() + function_imports_.size());
555         if (!function->name_.is_empty()) {
556           buffer.write_u32v(function_index);
557           buffer.write_string(function->name_);
558         }
559         ++function_index;
560       }
561     }
562     FixupSection(buffer, functions_start);
563     FixupSection(buffer, start);
564   }
565 }
566 
WriteAsmJsOffsetTable(ZoneBuffer & buffer) const567 void WasmModuleBuilder::WriteAsmJsOffsetTable(ZoneBuffer& buffer) const {
568   // == Emit asm.js offset table ===============================================
569   buffer.write_size(functions_.size());
570   // Emit the offset table per function.
571   for (auto function : functions_) {
572     function->WriteAsmWasmOffsetTable(buffer);
573   }
574   // Append a 0 to indicate that this is an encoded table.
575   buffer.write_u8(0);
576 }
577 }  // namespace wasm
578 }  // namespace internal
579 }  // namespace v8
580