1// Copyright 2021 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 "include/v8-wasm.h"
6#include "src/base/memory.h"
7#include "src/base/platform/mutex.h"
8#include "src/execution/arguments-inl.h"
9#include "src/execution/frames-inl.h"
10#include "src/heap/heap-inl.h"
11#include "src/logging/counters.h"
12#include "src/objects/smi.h"
13#include "src/runtime/runtime-utils.h"
14#include "src/trap-handler/trap-handler.h"
15#include "src/utils/ostreams.h"
16#include "src/wasm/memory-tracing.h"
17#include "src/wasm/module-compiler.h"
18#include "src/wasm/wasm-code-manager.h"
19#include "src/wasm/wasm-engine.h"
20#include "src/wasm/wasm-module.h"
21#include "src/wasm/wasm-objects-inl.h"
22#include "src/wasm/wasm-serialization.h"
23
24namespace v8 {
25namespace internal {
26
27namespace {
28struct WasmCompileControls {
29  uint32_t MaxWasmBufferSize = std::numeric_limits<uint32_t>::max();
30  bool AllowAnySizeForAsync = true;
31};
32using WasmCompileControlsMap = std::map<v8::Isolate*, WasmCompileControls>;
33
34// We need per-isolate controls, because we sometimes run tests in multiple
35// isolates concurrently. Methods need to hold the accompanying mutex on access.
36// To avoid upsetting the static initializer count, we lazy initialize this.
37DEFINE_LAZY_LEAKY_OBJECT_GETTER(WasmCompileControlsMap,
38                                GetPerIsolateWasmControls)
39base::LazyMutex g_PerIsolateWasmControlsMutex = LAZY_MUTEX_INITIALIZER;
40
41bool IsWasmCompileAllowed(v8::Isolate* isolate, v8::Local<v8::Value> value,
42                          bool is_async) {
43  base::MutexGuard guard(g_PerIsolateWasmControlsMutex.Pointer());
44  DCHECK_GT(GetPerIsolateWasmControls()->count(isolate), 0);
45  const WasmCompileControls& ctrls = GetPerIsolateWasmControls()->at(isolate);
46  return (is_async && ctrls.AllowAnySizeForAsync) ||
47         (value->IsArrayBuffer() && value.As<v8::ArrayBuffer>()->ByteLength() <=
48                                        ctrls.MaxWasmBufferSize) ||
49         (value->IsArrayBufferView() &&
50          value.As<v8::ArrayBufferView>()->ByteLength() <=
51              ctrls.MaxWasmBufferSize);
52}
53
54// Use the compile controls for instantiation, too
55bool IsWasmInstantiateAllowed(v8::Isolate* isolate,
56                              v8::Local<v8::Value> module_or_bytes,
57                              bool is_async) {
58  base::MutexGuard guard(g_PerIsolateWasmControlsMutex.Pointer());
59  DCHECK_GT(GetPerIsolateWasmControls()->count(isolate), 0);
60  const WasmCompileControls& ctrls = GetPerIsolateWasmControls()->at(isolate);
61  if (is_async && ctrls.AllowAnySizeForAsync) return true;
62  if (!module_or_bytes->IsWasmModuleObject()) {
63    return IsWasmCompileAllowed(isolate, module_or_bytes, is_async);
64  }
65  v8::Local<v8::WasmModuleObject> module =
66      v8::Local<v8::WasmModuleObject>::Cast(module_or_bytes);
67  return static_cast<uint32_t>(
68             module->GetCompiledModule().GetWireBytesRef().size()) <=
69         ctrls.MaxWasmBufferSize;
70}
71
72v8::Local<v8::Value> NewRangeException(v8::Isolate* isolate,
73                                       const char* message) {
74  return v8::Exception::RangeError(
75      v8::String::NewFromOneByte(isolate,
76                                 reinterpret_cast<const uint8_t*>(message))
77          .ToLocalChecked());
78}
79
80void ThrowRangeException(v8::Isolate* isolate, const char* message) {
81  isolate->ThrowException(NewRangeException(isolate, message));
82}
83
84bool WasmModuleOverride(const v8::FunctionCallbackInfo<v8::Value>& args) {
85  if (IsWasmCompileAllowed(args.GetIsolate(), args[0], false)) return false;
86  ThrowRangeException(args.GetIsolate(), "Sync compile not allowed");
87  return true;
88}
89
90bool WasmInstanceOverride(const v8::FunctionCallbackInfo<v8::Value>& args) {
91  if (IsWasmInstantiateAllowed(args.GetIsolate(), args[0], false)) return false;
92  ThrowRangeException(args.GetIsolate(), "Sync instantiate not allowed");
93  return true;
94}
95
96}  // namespace
97
98// Returns a callable object. The object returns the difference of its two
99// parameters when it is called.
100RUNTIME_FUNCTION(Runtime_SetWasmCompileControls) {
101  HandleScope scope(isolate);
102  v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
103  CHECK_EQ(args.length(), 2);
104  int block_size = args.smi_value_at(0);
105  bool allow_async = Oddball::cast(args[1]).ToBool(isolate);
106  base::MutexGuard guard(g_PerIsolateWasmControlsMutex.Pointer());
107  WasmCompileControls& ctrl = (*GetPerIsolateWasmControls())[v8_isolate];
108  ctrl.AllowAnySizeForAsync = allow_async;
109  ctrl.MaxWasmBufferSize = static_cast<uint32_t>(block_size);
110  v8_isolate->SetWasmModuleCallback(WasmModuleOverride);
111  return ReadOnlyRoots(isolate).undefined_value();
112}
113
114RUNTIME_FUNCTION(Runtime_SetWasmInstantiateControls) {
115  HandleScope scope(isolate);
116  v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
117  CHECK_EQ(args.length(), 0);
118  v8_isolate->SetWasmInstanceCallback(WasmInstanceOverride);
119  return ReadOnlyRoots(isolate).undefined_value();
120}
121
122namespace {
123
124void PrintIndentation(int stack_size) {
125  const int max_display = 80;
126  if (stack_size <= max_display) {
127    PrintF("%4d:%*s", stack_size, stack_size, "");
128  } else {
129    PrintF("%4d:%*s", stack_size, max_display, "...");
130  }
131}
132
133int WasmStackSize(Isolate* isolate) {
134  // TODO(wasm): Fix this for mixed JS/Wasm stacks with both --trace and
135  // --trace-wasm.
136  int n = 0;
137  for (StackTraceFrameIterator it(isolate); !it.done(); it.Advance()) {
138    if (it.is_wasm()) n++;
139  }
140  return n;
141}
142
143}  // namespace
144
145RUNTIME_FUNCTION(Runtime_WasmTraceEnter) {
146  HandleScope shs(isolate);
147  DCHECK_EQ(0, args.length());
148  PrintIndentation(WasmStackSize(isolate));
149
150  // Find the caller wasm frame.
151  wasm::WasmCodeRefScope wasm_code_ref_scope;
152  StackTraceFrameIterator it(isolate);
153  DCHECK(!it.done());
154  DCHECK(it.is_wasm());
155  WasmFrame* frame = WasmFrame::cast(it.frame());
156
157  // Find the function name.
158  int func_index = frame->function_index();
159  const wasm::WasmModule* module = frame->wasm_instance().module();
160  wasm::ModuleWireBytes wire_bytes =
161      wasm::ModuleWireBytes(frame->native_module()->wire_bytes());
162  wasm::WireBytesRef name_ref =
163      module->lazily_generated_names.LookupFunctionName(wire_bytes, func_index);
164  wasm::WasmName name = wire_bytes.GetNameOrNull(name_ref);
165
166  wasm::WasmCode* code = frame->wasm_code();
167  PrintF(code->is_liftoff() ? "~" : "*");
168
169  if (name.empty()) {
170    PrintF("wasm-function[%d] {\n", func_index);
171  } else {
172    PrintF("wasm-function[%d] \"%.*s\" {\n", func_index, name.length(),
173           name.begin());
174  }
175
176  return ReadOnlyRoots(isolate).undefined_value();
177}
178
179RUNTIME_FUNCTION(Runtime_WasmTraceExit) {
180  HandleScope shs(isolate);
181  DCHECK_EQ(1, args.length());
182  auto value_addr_smi = Smi::cast(args[0]);
183
184  PrintIndentation(WasmStackSize(isolate));
185  PrintF("}");
186
187  // Find the caller wasm frame.
188  wasm::WasmCodeRefScope wasm_code_ref_scope;
189  StackTraceFrameIterator it(isolate);
190  DCHECK(!it.done());
191  DCHECK(it.is_wasm());
192  WasmFrame* frame = WasmFrame::cast(it.frame());
193  int func_index = frame->function_index();
194  const wasm::FunctionSig* sig =
195      frame->wasm_instance().module()->functions[func_index].sig;
196
197  size_t num_returns = sig->return_count();
198  if (num_returns == 1) {
199    wasm::ValueType return_type = sig->GetReturn(0);
200    switch (return_type.kind()) {
201      case wasm::kI32: {
202        int32_t value = base::ReadUnalignedValue<int32_t>(value_addr_smi.ptr());
203        PrintF(" -> %d\n", value);
204        break;
205      }
206      case wasm::kI64: {
207        int64_t value = base::ReadUnalignedValue<int64_t>(value_addr_smi.ptr());
208        PrintF(" -> %" PRId64 "\n", value);
209        break;
210      }
211      case wasm::kF32: {
212        float value = base::ReadUnalignedValue<float>(value_addr_smi.ptr());
213        PrintF(" -> %f\n", value);
214        break;
215      }
216      case wasm::kF64: {
217        double value = base::ReadUnalignedValue<double>(value_addr_smi.ptr());
218        PrintF(" -> %f\n", value);
219        break;
220      }
221      default:
222        PrintF(" -> Unsupported type\n");
223        break;
224    }
225  } else {
226    // TODO(wasm) Handle multiple return values.
227    PrintF("\n");
228  }
229
230  return ReadOnlyRoots(isolate).undefined_value();
231}
232
233RUNTIME_FUNCTION(Runtime_IsAsmWasmCode) {
234  SealHandleScope shs(isolate);
235  DCHECK_EQ(1, args.length());
236  auto function = JSFunction::cast(args[0]);
237  if (!function.shared().HasAsmWasmData()) {
238    return ReadOnlyRoots(isolate).false_value();
239  }
240  if (function.shared().HasBuiltinId() &&
241      function.shared().builtin_id() == Builtin::kInstantiateAsmJs) {
242    // Hasn't been compiled yet.
243    return ReadOnlyRoots(isolate).false_value();
244  }
245  return ReadOnlyRoots(isolate).true_value();
246}
247
248namespace {
249
250bool DisallowWasmCodegenFromStringsCallback(v8::Local<v8::Context> context,
251                                            v8::Local<v8::String> source) {
252  return false;
253}
254
255}  // namespace
256
257RUNTIME_FUNCTION(Runtime_DisallowWasmCodegen) {
258  SealHandleScope shs(isolate);
259  DCHECK_EQ(1, args.length());
260  bool flag = Oddball::cast(args[0]).ToBool(isolate);
261  v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
262  v8_isolate->SetAllowWasmCodeGenerationCallback(
263      flag ? DisallowWasmCodegenFromStringsCallback : nullptr);
264  return ReadOnlyRoots(isolate).undefined_value();
265}
266
267RUNTIME_FUNCTION(Runtime_IsWasmCode) {
268  SealHandleScope shs(isolate);
269  DCHECK_EQ(1, args.length());
270  auto function = JSFunction::cast(args[0]);
271  CodeT code = function.code();
272  bool is_js_to_wasm = code.kind() == CodeKind::JS_TO_WASM_FUNCTION ||
273                       (code.builtin_id() == Builtin::kGenericJSToWasmWrapper);
274  return isolate->heap()->ToBoolean(is_js_to_wasm);
275}
276
277RUNTIME_FUNCTION(Runtime_IsWasmTrapHandlerEnabled) {
278  DisallowGarbageCollection no_gc;
279  DCHECK_EQ(0, args.length());
280  return isolate->heap()->ToBoolean(trap_handler::IsTrapHandlerEnabled());
281}
282
283RUNTIME_FUNCTION(Runtime_IsThreadInWasm) {
284  DisallowGarbageCollection no_gc;
285  DCHECK_EQ(0, args.length());
286  return isolate->heap()->ToBoolean(trap_handler::IsThreadInWasm());
287}
288
289RUNTIME_FUNCTION(Runtime_GetWasmRecoveredTrapCount) {
290  HandleScope scope(isolate);
291  DCHECK_EQ(0, args.length());
292  size_t trap_count = trap_handler::GetRecoveredTrapCount();
293  return *isolate->factory()->NewNumberFromSize(trap_count);
294}
295
296RUNTIME_FUNCTION(Runtime_GetWasmExceptionTagId) {
297  HandleScope scope(isolate);
298  DCHECK_EQ(2, args.length());
299  Handle<WasmExceptionPackage> exception = args.at<WasmExceptionPackage>(0);
300  Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(1);
301  Handle<Object> tag =
302      WasmExceptionPackage::GetExceptionTag(isolate, exception);
303  CHECK(tag->IsWasmExceptionTag());
304  Handle<FixedArray> tags_table(instance->tags_table(), isolate);
305  for (int index = 0; index < tags_table->length(); ++index) {
306    if (tags_table->get(index) == *tag) return Smi::FromInt(index);
307  }
308  UNREACHABLE();
309}
310
311RUNTIME_FUNCTION(Runtime_GetWasmExceptionValues) {
312  HandleScope scope(isolate);
313  DCHECK_EQ(1, args.length());
314  Handle<WasmExceptionPackage> exception = args.at<WasmExceptionPackage>(0);
315  Handle<Object> values_obj =
316      WasmExceptionPackage::GetExceptionValues(isolate, exception);
317  CHECK(values_obj->IsFixedArray());  // Only called with correct input.
318  Handle<FixedArray> values = Handle<FixedArray>::cast(values_obj);
319  return *isolate->factory()->NewJSArrayWithElements(values);
320}
321
322RUNTIME_FUNCTION(Runtime_SerializeWasmModule) {
323  HandleScope scope(isolate);
324  DCHECK_EQ(1, args.length());
325  Handle<WasmModuleObject> module_obj = args.at<WasmModuleObject>(0);
326
327  wasm::NativeModule* native_module = module_obj->native_module();
328  DCHECK(!native_module->compilation_state()->failed());
329
330  wasm::WasmSerializer wasm_serializer(native_module);
331  size_t byte_length = wasm_serializer.GetSerializedNativeModuleSize();
332
333  Handle<JSArrayBuffer> array_buffer =
334      isolate->factory()
335          ->NewJSArrayBufferAndBackingStore(byte_length,
336                                            InitializedFlag::kUninitialized)
337          .ToHandleChecked();
338
339  CHECK(wasm_serializer.SerializeNativeModule(
340      {static_cast<uint8_t*>(array_buffer->backing_store()), byte_length}));
341  return *array_buffer;
342}
343
344// Take an array buffer and attempt to reconstruct a compiled wasm module.
345// Return undefined if unsuccessful.
346RUNTIME_FUNCTION(Runtime_DeserializeWasmModule) {
347  HandleScope scope(isolate);
348  DCHECK_EQ(2, args.length());
349  Handle<JSArrayBuffer> buffer = args.at<JSArrayBuffer>(0);
350  Handle<JSTypedArray> wire_bytes = args.at<JSTypedArray>(1);
351  CHECK(!buffer->was_detached());
352  CHECK(!wire_bytes->WasDetached());
353
354  Handle<JSArrayBuffer> wire_bytes_buffer = wire_bytes->GetBuffer();
355  base::Vector<const uint8_t> wire_bytes_vec{
356      reinterpret_cast<const uint8_t*>(wire_bytes_buffer->backing_store()) +
357          wire_bytes->byte_offset(),
358      wire_bytes->byte_length()};
359  base::Vector<uint8_t> buffer_vec{
360      reinterpret_cast<uint8_t*>(buffer->backing_store()),
361      buffer->byte_length()};
362
363  // Note that {wasm::DeserializeNativeModule} will allocate. We assume the
364  // JSArrayBuffer backing store doesn't get relocated.
365  MaybeHandle<WasmModuleObject> maybe_module_object =
366      wasm::DeserializeNativeModule(isolate, buffer_vec, wire_bytes_vec, {});
367  Handle<WasmModuleObject> module_object;
368  if (!maybe_module_object.ToHandle(&module_object)) {
369    return ReadOnlyRoots(isolate).undefined_value();
370  }
371  return *module_object;
372}
373
374RUNTIME_FUNCTION(Runtime_WasmGetNumberOfInstances) {
375  SealHandleScope shs(isolate);
376  DCHECK_EQ(1, args.length());
377  Handle<WasmModuleObject> module_obj = args.at<WasmModuleObject>(0);
378  int instance_count = 0;
379  WeakArrayList weak_instance_list =
380      module_obj->script().wasm_weak_instance_list();
381  for (int i = 0; i < weak_instance_list.length(); ++i) {
382    if (weak_instance_list.Get(i)->IsWeak()) instance_count++;
383  }
384  return Smi::FromInt(instance_count);
385}
386
387RUNTIME_FUNCTION(Runtime_WasmNumCodeSpaces) {
388  DCHECK_EQ(1, args.length());
389  HandleScope scope(isolate);
390  Handle<JSObject> argument = args.at<JSObject>(0);
391  Handle<WasmModuleObject> module;
392  if (argument->IsWasmInstanceObject()) {
393    module = handle(Handle<WasmInstanceObject>::cast(argument)->module_object(),
394                    isolate);
395  } else if (argument->IsWasmModuleObject()) {
396    module = Handle<WasmModuleObject>::cast(argument);
397  }
398  size_t num_spaces =
399      module->native_module()->GetNumberOfCodeSpacesForTesting();
400  return *isolate->factory()->NewNumberFromSize(num_spaces);
401}
402
403RUNTIME_FUNCTION(Runtime_WasmTraceMemory) {
404  HandleScope scope(isolate);
405  DCHECK_EQ(1, args.length());
406  auto info_addr = Smi::cast(args[0]);
407
408  wasm::MemoryTracingInfo* info =
409      reinterpret_cast<wasm::MemoryTracingInfo*>(info_addr.ptr());
410
411  // Find the caller wasm frame.
412  wasm::WasmCodeRefScope wasm_code_ref_scope;
413  StackTraceFrameIterator it(isolate);
414  DCHECK(!it.done());
415  DCHECK(it.is_wasm());
416  WasmFrame* frame = WasmFrame::cast(it.frame());
417
418  uint8_t* mem_start = reinterpret_cast<uint8_t*>(
419      frame->wasm_instance().memory_object().array_buffer().backing_store());
420  int func_index = frame->function_index();
421  int pos = frame->position();
422  wasm::ExecutionTier tier = frame->wasm_code()->is_liftoff()
423                                 ? wasm::ExecutionTier::kLiftoff
424                                 : wasm::ExecutionTier::kTurbofan;
425  wasm::TraceMemoryOperation(tier, info, func_index, pos, mem_start);
426  return ReadOnlyRoots(isolate).undefined_value();
427}
428
429RUNTIME_FUNCTION(Runtime_WasmTierUpFunction) {
430  HandleScope scope(isolate);
431  DCHECK_EQ(2, args.length());
432  Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
433  int function_index = args.smi_value_at(1);
434  auto* native_module = instance->module_object().native_module();
435  wasm::GetWasmEngine()->CompileFunction(isolate, native_module, function_index,
436                                         wasm::ExecutionTier::kTurbofan);
437  CHECK(!native_module->compilation_state()->failed());
438  return ReadOnlyRoots(isolate).undefined_value();
439}
440
441RUNTIME_FUNCTION(Runtime_WasmTierDown) {
442  HandleScope scope(isolate);
443  DCHECK_EQ(0, args.length());
444  wasm::GetWasmEngine()->TierDownAllModulesPerIsolate(isolate);
445  return ReadOnlyRoots(isolate).undefined_value();
446}
447
448RUNTIME_FUNCTION(Runtime_WasmTierUp) {
449  HandleScope scope(isolate);
450  DCHECK_EQ(0, args.length());
451  wasm::GetWasmEngine()->TierUpAllModulesPerIsolate(isolate);
452  return ReadOnlyRoots(isolate).undefined_value();
453}
454
455RUNTIME_FUNCTION(Runtime_IsLiftoffFunction) {
456  HandleScope scope(isolate);
457  DCHECK_EQ(1, args.length());
458  Handle<JSFunction> function = args.at<JSFunction>(0);
459  CHECK(WasmExportedFunction::IsWasmExportedFunction(*function));
460  Handle<WasmExportedFunction> exp_fun =
461      Handle<WasmExportedFunction>::cast(function);
462  wasm::NativeModule* native_module =
463      exp_fun->instance().module_object().native_module();
464  uint32_t func_index = exp_fun->function_index();
465  wasm::WasmCodeRefScope code_ref_scope;
466  wasm::WasmCode* code = native_module->GetCode(func_index);
467  return isolate->heap()->ToBoolean(code && code->is_liftoff());
468}
469
470RUNTIME_FUNCTION(Runtime_IsTurboFanFunction) {
471  HandleScope scope(isolate);
472  DCHECK_EQ(1, args.length());
473  Handle<JSFunction> function = args.at<JSFunction>(0);
474  CHECK(WasmExportedFunction::IsWasmExportedFunction(*function));
475  Handle<WasmExportedFunction> exp_fun =
476      Handle<WasmExportedFunction>::cast(function);
477  wasm::NativeModule* native_module =
478      exp_fun->instance().module_object().native_module();
479  uint32_t func_index = exp_fun->function_index();
480  wasm::WasmCodeRefScope code_ref_scope;
481  wasm::WasmCode* code = native_module->GetCode(func_index);
482  return isolate->heap()->ToBoolean(code && code->is_turbofan());
483}
484
485RUNTIME_FUNCTION(Runtime_FreezeWasmLazyCompilation) {
486  DCHECK_EQ(1, args.length());
487  DisallowGarbageCollection no_gc;
488  auto instance = WasmInstanceObject::cast(args[0]);
489
490  instance.module_object().native_module()->set_lazy_compile_frozen(true);
491  return ReadOnlyRoots(isolate).undefined_value();
492}
493
494}  // namespace internal
495}  // namespace v8
496