1 // Copyright 2016 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/base/memory.h"
6 #include "src/common/assert-scope.h"
7 #include "src/common/message-template.h"
8 #include "src/compiler/wasm-compiler.h"
9 #include "src/debug/debug.h"
10 #include "src/execution/arguments-inl.h"
11 #include "src/execution/frame-constants.h"
12 #include "src/execution/frames.h"
13 #include "src/heap/factory.h"
14 #include "src/logging/counters.h"
15 #include "src/numbers/conversions.h"
16 #include "src/objects/objects-inl.h"
17 #include "src/runtime/runtime-utils.h"
18 #include "src/trap-handler/trap-handler.h"
19 #include "src/wasm/module-compiler.h"
20 #include "src/wasm/stacks.h"
21 #include "src/wasm/value-type.h"
22 #include "src/wasm/wasm-code-manager.h"
23 #include "src/wasm/wasm-constants.h"
24 #include "src/wasm/wasm-debug.h"
25 #include "src/wasm/wasm-engine.h"
26 #include "src/wasm/wasm-objects.h"
27 #include "src/wasm/wasm-subtyping.h"
28 #include "src/wasm/wasm-value.h"
29
30 namespace v8 {
31 namespace internal {
32
33 namespace {
34
35 template <typename FrameType>
36 class FrameFinder {
37 public:
FrameFinder(Isolate * isolate,std::initializer_list<StackFrame::Type> skipped_frame_types={StackFrame::EXIT})38 explicit FrameFinder(Isolate* isolate,
39 std::initializer_list<StackFrame::Type>
40 skipped_frame_types = {StackFrame::EXIT})
41 : frame_iterator_(isolate, isolate->thread_local_top()) {
42 // We skip at least one frame.
43 DCHECK_LT(0, skipped_frame_types.size());
44
45 for (auto type : skipped_frame_types) {
46 DCHECK_EQ(type, frame_iterator_.frame()->type());
47 USE(type);
48 frame_iterator_.Advance();
49 }
50 // Type check the frame where the iterator stopped now.
51 DCHECK_NOT_NULL(frame());
52 }
53
frame()54 FrameType* frame() { return FrameType::cast(frame_iterator_.frame()); }
55
56 private:
57 StackFrameIterator frame_iterator_;
58 };
59
GetWasmInstanceOnStackTop(Isolate * isolate,std::initializer_list<StackFrame::Type> skipped_frame_types={ StackFrame::EXIT})60 WasmInstanceObject GetWasmInstanceOnStackTop(
61 Isolate* isolate,
62 std::initializer_list<StackFrame::Type> skipped_frame_types = {
63 StackFrame::EXIT}) {
64 return FrameFinder<WasmFrame>(isolate, skipped_frame_types)
65 .frame()
66 ->wasm_instance();
67 }
68
GetNativeContextFromWasmInstanceOnStackTop(Isolate * isolate)69 Context GetNativeContextFromWasmInstanceOnStackTop(Isolate* isolate) {
70 return GetWasmInstanceOnStackTop(isolate).native_context();
71 }
72
73 class V8_NODISCARD ClearThreadInWasmScope {
74 public:
ClearThreadInWasmScope(Isolate * isolate)75 explicit ClearThreadInWasmScope(Isolate* isolate) : isolate_(isolate) {
76 DCHECK_IMPLIES(trap_handler::IsTrapHandlerEnabled(),
77 trap_handler::IsThreadInWasm());
78 trap_handler::ClearThreadInWasm();
79 }
~ClearThreadInWasmScope()80 ~ClearThreadInWasmScope() {
81 DCHECK_IMPLIES(trap_handler::IsTrapHandlerEnabled(),
82 !trap_handler::IsThreadInWasm());
83 if (!isolate_->has_pending_exception()) {
84 trap_handler::SetThreadInWasm();
85 }
86 // Otherwise we only want to set the flag if the exception is caught in
87 // wasm. This is handled by the unwinder.
88 }
89
90 private:
91 Isolate* isolate_;
92 };
93
ThrowWasmError(Isolate * isolate,MessageTemplate message)94 Object ThrowWasmError(Isolate* isolate, MessageTemplate message) {
95 Handle<JSObject> error_obj = isolate->factory()->NewWasmRuntimeError(message);
96 JSObject::AddProperty(isolate, error_obj,
97 isolate->factory()->wasm_uncatchable_symbol(),
98 isolate->factory()->true_value(), NONE);
99 return isolate->Throw(*error_obj);
100 }
101 } // namespace
102
RUNTIME_FUNCTION(Runtime_WasmIsValidRefValue)103 RUNTIME_FUNCTION(Runtime_WasmIsValidRefValue) {
104 // This code is called from wrappers, so the "thread is wasm" flag is not set.
105 DCHECK_IMPLIES(trap_handler::IsTrapHandlerEnabled(),
106 !trap_handler::IsThreadInWasm());
107 HandleScope scope(isolate);
108 DCHECK_EQ(3, args.length());
109 // 'raw_instance' can be either a WasmInstanceObject or undefined.
110 Handle<Object> raw_instance = args.at(0);
111 Handle<Object> value = args.at(1);
112 // Make sure ValueType fits properly in a Smi.
113 STATIC_ASSERT(wasm::ValueType::kLastUsedBit + 1 <= kSmiValueSize);
114 int raw_type = args.smi_value_at(2);
115
116 const wasm::WasmModule* module =
117 raw_instance->IsWasmInstanceObject()
118 ? Handle<WasmInstanceObject>::cast(raw_instance)->module()
119 : nullptr;
120
121 wasm::ValueType type = wasm::ValueType::FromRawBitField(raw_type);
122 const char* error_message;
123
124 bool result = internal::wasm::TypecheckJSObject(isolate, module, value, type,
125 &error_message);
126 return Smi::FromInt(result);
127 }
128
RUNTIME_FUNCTION(Runtime_WasmMemoryGrow)129 RUNTIME_FUNCTION(Runtime_WasmMemoryGrow) {
130 ClearThreadInWasmScope flag_scope(isolate);
131 HandleScope scope(isolate);
132 DCHECK_EQ(2, args.length());
133 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
134 // {delta_pages} is checked to be a positive smi in the WasmMemoryGrow builtin
135 // which calls this runtime function.
136 uint32_t delta_pages = args.positive_smi_value_at(1);
137
138 int ret = WasmMemoryObject::Grow(
139 isolate, handle(instance->memory_object(), isolate), delta_pages);
140 // The WasmMemoryGrow builtin which calls this runtime function expects us to
141 // always return a Smi.
142 DCHECK(!isolate->has_pending_exception());
143 return Smi::FromInt(ret);
144 }
145
RUNTIME_FUNCTION(Runtime_ThrowWasmError)146 RUNTIME_FUNCTION(Runtime_ThrowWasmError) {
147 ClearThreadInWasmScope flag_scope(isolate);
148 HandleScope scope(isolate);
149 DCHECK_EQ(1, args.length());
150 int message_id = args.smi_value_at(0);
151 return ThrowWasmError(isolate, MessageTemplateFromInt(message_id));
152 }
153
RUNTIME_FUNCTION(Runtime_ThrowWasmStackOverflow)154 RUNTIME_FUNCTION(Runtime_ThrowWasmStackOverflow) {
155 ClearThreadInWasmScope clear_wasm_flag(isolate);
156 SealHandleScope shs(isolate);
157 DCHECK_LE(0, args.length());
158 return isolate->StackOverflow();
159 }
160
RUNTIME_FUNCTION(Runtime_WasmThrowJSTypeError)161 RUNTIME_FUNCTION(Runtime_WasmThrowJSTypeError) {
162 // The caller may be wasm or JS. Only clear the thread_in_wasm flag if the
163 // caller is wasm, and let the unwinder set it back depending on the handler.
164 if (trap_handler::IsTrapHandlerEnabled() && trap_handler::IsThreadInWasm()) {
165 trap_handler::ClearThreadInWasm();
166 }
167 HandleScope scope(isolate);
168 DCHECK_EQ(0, args.length());
169 THROW_NEW_ERROR_RETURN_FAILURE(
170 isolate, NewTypeError(MessageTemplate::kWasmTrapJSTypeError));
171 }
172
RUNTIME_FUNCTION(Runtime_WasmThrow)173 RUNTIME_FUNCTION(Runtime_WasmThrow) {
174 ClearThreadInWasmScope clear_wasm_flag(isolate);
175 HandleScope scope(isolate);
176 DCHECK_EQ(2, args.length());
177 isolate->set_context(GetNativeContextFromWasmInstanceOnStackTop(isolate));
178
179 auto tag_raw = WasmExceptionTag::cast(args[0]);
180 auto values_raw = FixedArray::cast(args[1]);
181 // TODO(wasm): Manually box because parameters are not visited yet.
182 Handle<WasmExceptionTag> tag(tag_raw, isolate);
183 Handle<FixedArray> values(values_raw, isolate);
184 Handle<WasmExceptionPackage> exception =
185 WasmExceptionPackage::New(isolate, tag, values);
186 wasm::GetWasmEngine()->SampleThrowEvent(isolate);
187 return isolate->Throw(*exception);
188 }
189
RUNTIME_FUNCTION(Runtime_WasmReThrow)190 RUNTIME_FUNCTION(Runtime_WasmReThrow) {
191 ClearThreadInWasmScope clear_wasm_flag(isolate);
192 HandleScope scope(isolate);
193 DCHECK_EQ(1, args.length());
194 wasm::GetWasmEngine()->SampleRethrowEvent(isolate);
195 return isolate->ReThrow(args[0]);
196 }
197
RUNTIME_FUNCTION(Runtime_WasmStackGuard)198 RUNTIME_FUNCTION(Runtime_WasmStackGuard) {
199 ClearThreadInWasmScope wasm_flag(isolate);
200 SealHandleScope shs(isolate);
201 DCHECK_EQ(0, args.length());
202
203 // Check if this is a real stack overflow.
204 StackLimitCheck check(isolate);
205 if (check.JsHasOverflowed()) return isolate->StackOverflow();
206
207 return isolate->stack_guard()->HandleInterrupts();
208 }
209
RUNTIME_FUNCTION(Runtime_WasmCompileLazy)210 RUNTIME_FUNCTION(Runtime_WasmCompileLazy) {
211 ClearThreadInWasmScope wasm_flag(isolate);
212 HandleScope scope(isolate);
213 DCHECK_EQ(2, args.length());
214 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
215 int func_index = args.smi_value_at(1);
216
217 #ifdef DEBUG
218 FrameFinder<WasmCompileLazyFrame> frame_finder(isolate);
219 DCHECK_EQ(*instance, frame_finder.frame()->wasm_instance());
220 #endif
221
222 DCHECK(isolate->context().is_null());
223 isolate->set_context(instance->native_context());
224 bool success = wasm::CompileLazy(isolate, instance, func_index);
225 if (!success) {
226 DCHECK(isolate->has_pending_exception());
227 return ReadOnlyRoots{isolate}.exception();
228 }
229
230 auto* native_module = instance->module_object().native_module();
231 return Smi::FromInt(native_module->GetJumpTableOffset(func_index));
232 }
233
234 namespace {
ReplaceWrapper(Isolate * isolate,Handle<WasmInstanceObject> instance,int function_index,Handle<CodeT> wrapper_code)235 void ReplaceWrapper(Isolate* isolate, Handle<WasmInstanceObject> instance,
236 int function_index, Handle<CodeT> wrapper_code) {
237 Handle<WasmInternalFunction> internal =
238 WasmInstanceObject::GetWasmInternalFunction(isolate, instance,
239 function_index)
240 .ToHandleChecked();
241 Handle<WasmExternalFunction> exported_function =
242 handle(WasmExternalFunction::cast(internal->external()), isolate);
243 exported_function->set_code(*wrapper_code, kReleaseStore);
244 WasmExportedFunctionData function_data =
245 exported_function->shared().wasm_exported_function_data();
246 function_data.set_wrapper_code(*wrapper_code);
247 }
248 } // namespace
249
RUNTIME_FUNCTION(Runtime_WasmCompileWrapper)250 RUNTIME_FUNCTION(Runtime_WasmCompileWrapper) {
251 HandleScope scope(isolate);
252 DCHECK_EQ(2, args.length());
253 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
254 Handle<WasmExportedFunctionData> function_data =
255 args.at<WasmExportedFunctionData>(1);
256 DCHECK(isolate->context().is_null());
257 isolate->set_context(instance->native_context());
258
259 const wasm::WasmModule* module = instance->module();
260 const int function_index = function_data->function_index();
261 const wasm::WasmFunction& function = module->functions[function_index];
262 const wasm::FunctionSig* sig = function.sig;
263
264 // The start function is not guaranteed to be registered as
265 // an exported function (although it is called as one).
266 // If there is no entry for the start function,
267 // the tier-up is abandoned.
268 if (WasmInstanceObject::GetWasmInternalFunction(isolate, instance,
269 function_index)
270 .is_null()) {
271 DCHECK_EQ(function_index, module->start_function_index);
272 return ReadOnlyRoots(isolate).undefined_value();
273 }
274
275 Handle<CodeT> wrapper_code = ToCodeT(
276 wasm::JSToWasmWrapperCompilationUnit::CompileSpecificJSToWasmWrapper(
277 isolate, sig, module),
278 isolate);
279
280 // Replace the wrapper for the function that triggered the tier-up.
281 // This is to verify that the wrapper is replaced, even if the function
282 // is implicitly exported and is not part of the export_table.
283 ReplaceWrapper(isolate, instance, function_index, wrapper_code);
284
285 // Iterate over all exports to replace eagerly the wrapper for all functions
286 // that share the signature of the function that tiered up.
287 for (wasm::WasmExport exp : module->export_table) {
288 if (exp.kind != wasm::kExternalFunction) {
289 continue;
290 }
291 int index = static_cast<int>(exp.index);
292 const wasm::WasmFunction& exp_function = module->functions[index];
293 if (exp_function.sig == sig && index != function_index) {
294 ReplaceWrapper(isolate, instance, index, wrapper_code);
295 }
296 }
297
298 return ReadOnlyRoots(isolate).undefined_value();
299 }
300
RUNTIME_FUNCTION(Runtime_WasmTriggerTierUp)301 RUNTIME_FUNCTION(Runtime_WasmTriggerTierUp) {
302 ClearThreadInWasmScope clear_wasm_flag(isolate);
303 HandleScope scope(isolate);
304 DCHECK_EQ(1, args.length());
305 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
306
307 // We're reusing this interrupt mechanism to interrupt long-running loops.
308 StackLimitCheck check(isolate);
309 DCHECK(!check.JsHasOverflowed());
310 if (check.InterruptRequested()) {
311 Object result = isolate->stack_guard()->HandleInterrupts();
312 if (result.IsException()) return result;
313 }
314
315 FrameFinder<WasmFrame> frame_finder(isolate);
316 int func_index = frame_finder.frame()->function_index();
317 auto* native_module = instance->module_object().native_module();
318
319 wasm::TriggerTierUp(isolate, native_module, func_index, instance);
320
321 return ReadOnlyRoots(isolate).undefined_value();
322 }
323
RUNTIME_FUNCTION(Runtime_WasmAtomicNotify)324 RUNTIME_FUNCTION(Runtime_WasmAtomicNotify) {
325 ClearThreadInWasmScope clear_wasm_flag(isolate);
326 HandleScope scope(isolate);
327 DCHECK_EQ(3, args.length());
328 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
329 double offset_double = args.number_value_at(1);
330 uintptr_t offset = static_cast<uintptr_t>(offset_double);
331 uint32_t count = NumberToUint32(args[2]);
332 Handle<JSArrayBuffer> array_buffer{instance->memory_object().array_buffer(),
333 isolate};
334 // Should have trapped if address was OOB.
335 DCHECK_LT(offset, array_buffer->byte_length());
336 if (!array_buffer->is_shared()) return Smi::FromInt(0);
337 return FutexEmulation::Wake(array_buffer, offset, count);
338 }
339
RUNTIME_FUNCTION(Runtime_WasmI32AtomicWait)340 RUNTIME_FUNCTION(Runtime_WasmI32AtomicWait) {
341 ClearThreadInWasmScope clear_wasm_flag(isolate);
342 HandleScope scope(isolate);
343 DCHECK_EQ(4, args.length());
344 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
345 double offset_double = args.number_value_at(1);
346 uintptr_t offset = static_cast<uintptr_t>(offset_double);
347 int32_t expected_value = NumberToInt32(args[2]);
348 Handle<BigInt> timeout_ns = args.at<BigInt>(3);
349
350 Handle<JSArrayBuffer> array_buffer{instance->memory_object().array_buffer(),
351 isolate};
352 // Should have trapped if address was OOB.
353 DCHECK_LT(offset, array_buffer->byte_length());
354
355 // Trap if memory is not shared, or wait is not allowed on the isolate
356 if (!array_buffer->is_shared() || !isolate->allow_atomics_wait()) {
357 return ThrowWasmError(isolate, MessageTemplate::kAtomicsWaitNotAllowed);
358 }
359 return FutexEmulation::WaitWasm32(isolate, array_buffer, offset,
360 expected_value, timeout_ns->AsInt64());
361 }
362
RUNTIME_FUNCTION(Runtime_WasmI64AtomicWait)363 RUNTIME_FUNCTION(Runtime_WasmI64AtomicWait) {
364 ClearThreadInWasmScope clear_wasm_flag(isolate);
365 HandleScope scope(isolate);
366 DCHECK_EQ(4, args.length());
367 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
368 double offset_double = args.number_value_at(1);
369 uintptr_t offset = static_cast<uintptr_t>(offset_double);
370 Handle<BigInt> expected_value = args.at<BigInt>(2);
371 Handle<BigInt> timeout_ns = args.at<BigInt>(3);
372
373 Handle<JSArrayBuffer> array_buffer{instance->memory_object().array_buffer(),
374 isolate};
375 // Should have trapped if address was OOB.
376 DCHECK_LT(offset, array_buffer->byte_length());
377
378 // Trap if memory is not shared, or if wait is not allowed on the isolate
379 if (!array_buffer->is_shared() || !isolate->allow_atomics_wait()) {
380 return ThrowWasmError(isolate, MessageTemplate::kAtomicsWaitNotAllowed);
381 }
382 return FutexEmulation::WaitWasm64(isolate, array_buffer, offset,
383 expected_value->AsInt64(),
384 timeout_ns->AsInt64());
385 }
386
387 namespace {
ThrowTableOutOfBounds(Isolate * isolate,Handle<WasmInstanceObject> instance)388 Object ThrowTableOutOfBounds(Isolate* isolate,
389 Handle<WasmInstanceObject> instance) {
390 // Handle out-of-bounds access here in the runtime call, rather
391 // than having the lower-level layers deal with JS exceptions.
392 if (isolate->context().is_null()) {
393 isolate->set_context(instance->native_context());
394 }
395 return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds);
396 }
397 } // namespace
398
RUNTIME_FUNCTION(Runtime_WasmRefFunc)399 RUNTIME_FUNCTION(Runtime_WasmRefFunc) {
400 ClearThreadInWasmScope flag_scope(isolate);
401 HandleScope scope(isolate);
402 DCHECK_EQ(2, args.length());
403 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
404 uint32_t function_index = args.positive_smi_value_at(1);
405
406 return *WasmInstanceObject::GetOrCreateWasmInternalFunction(isolate, instance,
407 function_index);
408 }
409
RUNTIME_FUNCTION(Runtime_WasmFunctionTableGet)410 RUNTIME_FUNCTION(Runtime_WasmFunctionTableGet) {
411 ClearThreadInWasmScope flag_scope(isolate);
412 HandleScope scope(isolate);
413 DCHECK_EQ(3, args.length());
414 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
415 uint32_t table_index = args.positive_smi_value_at(1);
416 uint32_t entry_index = args.positive_smi_value_at(2);
417 DCHECK_LT(table_index, instance->tables().length());
418 auto table = handle(
419 WasmTableObject::cast(instance->tables().get(table_index)), isolate);
420 // We only use the runtime call for lazily initialized function references.
421 DCHECK(
422 table->instance().IsUndefined()
423 ? table->type() == wasm::kWasmFuncRef
424 : IsSubtypeOf(table->type(), wasm::kWasmFuncRef,
425 WasmInstanceObject::cast(table->instance()).module()));
426
427 if (!WasmTableObject::IsInBounds(isolate, table, entry_index)) {
428 return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds);
429 }
430
431 return *WasmTableObject::Get(isolate, table, entry_index);
432 }
433
RUNTIME_FUNCTION(Runtime_WasmFunctionTableSet)434 RUNTIME_FUNCTION(Runtime_WasmFunctionTableSet) {
435 ClearThreadInWasmScope flag_scope(isolate);
436 HandleScope scope(isolate);
437 DCHECK_EQ(4, args.length());
438 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
439 uint32_t table_index = args.positive_smi_value_at(1);
440 uint32_t entry_index = args.positive_smi_value_at(2);
441 Object element_raw = args[3];
442 // TODO(wasm): Manually box because parameters are not visited yet.
443 Handle<Object> element(element_raw, isolate);
444 DCHECK_LT(table_index, instance->tables().length());
445 auto table = handle(
446 WasmTableObject::cast(instance->tables().get(table_index)), isolate);
447 // We only use the runtime call for function references.
448 DCHECK(
449 table->instance().IsUndefined()
450 ? table->type() == wasm::kWasmFuncRef
451 : IsSubtypeOf(table->type(), wasm::kWasmFuncRef,
452 WasmInstanceObject::cast(table->instance()).module()));
453
454 if (!WasmTableObject::IsInBounds(isolate, table, entry_index)) {
455 return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds);
456 }
457 WasmTableObject::Set(isolate, table, entry_index, element);
458 return ReadOnlyRoots(isolate).undefined_value();
459 }
460
RUNTIME_FUNCTION(Runtime_WasmTableInit)461 RUNTIME_FUNCTION(Runtime_WasmTableInit) {
462 ClearThreadInWasmScope flag_scope(isolate);
463 HandleScope scope(isolate);
464 DCHECK_EQ(6, args.length());
465 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
466 uint32_t table_index = args.positive_smi_value_at(1);
467 uint32_t elem_segment_index = args.positive_smi_value_at(2);
468 static_assert(
469 wasm::kV8MaxWasmTableSize < kSmiMaxValue,
470 "Make sure clamping to Smi range doesn't make an invalid call valid");
471 uint32_t dst = args.positive_smi_value_at(3);
472 uint32_t src = args.positive_smi_value_at(4);
473 uint32_t count = args.positive_smi_value_at(5);
474
475 DCHECK(!isolate->context().is_null());
476
477 bool oob = !WasmInstanceObject::InitTableEntries(
478 isolate, instance, table_index, elem_segment_index, dst, src, count);
479 if (oob) return ThrowTableOutOfBounds(isolate, instance);
480 return ReadOnlyRoots(isolate).undefined_value();
481 }
482
RUNTIME_FUNCTION(Runtime_WasmTableCopy)483 RUNTIME_FUNCTION(Runtime_WasmTableCopy) {
484 ClearThreadInWasmScope flag_scope(isolate);
485 HandleScope scope(isolate);
486 DCHECK_EQ(6, args.length());
487 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
488 uint32_t table_dst_index = args.positive_smi_value_at(1);
489 uint32_t table_src_index = args.positive_smi_value_at(2);
490 static_assert(
491 wasm::kV8MaxWasmTableSize < kSmiMaxValue,
492 "Make sure clamping to Smi range doesn't make an invalid call valid");
493 uint32_t dst = args.positive_smi_value_at(3);
494 uint32_t src = args.positive_smi_value_at(4);
495 uint32_t count = args.positive_smi_value_at(5);
496
497 DCHECK(!isolate->context().is_null());
498
499 bool oob = !WasmInstanceObject::CopyTableEntries(
500 isolate, instance, table_dst_index, table_src_index, dst, src, count);
501 if (oob) return ThrowTableOutOfBounds(isolate, instance);
502 return ReadOnlyRoots(isolate).undefined_value();
503 }
504
RUNTIME_FUNCTION(Runtime_WasmTableGrow)505 RUNTIME_FUNCTION(Runtime_WasmTableGrow) {
506 ClearThreadInWasmScope flag_scope(isolate);
507 HandleScope scope(isolate);
508 DCHECK_EQ(4, args.length());
509 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
510 uint32_t table_index = args.positive_smi_value_at(1);
511 Object value_raw = args[2];
512 // TODO(wasm): Manually box because parameters are not visited yet.
513 Handle<Object> value(value_raw, isolate);
514 uint32_t delta = args.positive_smi_value_at(3);
515
516 Handle<WasmTableObject> table(
517 WasmTableObject::cast(instance->tables().get(table_index)), isolate);
518 int result = WasmTableObject::Grow(isolate, table, delta, value);
519
520 return Smi::FromInt(result);
521 }
522
RUNTIME_FUNCTION(Runtime_WasmTableFill)523 RUNTIME_FUNCTION(Runtime_WasmTableFill) {
524 ClearThreadInWasmScope flag_scope(isolate);
525 HandleScope scope(isolate);
526 DCHECK_EQ(5, args.length());
527 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
528 uint32_t table_index = args.positive_smi_value_at(1);
529 uint32_t start = args.positive_smi_value_at(2);
530 Object value_raw = args[3];
531 // TODO(wasm): Manually box because parameters are not visited yet.
532 Handle<Object> value(value_raw, isolate);
533 uint32_t count = args.positive_smi_value_at(4);
534
535 Handle<WasmTableObject> table(
536 WasmTableObject::cast(instance->tables().get(table_index)), isolate);
537
538 uint32_t table_size = table->current_length();
539
540 if (start > table_size) {
541 return ThrowTableOutOfBounds(isolate, instance);
542 }
543
544 // Even when table.fill goes out-of-bounds, as many entries as possible are
545 // put into the table. Only afterwards we trap.
546 uint32_t fill_count = std::min(count, table_size - start);
547 if (fill_count < count) {
548 return ThrowTableOutOfBounds(isolate, instance);
549 }
550 WasmTableObject::Fill(isolate, table, start, value, fill_count);
551
552 return ReadOnlyRoots(isolate).undefined_value();
553 }
554
RUNTIME_FUNCTION(Runtime_WasmDebugBreak)555 RUNTIME_FUNCTION(Runtime_WasmDebugBreak) {
556 ClearThreadInWasmScope flag_scope(isolate);
557 HandleScope scope(isolate);
558 DCHECK_EQ(0, args.length());
559 FrameFinder<WasmFrame> frame_finder(
560 isolate, {StackFrame::EXIT, StackFrame::WASM_DEBUG_BREAK});
561 WasmFrame* frame = frame_finder.frame();
562 auto instance = handle(frame->wasm_instance(), isolate);
563 auto script = handle(instance->module_object().script(), isolate);
564 auto* debug_info = instance->module_object().native_module()->GetDebugInfo();
565 isolate->set_context(instance->native_context());
566
567 // Stepping can repeatedly create code, and code GC requires stack guards to
568 // be executed on all involved isolates. Proactively do this here.
569 StackLimitCheck check(isolate);
570 if (check.InterruptRequested()) {
571 Object interrupt_object = isolate->stack_guard()->HandleInterrupts();
572 // Interrupt handling can create an exception, including the
573 // termination exception.
574 if (interrupt_object.IsException(isolate)) return interrupt_object;
575 DCHECK(interrupt_object.IsUndefined(isolate));
576 }
577
578 // Enter the debugger.
579 DebugScope debug_scope(isolate->debug());
580 bool paused_on_instrumentation = false;
581 // Check for instrumentation breakpoint.
582 DCHECK_EQ(script->break_on_entry(), !!instance->break_on_entry());
583 if (script->break_on_entry()) {
584 MaybeHandle<FixedArray> maybe_on_entry_breakpoints =
585 WasmScript::CheckBreakPoints(isolate, script,
586 WasmScript::kOnEntryBreakpointPosition,
587 frame->id());
588 script->set_break_on_entry(false);
589 // Update the "break_on_entry" flag on all live instances.
590 i::WeakArrayList weak_instance_list = script->wasm_weak_instance_list();
591 for (int i = 0; i < weak_instance_list.length(); ++i) {
592 if (weak_instance_list.Get(i)->IsCleared()) continue;
593 i::WasmInstanceObject::cast(weak_instance_list.Get(i)->GetHeapObject())
594 .set_break_on_entry(false);
595 }
596 DCHECK(!instance->break_on_entry());
597 if (!maybe_on_entry_breakpoints.is_null()) {
598 isolate->debug()->OnInstrumentationBreak();
599 paused_on_instrumentation = true;
600 }
601 }
602
603 if (debug_info->IsStepping(frame)) {
604 debug_info->ClearStepping(isolate);
605 StepAction step_action = isolate->debug()->last_step_action();
606 isolate->debug()->ClearStepping();
607 isolate->debug()->OnDebugBreak(isolate->factory()->empty_fixed_array(),
608 step_action);
609 return ReadOnlyRoots(isolate).undefined_value();
610 }
611
612 // Check whether we hit a breakpoint.
613 Handle<FixedArray> breakpoints;
614 if (WasmScript::CheckBreakPoints(isolate, script, frame->position(),
615 frame->id())
616 .ToHandle(&breakpoints)) {
617 debug_info->ClearStepping(isolate);
618 StepAction step_action = isolate->debug()->last_step_action();
619 isolate->debug()->ClearStepping();
620 if (isolate->debug()->break_points_active()) {
621 // We hit one or several breakpoints. Notify the debug listeners.
622 isolate->debug()->OnDebugBreak(breakpoints, step_action);
623 }
624 return ReadOnlyRoots(isolate).undefined_value();
625 }
626
627 // We only hit the instrumentation breakpoint, and there is no other reason to
628 // break.
629 if (paused_on_instrumentation) {
630 return ReadOnlyRoots(isolate).undefined_value();
631 }
632
633 // We did not hit a breakpoint. If we are in stepping code, but the user did
634 // not request stepping, clear this (to save further calls into this runtime
635 // function).
636 debug_info->ClearStepping(frame);
637
638 return ReadOnlyRoots(isolate).undefined_value();
639 }
640
641 namespace {
ArrayElementAddress(Handle<WasmArray> array,uint32_t index,int element_size_bytes)642 inline void* ArrayElementAddress(Handle<WasmArray> array, uint32_t index,
643 int element_size_bytes) {
644 return reinterpret_cast<void*>(array->ptr() + WasmArray::kHeaderSize -
645 kHeapObjectTag + index * element_size_bytes);
646 }
647 } // namespace
648
649 // Assumes copy ranges are in-bounds and copy length > 0.
RUNTIME_FUNCTION(Runtime_WasmArrayCopy)650 RUNTIME_FUNCTION(Runtime_WasmArrayCopy) {
651 ClearThreadInWasmScope flag_scope(isolate);
652 HandleScope scope(isolate);
653 DCHECK_EQ(5, args.length());
654 Handle<WasmArray> dst_array = args.at<WasmArray>(0);
655 uint32_t dst_index = args.positive_smi_value_at(1);
656 Handle<WasmArray> src_array = args.at<WasmArray>(2);
657 uint32_t src_index = args.positive_smi_value_at(3);
658 uint32_t length = args.positive_smi_value_at(4);
659 DCHECK_GT(length, 0);
660 bool overlapping_ranges =
661 dst_array->ptr() == src_array->ptr() &&
662 (dst_index < src_index ? dst_index + length > src_index
663 : src_index + length > dst_index);
664 wasm::ValueType element_type = src_array->type()->element_type();
665 if (element_type.is_reference()) {
666 ObjectSlot dst_slot = dst_array->ElementSlot(dst_index);
667 ObjectSlot src_slot = src_array->ElementSlot(src_index);
668 if (overlapping_ranges) {
669 isolate->heap()->MoveRange(*dst_array, dst_slot, src_slot, length,
670 UPDATE_WRITE_BARRIER);
671 } else {
672 isolate->heap()->CopyRange(*dst_array, dst_slot, src_slot, length,
673 UPDATE_WRITE_BARRIER);
674 }
675 } else {
676 int element_size_bytes = element_type.value_kind_size();
677 void* dst = ArrayElementAddress(dst_array, dst_index, element_size_bytes);
678 void* src = ArrayElementAddress(src_array, src_index, element_size_bytes);
679 size_t copy_size = length * element_size_bytes;
680 if (overlapping_ranges) {
681 MemMove(dst, src, copy_size);
682 } else {
683 MemCopy(dst, src, copy_size);
684 }
685 }
686 return ReadOnlyRoots(isolate).undefined_value();
687 }
688
689 // Returns
690 // - the new array if the operation succeeds,
691 // - Smi(0) if the requested array length is too large,
692 // - Smi(1) if the data segment ran out-of-bounds.
RUNTIME_FUNCTION(Runtime_WasmArrayInitFromData)693 RUNTIME_FUNCTION(Runtime_WasmArrayInitFromData) {
694 ClearThreadInWasmScope flag_scope(isolate);
695 HandleScope scope(isolate);
696 DCHECK_EQ(5, args.length());
697 Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
698 uint32_t data_segment = args.positive_smi_value_at(1);
699 uint32_t offset = args.positive_smi_value_at(2);
700 uint32_t length = args.positive_smi_value_at(3);
701 Handle<Map> rtt = args.at<Map>(4);
702 uint32_t element_size = WasmArray::DecodeElementSizeFromMap(*rtt);
703 uint32_t length_in_bytes = length * element_size;
704
705 if (length > static_cast<uint32_t>(WasmArray::MaxLength(element_size))) {
706 return Smi::FromInt(wasm::kArrayInitFromDataArrayTooLargeErrorCode);
707 }
708 // The check above implies no overflow.
709 DCHECK_EQ(length_in_bytes / element_size, length);
710 if (!base::IsInBounds<uint32_t>(
711 offset, length_in_bytes,
712 instance->data_segment_sizes()[data_segment])) {
713 return Smi::FromInt(wasm::kArrayInitFromDataSegmentOutOfBoundsErrorCode);
714 }
715
716 Address source = instance->data_segment_starts()[data_segment] + offset;
717 return *isolate->factory()->NewWasmArrayFromMemory(length, rtt, source);
718 }
719
720 namespace {
721 // Synchronize the stack limit with the active continuation for stack-switching.
722 // This can be done before or after changing the stack pointer itself, as long
723 // as we update both before the next stack check.
724 // {StackGuard::SetStackLimit} doesn't update the value of the jslimit if it
725 // contains a sentinel value, and it is also thread-safe. So if an interrupt is
726 // requested before, during or after this call, it will be preserved and handled
727 // at the next stack check.
SyncStackLimit(Isolate * isolate)728 void SyncStackLimit(Isolate* isolate) {
729 DisallowGarbageCollection no_gc;
730 auto continuation = WasmContinuationObject::cast(
731 *isolate->roots_table().slot(RootIndex::kActiveContinuation));
732 auto stack = Managed<wasm::StackMemory>::cast(continuation.stack()).get();
733 if (FLAG_trace_wasm_stack_switching) {
734 PrintF("Switch to stack #%d\n", stack->id());
735 }
736 uintptr_t limit = reinterpret_cast<uintptr_t>(stack->jmpbuf()->stack_limit);
737 isolate->stack_guard()->SetStackLimit(limit);
738 }
739 } // namespace
740
741 // Allocate a new continuation, and prepare for stack switching by updating the
742 // active continuation, active suspender and stack limit.
RUNTIME_FUNCTION(Runtime_WasmAllocateContinuation)743 RUNTIME_FUNCTION(Runtime_WasmAllocateContinuation) {
744 CHECK(FLAG_experimental_wasm_stack_switching);
745 HandleScope scope(isolate);
746 Handle<WasmSuspenderObject> suspender = args.at<WasmSuspenderObject>(0);
747
748 // Update the continuation state.
749 auto parent =
750 handle(WasmContinuationObject::cast(
751 *isolate->roots_table().slot(RootIndex::kActiveContinuation)),
752 isolate);
753 Handle<WasmContinuationObject> target =
754 WasmContinuationObject::New(isolate, parent);
755 auto target_stack =
756 Managed<wasm::StackMemory>::cast(target->stack()).get().get();
757 isolate->wasm_stacks()->Add(target_stack);
758 isolate->roots_table().slot(RootIndex::kActiveContinuation).store(*target);
759
760 // Update the suspender state.
761 FullObjectSlot active_suspender_slot =
762 isolate->roots_table().slot(RootIndex::kActiveSuspender);
763 suspender->set_parent(HeapObject::cast(*active_suspender_slot));
764 if (!(*active_suspender_slot).IsUndefined()) {
765 WasmSuspenderObject::cast(*active_suspender_slot)
766 .set_state(WasmSuspenderObject::Inactive);
767 }
768 suspender->set_state(WasmSuspenderObject::State::Active);
769 suspender->set_continuation(*target);
770 active_suspender_slot.store(*suspender);
771
772 SyncStackLimit(isolate);
773 return *target;
774 }
775
776 // Update the stack limit after a stack switch, and preserve pending interrupts.
RUNTIME_FUNCTION(Runtime_WasmSyncStackLimit)777 RUNTIME_FUNCTION(Runtime_WasmSyncStackLimit) {
778 CHECK(FLAG_experimental_wasm_stack_switching);
779 SyncStackLimit(isolate);
780 return ReadOnlyRoots(isolate).undefined_value();
781 }
782
783 // Takes a promise and a suspender, and returns promise.then(onFulfilled), where
784 // onFulfilled resumes the suspender.
RUNTIME_FUNCTION(Runtime_WasmCreateResumePromise)785 RUNTIME_FUNCTION(Runtime_WasmCreateResumePromise) {
786 CHECK(FLAG_experimental_wasm_stack_switching);
787 HandleScope scope(isolate);
788 Handle<Object> promise = args.at(0);
789 Handle<WasmSuspenderObject> suspender = args.at<WasmSuspenderObject>(1);
790
791 i::Handle<i::Object> argv[] = {handle(suspender->resume(), isolate)};
792 i::Handle<i::Object> result;
793 bool has_pending_exception =
794 !i::Execution::CallBuiltin(isolate, isolate->promise_then(), promise,
795 arraysize(argv), argv)
796 .ToHandle(&result);
797 // TODO(thibaudm): Propagate exception.
798 CHECK(!has_pending_exception);
799 return *result;
800 }
801
802 } // namespace internal
803 } // namespace v8
804