[wasm-c-api] Add tests and fixes
In a new test suite: "wasm-api-tests", using a new binary "wasm_api_tests", powered by gtest/gmock (like unittests). Also fix a bunch of issues that these tests uncovered, mostly to ensure that the stack is walkable. Change-Id: I1d5604eea85da078ebecd4ebb7383647595f16ac Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1627539 Commit-Queue: Jakob Kummerow <jkummerow@chromium.org> Reviewed-by: Michael Starzinger <mstarzinger@chromium.org> Cr-Commit-Position: refs/heads/master@{#61885}
This commit is contained in:
parent
68da88559e
commit
f5ab7d38be
1
BUILD.gn
1
BUILD.gn
@ -3761,6 +3761,7 @@ v8_static_library("wee8") {
|
||||
sources = [
|
||||
### gcmole(all) ###
|
||||
"src/wasm/c-api.cc",
|
||||
"src/wasm/c-api.h",
|
||||
"third_party/wasm-api/wasm.h",
|
||||
"third_party/wasm-api/wasm.hh",
|
||||
]
|
||||
|
@ -833,6 +833,14 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
break;
|
||||
case kArchCallCFunction: {
|
||||
int const num_parameters = MiscField::decode(instr->opcode());
|
||||
// Put the return address in a stack slot.
|
||||
if (linkage()->GetIncomingDescriptor()->kind() ==
|
||||
CallDescriptor::kCallWasmImportWrapper) {
|
||||
// WasmCapiFunctionWrappers, which are reusing the WasmImportWrapper
|
||||
// call descriptor, also need access to the PC.
|
||||
// TODO(jkummerow): Separate the call descriptors for clarity.
|
||||
__ str(pc, MemOperand(fp, WasmExitFrameConstants::kCallingPCOffset));
|
||||
}
|
||||
if (instr->InputAt(0)->IsImmediate()) {
|
||||
ExternalReference ref = i.InputExternalReference(0);
|
||||
__ CallCFunction(ref, num_parameters);
|
||||
@ -840,6 +848,7 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
Register func = i.InputRegister(0);
|
||||
__ CallCFunction(func, num_parameters);
|
||||
}
|
||||
RecordSafepoint(instr->reference_map(), Safepoint::kNoLazyDeopt);
|
||||
frame_access_state()->SetFrameAccessToDefault();
|
||||
// Ideally, we should decrement SP delta to match the change of stack
|
||||
// pointer in CallCFunction. However, for certain architectures (e.g.
|
||||
@ -3009,6 +3018,9 @@ void CodeGenerator::AssembleConstructFrame() {
|
||||
__ ldr(kWasmInstanceRegister,
|
||||
FieldMemOperand(kWasmInstanceRegister, Tuple2::kValue1Offset));
|
||||
__ Push(kWasmInstanceRegister);
|
||||
// Reserve PC slot space for WasmCapiFunction wrappers.
|
||||
// TODO(jkummerow): Separate the call descriptors for clarity.
|
||||
__ AllocateStackSpace(kSystemPointerSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -744,6 +744,19 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
break;
|
||||
case kArchCallCFunction: {
|
||||
int const num_parameters = MiscField::decode(instr->opcode());
|
||||
// Put the return address in a stack slot.
|
||||
Register scratch = x8;
|
||||
Label return_location;
|
||||
if (linkage()->GetIncomingDescriptor()->kind() ==
|
||||
CallDescriptor::kCallWasmImportWrapper) {
|
||||
// WasmCapiFunctionWrappers, which are reusing the WasmImportWrapper
|
||||
// call descriptor, need access to the calling PC.
|
||||
// TODO(jkummerow): Separate the call descriptors for clarity.
|
||||
__ Adr(scratch, &return_location);
|
||||
__ Str(scratch,
|
||||
MemOperand(fp, WasmExitFrameConstants::kCallingPCOffset));
|
||||
}
|
||||
|
||||
if (instr->InputAt(0)->IsImmediate()) {
|
||||
ExternalReference ref = i.InputExternalReference(0);
|
||||
__ CallCFunction(ref, num_parameters, 0);
|
||||
@ -751,6 +764,8 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
Register func = i.InputRegister(0);
|
||||
__ CallCFunction(func, num_parameters, 0);
|
||||
}
|
||||
__ Bind(&return_location);
|
||||
RecordSafepoint(instr->reference_map(), Safepoint::kNoLazyDeopt);
|
||||
frame_access_state()->SetFrameAccessToDefault();
|
||||
// Ideally, we should decrement SP delta to match the change of stack
|
||||
// pointer in CallCFunction. However, for certain architectures (e.g.
|
||||
@ -2553,7 +2568,7 @@ void CodeGenerator::AssembleConstructFrame() {
|
||||
kWasmInstanceRegister,
|
||||
FieldMemOperand(kWasmInstanceRegister, Tuple2::kValue1Offset));
|
||||
__ Claim(required_slots +
|
||||
2); // Claim extra slots for marker + instance.
|
||||
3); // Claim extra slots for marker + instance + pc.
|
||||
Register scratch = temps.AcquireX();
|
||||
__ Mov(scratch,
|
||||
StackFrame::TypeToMarker(info()->GetOutputStackFrameType()));
|
||||
|
@ -813,6 +813,23 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
break;
|
||||
case kArchCallCFunction: {
|
||||
int const num_parameters = MiscField::decode(instr->opcode());
|
||||
Label return_location;
|
||||
if (linkage()->GetIncomingDescriptor()->kind() ==
|
||||
CallDescriptor::kCallWasmImportWrapper) {
|
||||
// WasmCapiFunctionWrappers, which are reusing the WasmImportWrapper
|
||||
// call descriptor, also need access to the PC.
|
||||
// TODO(jkummerow): Separate the call descriptors for clarity.
|
||||
Register scratch = eax;
|
||||
__ push(scratch);
|
||||
__ PushPC();
|
||||
int pc = __ pc_offset();
|
||||
__ pop(scratch);
|
||||
__ sub(scratch, Immediate(pc + Code::kHeaderSize - kHeapObjectTag));
|
||||
__ add(scratch, Immediate::CodeRelativeOffset(&return_location));
|
||||
__ mov(MemOperand(ebp, WasmExitFrameConstants::kCallingPCOffset),
|
||||
scratch);
|
||||
__ pop(scratch);
|
||||
}
|
||||
if (HasImmediateInput(instr, 0)) {
|
||||
ExternalReference ref = i.InputExternalReference(0);
|
||||
__ CallCFunction(ref, num_parameters);
|
||||
@ -820,6 +837,8 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
Register func = i.InputRegister(0);
|
||||
__ CallCFunction(func, num_parameters);
|
||||
}
|
||||
__ bind(&return_location);
|
||||
RecordSafepoint(instr->reference_map(), Safepoint::kNoLazyDeopt);
|
||||
frame_access_state()->SetFrameAccessToDefault();
|
||||
// Ideally, we should decrement SP delta to match the change of stack
|
||||
// pointer in CallCFunction. However, for certain architectures (e.g.
|
||||
@ -4229,6 +4248,9 @@ void CodeGenerator::AssembleConstructFrame() {
|
||||
Operand(kWasmInstanceRegister,
|
||||
Tuple2::kValue1Offset - kHeapObjectTag));
|
||||
__ push(kWasmInstanceRegister);
|
||||
// Reserve PC slot space for WasmCapiFunction wrappers.
|
||||
// TODO(jkummerow): Separate the call descriptors for clarity.
|
||||
__ AllocateStackSpace(kSystemPointerSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -901,6 +901,16 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
break;
|
||||
case kArchCallCFunction: {
|
||||
int const num_parameters = MiscField::decode(instr->opcode());
|
||||
Label return_location;
|
||||
if (linkage()->GetIncomingDescriptor()->kind() ==
|
||||
CallDescriptor::kCallWasmImportWrapper) {
|
||||
// WasmCapiFunctionWrappers, which are reusing the WasmImportWrapper
|
||||
// call descriptor, also need access to the PC.
|
||||
// TODO(jkummerow): Separate the call descriptors for clarity.
|
||||
__ leaq(kScratchRegister, Operand(&return_location, 0));
|
||||
__ movq(MemOperand(rbp, WasmExitFrameConstants::kCallingPCOffset),
|
||||
kScratchRegister);
|
||||
}
|
||||
if (HasImmediateInput(instr, 0)) {
|
||||
ExternalReference ref = i.InputExternalReference(0);
|
||||
__ CallCFunction(ref, num_parameters);
|
||||
@ -908,6 +918,8 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
Register func = i.InputRegister(0);
|
||||
__ CallCFunction(func, num_parameters);
|
||||
}
|
||||
__ bind(&return_location);
|
||||
RecordSafepoint(instr->reference_map(), Safepoint::kNoLazyDeopt);
|
||||
frame_access_state()->SetFrameAccessToDefault();
|
||||
// Ideally, we should decrement SP delta to match the change of stack
|
||||
// pointer in CallCFunction. However, for certain architectures (e.g.
|
||||
@ -3746,6 +3758,9 @@ void CodeGenerator::AssembleConstructFrame() {
|
||||
kWasmInstanceRegister,
|
||||
FieldOperand(kWasmInstanceRegister, Tuple2::kValue1Offset));
|
||||
__ pushq(kWasmInstanceRegister);
|
||||
// Reserve PC slot space for WasmCapiFunction wrappers.
|
||||
// TODO(jkummerow): Separate the call descriptors for clarity.
|
||||
__ AllocateStackSpace(kSystemPointerSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,8 +147,12 @@ int CallDescriptor::CalculateFixedFrameSize() const {
|
||||
case kCallBuiltinPointer:
|
||||
return TypedFrameConstants::kFixedSlotCount;
|
||||
case kCallWasmFunction:
|
||||
case kCallWasmImportWrapper:
|
||||
return WasmCompiledFrameConstants::kFixedSlotCount;
|
||||
case kCallWasmImportWrapper:
|
||||
// TODO(jkummerow): Introduce a separate "wasm-to-capi" frame type,
|
||||
// and let other CallWasmImportWrapper frames go back to having the
|
||||
// same size as CallWasmFunction frames.
|
||||
return WasmExitFrameConstants::kFixedSlotCount;
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
@ -5675,7 +5675,14 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
|
||||
MachineType::TypeCompressedTagged());
|
||||
Node* host_data = LOAD_RAW(
|
||||
sfi_data, WasmCapiFunctionData::kEmbedderDataOffset - kHeapObjectTag,
|
||||
MachineType::TypeCompressedTagged());
|
||||
MachineType::Pointer());
|
||||
|
||||
BuildModifyThreadInWasmFlag(false);
|
||||
Node* isolate_root =
|
||||
LOAD_INSTANCE_FIELD(IsolateRoot, MachineType::Pointer());
|
||||
Node* fp_value = graph()->NewNode(mcgraph()->machine()->LoadFramePointer());
|
||||
STORE_RAW(isolate_root, Isolate::c_entry_fp_offset(), fp_value,
|
||||
MachineType::PointerRepresentation(), kNoWriteBarrier);
|
||||
|
||||
// TODO(jkummerow): Load the address from the {host_data}, and cache
|
||||
// wrappers per signature.
|
||||
@ -5687,12 +5694,10 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
|
||||
MachineType host_sig_types[] = {
|
||||
MachineType::Pointer(), MachineType::Pointer(), MachineType::Pointer()};
|
||||
MachineSignature host_sig(1, 2, host_sig_types);
|
||||
// TODO(jkummerow): Set/unset the "thread-in-wasm" flag before and after
|
||||
// the C call, using {BuildModifyThreadInWasmFlag}.
|
||||
// TODO(jkummerow): This is currently not safe for stack walking. Add
|
||||
// test coverage for that, and fix it!
|
||||
Node* return_value = BuildCCall(&host_sig, function, host_data, values);
|
||||
|
||||
BuildModifyThreadInWasmFlag(true);
|
||||
|
||||
Node* exception_branch =
|
||||
graph()->NewNode(mcgraph()->common()->Branch(BranchHint::kTrue),
|
||||
graph()->NewNode(mcgraph()->machine()->WordEqual(),
|
||||
|
@ -256,6 +256,13 @@ class WasmCompiledFrameConstants : public TypedFrameConstants {
|
||||
DEFINE_TYPED_FRAME_SIZES(1);
|
||||
};
|
||||
|
||||
class WasmExitFrameConstants : public WasmCompiledFrameConstants {
|
||||
public:
|
||||
// FP-relative.
|
||||
static const int kCallingPCOffset = TYPED_FRAME_PUSHED_VALUE_OFFSET(1);
|
||||
DEFINE_TYPED_FRAME_SIZES(2);
|
||||
};
|
||||
|
||||
class BuiltinContinuationFrameConstants : public TypedFrameConstants {
|
||||
public:
|
||||
// FP-relative.
|
||||
|
@ -159,11 +159,20 @@ void StackTraceFrameIterator::Advance() {
|
||||
|
||||
bool StackTraceFrameIterator::IsValidFrame(StackFrame* frame) const {
|
||||
if (frame->is_java_script()) {
|
||||
JavaScriptFrame* jsFrame = static_cast<JavaScriptFrame*>(frame);
|
||||
if (!jsFrame->function().IsJSFunction()) return false;
|
||||
return jsFrame->function().shared().IsSubjectToDebugging();
|
||||
JavaScriptFrame* js_frame = static_cast<JavaScriptFrame*>(frame);
|
||||
if (!js_frame->function().IsJSFunction()) return false;
|
||||
return js_frame->function().shared().IsSubjectToDebugging();
|
||||
}
|
||||
// Apart from JavaScript frames, only Wasm frames are valid, with the
|
||||
// exception of Wasm-to-Capi frames.
|
||||
// TODO(jkummerow): Give Wasm-to-Capi frames their own marker.
|
||||
if (frame->is_wasm_compiled()) {
|
||||
wasm::WasmCodeRefScope scope;
|
||||
if (static_cast<WasmCompiledFrame*>(frame)->wasm_code()->kind() ==
|
||||
wasm::WasmCode::kWasmToCapiWrapper) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// apart from javascript, only wasm is valid
|
||||
return frame->is_wasm();
|
||||
}
|
||||
|
||||
@ -667,10 +676,17 @@ Address ExitFrame::GetCallerStackPointer() const {
|
||||
StackFrame::Type ExitFrame::GetStateForFramePointer(Address fp, State* state) {
|
||||
if (fp == 0) return NONE;
|
||||
Address sp = ComputeStackPointer(fp);
|
||||
StackFrame::Type type = ComputeFrameType(fp);
|
||||
if (type == StackFrame::WASM_COMPILED) {
|
||||
// {sp} is only needed for finding the PC slot, the rest is handled
|
||||
// via safepoint.
|
||||
sp = fp + WasmExitFrameConstants::kWasmInstanceOffset;
|
||||
DCHECK_EQ(sp - 1 * kPCOnStackSize,
|
||||
fp + WasmExitFrameConstants::kCallingPCOffset);
|
||||
}
|
||||
FillState(fp, sp, state);
|
||||
DCHECK_NE(*state->pc_address, kNullAddress);
|
||||
|
||||
return ComputeFrameType(fp);
|
||||
return type;
|
||||
}
|
||||
|
||||
StackFrame::Type ExitFrame::ComputeFrameType(Address fp) {
|
||||
@ -686,7 +702,8 @@ StackFrame::Type ExitFrame::ComputeFrameType(Address fp) {
|
||||
intptr_t marker_int = bit_cast<intptr_t>(marker);
|
||||
|
||||
StackFrame::Type frame_type = static_cast<StackFrame::Type>(marker_int >> 1);
|
||||
if (frame_type == EXIT || frame_type == BUILTIN_EXIT) {
|
||||
if (frame_type == EXIT || frame_type == BUILTIN_EXIT ||
|
||||
frame_type == WASM_COMPILED) {
|
||||
return frame_type;
|
||||
}
|
||||
|
||||
|
@ -655,6 +655,11 @@ class Isolate final : private HiddenFactory {
|
||||
inline Address* c_entry_fp_address() {
|
||||
return &thread_local_top()->c_entry_fp_;
|
||||
}
|
||||
static uint32_t c_entry_fp_offset() {
|
||||
return static_cast<uint32_t>(
|
||||
OFFSET_OF(Isolate, thread_local_top()->c_entry_fp_) -
|
||||
isolate_root_bias());
|
||||
}
|
||||
inline Address* handler_address() { return &thread_local_top()->handler_; }
|
||||
inline Address* c_function_address() {
|
||||
return &thread_local_top()->c_function_;
|
||||
|
@ -1836,16 +1836,12 @@ void TurboAssembler::CallCFunction(Register function, int num_arguments) {
|
||||
// Save the frame pointer and PC so that the stack layout remains iterable,
|
||||
// even without an ExitFrame which normally exists between JS and C frames.
|
||||
if (isolate() != nullptr) {
|
||||
// Get the current PC via call, pop. This gets the return address pushed to
|
||||
// the stack by call.
|
||||
Label get_pc;
|
||||
call(&get_pc);
|
||||
bind(&get_pc);
|
||||
// Find two caller-saved scratch registers.
|
||||
Register scratch1 = eax;
|
||||
Register scratch2 = ecx;
|
||||
if (function == eax) scratch1 = edx;
|
||||
if (function == ecx) scratch2 = edx;
|
||||
PushPC();
|
||||
pop(scratch1);
|
||||
mov(ExternalReferenceAsOperand(
|
||||
ExternalReference::fast_c_call_caller_pc_address(isolate()),
|
||||
@ -1873,6 +1869,14 @@ void TurboAssembler::CallCFunction(Register function, int num_arguments) {
|
||||
}
|
||||
}
|
||||
|
||||
void TurboAssembler::PushPC() {
|
||||
// Push the current PC onto the stack as "return address" via calling
|
||||
// the next instruction.
|
||||
Label get_pc;
|
||||
call(&get_pc);
|
||||
bind(&get_pc);
|
||||
}
|
||||
|
||||
void TurboAssembler::Call(Handle<Code> code_object, RelocInfo::Mode rmode) {
|
||||
DCHECK_IMPLIES(options().isolate_independent_code,
|
||||
Builtins::IsIsolateIndependentBuiltin(*code_object));
|
||||
|
@ -195,6 +195,8 @@ class V8_EXPORT_PRIVATE TurboAssembler : public TurboAssemblerBase {
|
||||
void LoadRootRegisterOffset(Register destination, intptr_t offset) override;
|
||||
void LoadRootRelative(Register destination, int32_t offset) override;
|
||||
|
||||
void PushPC();
|
||||
|
||||
// Operand pointing to an external reference.
|
||||
// May emit code to set up the scratch register. The operand is
|
||||
// only guaranteed to be correct as long as the scratch register
|
||||
|
@ -334,6 +334,9 @@ VisitorId Map::GetVisitorId(Map map) {
|
||||
if (instance_type == PROTOTYPE_INFO_TYPE) {
|
||||
return kVisitPrototypeInfo;
|
||||
}
|
||||
if (instance_type == WASM_CAPI_FUNCTION_DATA_TYPE) {
|
||||
return kVisitWasmCapiFunctionData;
|
||||
}
|
||||
return kVisitStruct;
|
||||
|
||||
case LOAD_HANDLER_TYPE:
|
||||
|
@ -1040,6 +1040,9 @@ ReturnType BodyDescriptorApply(InstanceType type, T1 p1, T2 p2, T3 p3, T4 p4) {
|
||||
if (type == PROTOTYPE_INFO_TYPE) {
|
||||
return Op::template apply<PrototypeInfo::BodyDescriptor>(p1, p2, p3,
|
||||
p4);
|
||||
} else if (type == WASM_CAPI_FUNCTION_DATA_TYPE) {
|
||||
return Op::template apply<WasmCapiFunctionData::BodyDescriptor>(p1, p2,
|
||||
p3, p4);
|
||||
} else {
|
||||
return Op::template apply<StructBodyDescriptor>(p1, p2, p3, p4);
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ specific_include_rules = {
|
||||
"c-api\.cc": [
|
||||
"+include/libplatform/libplatform.h",
|
||||
"+third_party/wasm-api/wasm.h",
|
||||
],
|
||||
"c-api\.h": [
|
||||
"+third_party/wasm-api/wasm.hh",
|
||||
],
|
||||
}
|
||||
|
@ -22,11 +22,11 @@
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "src/wasm/c-api.h"
|
||||
|
||||
#include "third_party/wasm-api/wasm.h"
|
||||
#include "third_party/wasm-api/wasm.hh"
|
||||
|
||||
#include "include/libplatform/libplatform.h"
|
||||
#include "include/v8.h"
|
||||
#include "src/api/api-inl.h"
|
||||
#include "src/wasm/leb-helper.h"
|
||||
#include "src/wasm/module-instantiate.h"
|
||||
@ -707,42 +707,17 @@ auto Engine::make(own<Config*>&& config) -> own<Engine*> {
|
||||
|
||||
// Stores
|
||||
|
||||
class StoreImpl {
|
||||
friend own<Store*> Store::make(Engine*);
|
||||
|
||||
v8::Isolate::CreateParams create_params_;
|
||||
v8::Isolate* isolate_;
|
||||
v8::Eternal<v8::Context> context_;
|
||||
|
||||
public:
|
||||
StoreImpl() {}
|
||||
|
||||
~StoreImpl() {
|
||||
StoreImpl::~StoreImpl() {
|
||||
#ifdef DEBUG
|
||||
reinterpret_cast<i::Isolate*>(isolate_)->heap()->PreciseCollectAllGarbage(
|
||||
i::Heap::kNoGCFlags, i::GarbageCollectionReason::kTesting,
|
||||
v8::kGCCallbackFlagForced);
|
||||
reinterpret_cast<i::Isolate*>(isolate_)->heap()->PreciseCollectAllGarbage(
|
||||
i::Heap::kNoGCFlags, i::GarbageCollectionReason::kTesting,
|
||||
v8::kGCCallbackFlagForced);
|
||||
#endif
|
||||
context()->Exit();
|
||||
isolate_->Exit();
|
||||
isolate_->Dispose();
|
||||
delete create_params_.array_buffer_allocator;
|
||||
}
|
||||
|
||||
auto isolate() const -> v8::Isolate* { return isolate_; }
|
||||
i::Isolate* i_isolate() const {
|
||||
return reinterpret_cast<i::Isolate*>(isolate_);
|
||||
}
|
||||
|
||||
auto context() const -> v8::Local<v8::Context> {
|
||||
return context_.Get(isolate_);
|
||||
}
|
||||
|
||||
static auto get(i::Isolate* isolate) -> StoreImpl* {
|
||||
return static_cast<StoreImpl*>(
|
||||
reinterpret_cast<v8::Isolate*>(isolate)->GetData(0));
|
||||
}
|
||||
};
|
||||
context()->Exit();
|
||||
isolate_->Exit();
|
||||
isolate_->Dispose();
|
||||
delete create_params_.array_buffer_allocator;
|
||||
}
|
||||
|
||||
template <>
|
||||
struct implement<Store> {
|
||||
|
42
src/wasm/c-api.h
Normal file
42
src/wasm/c-api.h
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2019 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.
|
||||
|
||||
#ifndef V8_WASM_C_API_H_
|
||||
#define V8_WASM_C_API_H_
|
||||
|
||||
#include "include/v8.h"
|
||||
#include "src/common/globals.h"
|
||||
#include "third_party/wasm-api/wasm.hh"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
class StoreImpl {
|
||||
public:
|
||||
~StoreImpl();
|
||||
|
||||
v8::Isolate* isolate() const { return isolate_; }
|
||||
i::Isolate* i_isolate() const {
|
||||
return reinterpret_cast<i::Isolate*>(isolate_);
|
||||
}
|
||||
|
||||
v8::Local<v8::Context> context() const { return context_.Get(isolate_); }
|
||||
|
||||
static StoreImpl* get(i::Isolate* isolate) {
|
||||
return static_cast<StoreImpl*>(
|
||||
reinterpret_cast<v8::Isolate*>(isolate)->GetData(0));
|
||||
}
|
||||
|
||||
private:
|
||||
friend own<Store*> Store::make(Engine*);
|
||||
|
||||
StoreImpl() {}
|
||||
|
||||
v8::Isolate::CreateParams create_params_;
|
||||
v8::Isolate* isolate_ = nullptr;
|
||||
v8::Eternal<v8::Context> context_;
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // V8_WASM_C_API_H_
|
@ -24,6 +24,7 @@ group("gn_all") {
|
||||
deps = [
|
||||
"inspector:inspector-test",
|
||||
"mkgrokdump:mkgrokdump",
|
||||
"wasm-api-tests:wasm_api_tests",
|
||||
]
|
||||
|
||||
if (host_os != "mac" || !is_android) {
|
||||
@ -82,6 +83,7 @@ group("v8_bot_default") {
|
||||
"mkgrokdump:mkgrokdump",
|
||||
"preparser:v8_preparser",
|
||||
"unittests:unittests",
|
||||
"wasm-api-tests:wasm_api_tests",
|
||||
"wasm-js:v8_wasm_js",
|
||||
"wasm-spec-tests:v8_wasm_spec_tests",
|
||||
"webkit:v8_webkit",
|
||||
@ -102,6 +104,7 @@ group("v8_default") {
|
||||
"mkgrokdump:mkgrokdump",
|
||||
"preparser:v8_preparser",
|
||||
"unittests:unittests",
|
||||
"wasm-api-tests:wasm_api_tests",
|
||||
"wasm-js:v8_wasm_js",
|
||||
"wasm-spec-tests:v8_wasm_spec_tests",
|
||||
]
|
||||
|
35
test/wasm-api-tests/BUILD.gn
Normal file
35
test/wasm-api-tests/BUILD.gn
Normal file
@ -0,0 +1,35 @@
|
||||
# Copyright 2019 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.
|
||||
|
||||
import("../../gni/v8.gni")
|
||||
|
||||
v8_executable("wasm_api_tests") {
|
||||
testonly = true
|
||||
|
||||
deps = [
|
||||
"../..:v8_maybe_icu",
|
||||
"../..:wee8",
|
||||
"//build/win:default_exe_manifest",
|
||||
"//testing/gmock",
|
||||
"//testing/gtest",
|
||||
]
|
||||
|
||||
data_deps = [
|
||||
"../../tools:v8_testrunner",
|
||||
]
|
||||
|
||||
data = [
|
||||
"testcfg.py",
|
||||
"wasm-api-tests.status",
|
||||
]
|
||||
|
||||
configs = [ "../..:internal_config_base" ]
|
||||
|
||||
sources = [
|
||||
"../../testing/gmock-support.h",
|
||||
"../../testing/gtest-support.h",
|
||||
"callbacks.cc",
|
||||
"run-all-wasm-api-tests.cc",
|
||||
]
|
||||
}
|
5
test/wasm-api-tests/DEPS
Normal file
5
test/wasm-api-tests/DEPS
Normal file
@ -0,0 +1,5 @@
|
||||
include_rules = [
|
||||
"+src",
|
||||
"+testing",
|
||||
"+third_party/wasm-api"
|
||||
]
|
193
test/wasm-api-tests/callbacks.cc
Normal file
193
test/wasm-api-tests/callbacks.cc
Normal file
@ -0,0 +1,193 @@
|
||||
// Copyright 2019 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/execution/isolate.h"
|
||||
#include "src/heap/heap.h"
|
||||
#include "src/wasm/c-api.h"
|
||||
#include "src/wasm/wasm-module-builder.h"
|
||||
#include "src/wasm/wasm-opcodes.h"
|
||||
#include "src/zone/accounting-allocator.h"
|
||||
#include "src/zone/zone.h"
|
||||
#include "test/common/wasm/wasm-macro-gen.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "third_party/wasm-api/wasm.hh"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
// TODO(jkummerow): Drop these from the API.
|
||||
#ifdef DEBUG
|
||||
template <class T>
|
||||
void vec<T>::make_data() {}
|
||||
|
||||
template <class T>
|
||||
void vec<T>::free_data() {}
|
||||
#endif
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace wasm {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::wasm::Engine;
|
||||
using ::wasm::Extern;
|
||||
using ::wasm::Func;
|
||||
using ::wasm::FuncType;
|
||||
using ::wasm::Instance;
|
||||
using ::wasm::Module;
|
||||
using ::wasm::own;
|
||||
using ::wasm::Store;
|
||||
using ::wasm::Trap;
|
||||
using ::wasm::Val;
|
||||
using ::wasm::ValType;
|
||||
using ::wasm::vec;
|
||||
|
||||
own<Trap*> Stage2(void* env, const Val args[], Val results[]);
|
||||
|
||||
class WasmCapiTest : public ::testing::Test {
|
||||
public:
|
||||
WasmCapiTest()
|
||||
: Test(),
|
||||
zone_(&allocator_, ZONE_NAME),
|
||||
builder_(&zone_),
|
||||
exports_(vec<Extern*>::make()),
|
||||
wasm_sig_(1, 1, wasm_sig_types_) {
|
||||
engine_ = Engine::make();
|
||||
store_ = Store::make(engine_.get());
|
||||
|
||||
// Build the following function:
|
||||
// int32 stage1(int32 arg0) { return stage2(arg0); }
|
||||
uint32_t stage2_index =
|
||||
builder_.AddImport(ArrayVector("stage2"), wasm_sig());
|
||||
byte code[] = {WASM_CALL_FUNCTION(stage2_index, WASM_GET_LOCAL(0))};
|
||||
AddExportedFunction(CStrVector("stage1"), code, sizeof(code));
|
||||
|
||||
cpp_sig_ = FuncType::make(vec<ValType*>::make(ValType::make(::wasm::I32)),
|
||||
vec<ValType*>::make(ValType::make(::wasm::I32)));
|
||||
stage2_ = Func::make(store(), cpp_sig_.get(), Stage2, this);
|
||||
}
|
||||
|
||||
void Compile() {
|
||||
ZoneBuffer buffer(&zone_);
|
||||
builder_.WriteTo(buffer);
|
||||
size_t size = buffer.end() - buffer.begin();
|
||||
vec<byte_t> binary = vec<byte_t>::make(
|
||||
size, reinterpret_cast<byte_t*>(const_cast<byte*>(buffer.begin())));
|
||||
|
||||
module_ = Module::make(store_.get(), binary);
|
||||
DCHECK_NE(module_.get(), nullptr);
|
||||
}
|
||||
|
||||
own<Trap*> Run(Extern* imports[], Val args[], Val results[]) {
|
||||
instance_ = Instance::make(store_.get(), module_.get(), imports);
|
||||
DCHECK_NE(instance_.get(), nullptr);
|
||||
exports_ = instance_->exports();
|
||||
Func* entry = GetExportedFunction(0);
|
||||
return entry->call(args, results);
|
||||
}
|
||||
|
||||
void AddExportedFunction(Vector<const char> name, byte code[],
|
||||
size_t code_size) {
|
||||
WasmFunctionBuilder* fun = builder()->AddFunction(wasm_sig());
|
||||
fun->EmitCode(code, static_cast<uint32_t>(code_size));
|
||||
fun->Emit(kExprEnd);
|
||||
builder()->AddExport(name, fun);
|
||||
}
|
||||
|
||||
Func* GetExportedFunction(size_t index) {
|
||||
DCHECK_GT(exports_.size(), index);
|
||||
Extern* exported = exports_[index];
|
||||
DCHECK_EQ(exported->kind(), ::wasm::EXTERN_FUNC);
|
||||
Func* func = exported->func();
|
||||
DCHECK_NE(func, nullptr);
|
||||
return func;
|
||||
}
|
||||
|
||||
WasmModuleBuilder* builder() { return &builder_; }
|
||||
Store* store() { return store_.get(); }
|
||||
Func* stage2() { return stage2_.get(); }
|
||||
|
||||
FunctionSig* wasm_sig() { return &wasm_sig_; }
|
||||
FuncType* cpp_sig() { return cpp_sig_.get(); }
|
||||
|
||||
private:
|
||||
AccountingAllocator allocator_;
|
||||
Zone zone_;
|
||||
WasmModuleBuilder builder_;
|
||||
own<Engine*> engine_;
|
||||
own<Store*> store_;
|
||||
own<Module*> module_;
|
||||
own<Instance*> instance_;
|
||||
vec<Extern*> exports_;
|
||||
own<Func*> stage2_;
|
||||
own<FuncType*> cpp_sig_;
|
||||
ValueType wasm_sig_types_[2] = {kWasmI32, kWasmI32};
|
||||
FunctionSig wasm_sig_;
|
||||
};
|
||||
|
||||
own<Trap*> Stage2(void* env, const Val args[], Val results[]) {
|
||||
printf("Stage2...\n");
|
||||
WasmCapiTest* self = reinterpret_cast<WasmCapiTest*>(env);
|
||||
Func* stage3 = self->GetExportedFunction(1);
|
||||
own<Trap*> result = stage3->call(args, results);
|
||||
if (result) {
|
||||
printf("Stage2: got exception: %s\n", result->message().get());
|
||||
} else {
|
||||
printf("Stage2: call successful\n");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
own<Trap*> Stage4_GC(void* env, const Val args[], Val results[]) {
|
||||
printf("Stage4...\n");
|
||||
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(env);
|
||||
isolate->heap()->PreciseCollectAllGarbage(
|
||||
i::Heap::kNoGCFlags, i::GarbageCollectionReason::kTesting,
|
||||
v8::kGCCallbackFlagForced);
|
||||
results[0] = Val::i32(args[0].i32() + 1);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(WasmCapiTest, Trap) {
|
||||
// Build the following function:
|
||||
// int32 stage3_trap(int32 arg0) { unreachable(); }
|
||||
byte code[] = {WASM_UNREACHABLE};
|
||||
AddExportedFunction(CStrVector("stage3_trap"), code, sizeof(code));
|
||||
Compile();
|
||||
|
||||
Extern* imports[] = {stage2()};
|
||||
Val args[] = {Val::i32(42)};
|
||||
Val results[1];
|
||||
own<Trap*> result = Run(imports, args, results);
|
||||
EXPECT_NE(result, nullptr);
|
||||
printf("Stage0: Got trap as expected: %s\n", result->message().get());
|
||||
}
|
||||
|
||||
TEST_F(WasmCapiTest, GC) {
|
||||
// Build the following function:
|
||||
// int32 stage3_to4(int32 arg0) { return stage4(arg0); }
|
||||
uint32_t stage4_index =
|
||||
builder()->AddImport(ArrayVector("stage4"), wasm_sig());
|
||||
byte code[] = {WASM_CALL_FUNCTION(stage4_index, WASM_GET_LOCAL(0))};
|
||||
AddExportedFunction(CStrVector("stage3_to4"), code, sizeof(code));
|
||||
Compile();
|
||||
|
||||
i::Isolate* isolate =
|
||||
reinterpret_cast<::wasm::StoreImpl*>(store())->i_isolate();
|
||||
own<Func*> stage4 = Func::make(store(), cpp_sig(), Stage4_GC, isolate);
|
||||
Extern* imports[] = {stage2(), stage4.get()};
|
||||
Val args[] = {Val::i32(42)};
|
||||
Val results[1];
|
||||
own<Trap*> result = Run(imports, args, results);
|
||||
EXPECT_EQ(result, nullptr);
|
||||
EXPECT_EQ(43, results[0].i32());
|
||||
}
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
} // namespace v8
|
17
test/wasm-api-tests/run-all-wasm-api-tests.cc
Normal file
17
test/wasm-api-tests/run-all-wasm-api-tests.cc
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2019 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 "include/v8.h"
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// Don't catch SEH exceptions and continue as the following tests might hang
|
||||
// in an broken environment on windows.
|
||||
testing::GTEST_FLAG(catch_exceptions) = false;
|
||||
testing::InitGoogleMock(&argc, argv);
|
||||
v8::V8::SetFlagsFromCommandLine(&argc, argv, true);
|
||||
v8::V8::InitializeExternalStartupData(argv[0]);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
85
test/wasm-api-tests/testcfg.py
Normal file
85
test/wasm-api-tests/testcfg.py
Normal file
@ -0,0 +1,85 @@
|
||||
# Copyright 2019 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.
|
||||
|
||||
# for py2/py3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
|
||||
from testrunner.local import command
|
||||
from testrunner.local import utils
|
||||
from testrunner.local import testsuite
|
||||
from testrunner.objects import testcase
|
||||
|
||||
|
||||
class VariantsGenerator(testsuite.VariantsGenerator):
|
||||
def _get_variants(self, test):
|
||||
return self._standard_variant
|
||||
|
||||
|
||||
class TestLoader(testsuite.TestLoader):
|
||||
def _list_test_filenames(self):
|
||||
shell = os.path.abspath(
|
||||
os.path.join(self.test_config.shell_dir, "wasm_api_tests"))
|
||||
if utils.IsWindows():
|
||||
shell += ".exe"
|
||||
|
||||
output = None
|
||||
for i in range(3): # Try 3 times in case of errors.
|
||||
cmd = command.Command(
|
||||
cmd_prefix=self.test_config.command_prefix,
|
||||
shell=shell,
|
||||
args=['--gtest_list_tests'] + self.test_config.extra_flags)
|
||||
output = cmd.execute()
|
||||
if output.exit_code == 0:
|
||||
break
|
||||
|
||||
print("Test executable failed to list the tests (try %d).\n\nCmd:" % i)
|
||||
print(cmd)
|
||||
print("\nStdout:")
|
||||
print(output.stdout)
|
||||
print("\nStderr:")
|
||||
print(output.stderr)
|
||||
print("\nExit code: %d" % output.exit_code)
|
||||
else:
|
||||
raise Exception("Test executable failed to list the tests.")
|
||||
|
||||
# TODO create an ExecutableTestLoader for refactoring this similar to
|
||||
# JSTestLoader.
|
||||
test_names = []
|
||||
for line in output.stdout.splitlines():
|
||||
test_desc = line.strip().split()[0]
|
||||
if test_desc.endswith('.'):
|
||||
test_case = test_desc
|
||||
elif test_case and test_desc:
|
||||
test_names.append(test_case + test_desc)
|
||||
|
||||
return sorted(test_names)
|
||||
|
||||
|
||||
class TestSuite(testsuite.TestSuite):
|
||||
def _test_loader_class(self):
|
||||
return TestLoader
|
||||
|
||||
def _test_class(self):
|
||||
return TestCase
|
||||
|
||||
def _variants_gen_class(self):
|
||||
return VariantsGenerator
|
||||
|
||||
|
||||
class TestCase(testcase.TestCase):
|
||||
def _get_suite_flags(self):
|
||||
return (
|
||||
["--gtest_filter=" + self.path] +
|
||||
["--gtest_random_seed=%s" % self.random_seed] +
|
||||
["--gtest_print_time=0"]
|
||||
)
|
||||
|
||||
def get_shell(self):
|
||||
return "wasm_api_tests"
|
||||
|
||||
|
||||
def GetSuite(*args, **kwargs):
|
||||
return TestSuite(*args, **kwargs)
|
16
test/wasm-api-tests/wasm-api-tests.status
Normal file
16
test/wasm-api-tests/wasm-api-tests.status
Normal file
@ -0,0 +1,16 @@
|
||||
# Copyright 2019 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.
|
||||
|
||||
[
|
||||
['lite_mode or variant == jitless', {
|
||||
# TODO(v8:7777): Re-enable once wasm is supported in jitless mode.
|
||||
'*': [SKIP],
|
||||
}], # lite_mode or variant == jitless
|
||||
|
||||
##############################################################################
|
||||
['variant == jitless and not embedded_builtins', {
|
||||
'*': [SKIP],
|
||||
}], # variant == jitless and not embedded_builtins
|
||||
|
||||
]
|
@ -43,8 +43,8 @@ MODES = ["release", "debug", "optdebug"]
|
||||
# Modes that get built/run when you don't specify any.
|
||||
DEFAULT_MODES = ["release", "debug"]
|
||||
# Build targets that can be manually specified.
|
||||
TARGETS = ["d8", "cctest", "unittests", "v8_fuzzers", "mkgrokdump",
|
||||
"generate-bytecode-expectations", "inspector-test"]
|
||||
TARGETS = ["d8", "cctest", "unittests", "v8_fuzzers", "wasm_api_tests", "wee8",
|
||||
"mkgrokdump", "generate-bytecode-expectations", "inspector-test"]
|
||||
# Build targets that get built when you don't specify any (and specified tests
|
||||
# don't imply any other targets).
|
||||
DEFAULT_TARGETS = ["d8"]
|
||||
@ -64,13 +64,14 @@ ACTIONS = {
|
||||
HELP = """<arch> can be any of: %(arches)s
|
||||
<mode> can be any of: %(modes)s
|
||||
<target> can be any of:
|
||||
- cctest, d8, unittests, v8_fuzzers (build respective binary)
|
||||
- %(targets)s (build respective binary)
|
||||
- all (build all binaries)
|
||||
- tests (build test binaries)
|
||||
- check (build test binaries, run most tests)
|
||||
- checkall (build all binaries, run more tests)
|
||||
""" % {"arches": " ".join(ARCHES),
|
||||
"modes": " ".join(MODES)}
|
||||
"modes": " ".join(MODES),
|
||||
"targets": ", ".join(TARGETS)}
|
||||
|
||||
TESTSUITES_TARGETS = {"benchmarks": "d8",
|
||||
"cctest": "cctest",
|
||||
@ -84,6 +85,7 @@ TESTSUITES_TARGETS = {"benchmarks": "d8",
|
||||
"preparser": "d8",
|
||||
"test262": "d8",
|
||||
"unittests": "unittests",
|
||||
"wasm-api-tests": "wasm_api_tests",
|
||||
"webkit": "d8"}
|
||||
|
||||
OUTDIR = "out"
|
||||
|
Loading…
Reference in New Issue
Block a user