[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:
Jakob Kummerow 2019-05-28 11:15:48 +02:00 committed by Commit Bot
parent 68da88559e
commit f5ab7d38be
25 changed files with 549 additions and 59 deletions

View File

@ -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",
]

View File

@ -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);
}
}

View File

@ -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()));

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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(),

View File

@ -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.

View File

@ -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;
}

View File

@ -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_;

View File

@ -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));

View File

@ -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

View File

@ -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:

View File

@ -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);
}

View File

@ -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",
],
}

View File

@ -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
View 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_

View File

@ -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",
]

View 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
View File

@ -0,0 +1,5 @@
include_rules = [
"+src",
"+testing",
"+third_party/wasm-api"
]

View 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

View 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();
}

View 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)

View 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
]

View File

@ -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"