| // Copyright 2017 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 "test/cctest/wasm/wasm-run-utils.h" |
| |
| #include "src/api.h" |
| #include "src/assembler-inl.h" |
| #include "src/wasm/wasm-memory.h" |
| #include "src/wasm/wasm-objects-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace wasm { |
| |
| TestingModuleBuilder::TestingModuleBuilder( |
| Zone* zone, WasmExecutionMode mode, |
| compiler::RuntimeExceptionSupport exception_support) |
| : test_module_ptr_(&test_module_), |
| isolate_(CcTest::InitIsolateOnce()), |
| global_offset(0), |
| mem_start_(nullptr), |
| mem_size_(0), |
| interpreter_(nullptr), |
| runtime_exception_support_(exception_support) { |
| WasmJs::Install(isolate_, true); |
| test_module_.globals_size = kMaxGlobalsSize; |
| memset(globals_data_, 0, sizeof(globals_data_)); |
| instance_object_ = InitInstanceObject(); |
| if (mode == kExecuteInterpreted) { |
| interpreter_ = WasmDebugInfo::SetupForTesting(instance_object_); |
| } |
| } |
| |
| byte* TestingModuleBuilder::AddMemory(uint32_t size) { |
| CHECK(!test_module_.has_memory); |
| CHECK_NULL(mem_start_); |
| CHECK_EQ(0, mem_size_); |
| DCHECK(!instance_object_->has_memory_buffer()); |
| DCHECK(!instance_object_->has_memory_object()); |
| test_module_.has_memory = true; |
| bool enable_guard_regions = EnableGuardRegions() && test_module_.is_wasm(); |
| uint32_t alloc_size = |
| enable_guard_regions ? RoundUp(size, base::OS::CommitPageSize()) : size; |
| Handle<JSArrayBuffer> new_buffer = |
| wasm::NewArrayBuffer(isolate_, alloc_size, enable_guard_regions); |
| CHECK(!new_buffer.is_null()); |
| instance_object_->set_memory_buffer(*new_buffer); |
| mem_start_ = reinterpret_cast<byte*>(new_buffer->backing_store()); |
| mem_size_ = size; |
| CHECK(size == 0 || mem_start_); |
| memset(mem_start_, 0, size); |
| |
| if (interpreter_) { |
| interpreter_->UpdateMemory(mem_start_, mem_size_); |
| } |
| // Create the WasmMemoryObject. |
| Handle<WasmMemoryObject> memory_object = WasmMemoryObject::New( |
| isolate_, new_buffer, |
| (test_module_.maximum_pages != 0) ? test_module_.maximum_pages : -1); |
| instance_object_->set_memory_object(*memory_object); |
| WasmMemoryObject::AddInstance(isolate_, memory_object, instance_object_); |
| // TODO(wasm): Delete the following two lines when test-run-wasm will use a |
| // multiple of kPageSize as memory size. At the moment, the effect of these |
| // two lines is used to shrink the memory for testing purposes. |
| instance_object_->wasm_context()->mem_start = mem_start_; |
| instance_object_->wasm_context()->mem_size = mem_size_; |
| return mem_start_; |
| } |
| |
| uint32_t TestingModuleBuilder::AddFunction(FunctionSig* sig, Handle<Code> code, |
| const char* name) { |
| if (test_module_.functions.size() == 0) { |
| // TODO(titzer): Reserving space here to avoid the underlying WasmFunction |
| // structs from moving. |
| test_module_.functions.reserve(kMaxFunctions); |
| } |
| uint32_t index = static_cast<uint32_t>(test_module_.functions.size()); |
| test_module_.functions.push_back( |
| {sig, index, 0, {0, 0}, {0, 0}, false, false}); |
| if (name) { |
| Vector<const byte> name_vec = Vector<const byte>::cast(CStrVector(name)); |
| test_module_.functions.back().name = { |
| AddBytes(name_vec), static_cast<uint32_t>(name_vec.length())}; |
| } |
| function_code_.push_back(code); |
| if (interpreter_) { |
| interpreter_->AddFunctionForTesting(&test_module_.functions.back()); |
| } |
| DCHECK_LT(index, kMaxFunctions); // limited for testing. |
| return index; |
| } |
| |
| uint32_t TestingModuleBuilder::AddJsFunction( |
| FunctionSig* sig, const char* source, Handle<FixedArray> js_imports_table) { |
| Handle<JSFunction> jsfunc = Handle<JSFunction>::cast(v8::Utils::OpenHandle( |
| *v8::Local<v8::Function>::Cast(CompileRun(source)))); |
| uint32_t index = AddFunction(sig, Handle<Code>::null(), nullptr); |
| js_imports_table->set(0, *isolate_->native_context()); |
| Handle<Code> code = compiler::CompileWasmToJSWrapper( |
| isolate_, jsfunc, sig, index, test_module_.origin(), js_imports_table); |
| function_code_[index] = code; |
| return index; |
| } |
| |
| Handle<JSFunction> TestingModuleBuilder::WrapCode(uint32_t index) { |
| // Wrap the code so it can be called as a JS function. |
| Handle<Code> code = function_code_[index]; |
| byte* context_address = |
| test_module_.has_memory |
| ? reinterpret_cast<byte*>(instance_object_->wasm_context()) |
| : nullptr; |
| Handle<Code> ret_code = compiler::CompileJSToWasmWrapper( |
| isolate_, &test_module_, code, index, context_address); |
| Handle<JSFunction> ret = WasmExportedFunction::New( |
| isolate_, instance_object(), MaybeHandle<String>(), |
| static_cast<int>(index), |
| static_cast<int>(test_module_.functions[index].sig->parameter_count()), |
| ret_code); |
| |
| // Add weak reference to exported functions. |
| Handle<WasmCompiledModule> compiled_module( |
| instance_object()->compiled_module(), isolate_); |
| Handle<FixedArray> old_arr = compiled_module->weak_exported_functions(); |
| Handle<FixedArray> new_arr = |
| isolate_->factory()->NewFixedArray(old_arr->length() + 1); |
| old_arr->CopyTo(0, *new_arr, 0, old_arr->length()); |
| Handle<WeakCell> weak_fn = isolate_->factory()->NewWeakCell(ret); |
| new_arr->set(old_arr->length(), *weak_fn); |
| compiled_module->set_weak_exported_functions(new_arr); |
| |
| return ret; |
| } |
| |
| void TestingModuleBuilder::AddIndirectFunctionTable(uint16_t* function_indexes, |
| uint32_t table_size) { |
| test_module_.function_tables.emplace_back(); |
| WasmIndirectFunctionTable& table = test_module_.function_tables.back(); |
| table.initial_size = table_size; |
| table.maximum_size = table_size; |
| table.has_maximum_size = true; |
| for (uint32_t i = 0; i < table_size; ++i) { |
| table.values.push_back(function_indexes[i]); |
| table.map.FindOrInsert(test_module_.functions[function_indexes[i]].sig); |
| } |
| |
| function_tables_.push_back( |
| isolate_->global_handles() |
| ->Create(*isolate_->factory()->NewFixedArray(table_size)) |
| .address()); |
| signature_tables_.push_back( |
| isolate_->global_handles() |
| ->Create(*isolate_->factory()->NewFixedArray(table_size)) |
| .address()); |
| } |
| |
| void TestingModuleBuilder::PopulateIndirectFunctionTable() { |
| if (interpret()) return; |
| // Initialize the fixed arrays in instance->function_tables. |
| for (uint32_t i = 0; i < function_tables_.size(); i++) { |
| WasmIndirectFunctionTable& table = test_module_.function_tables[i]; |
| Handle<FixedArray> function_table( |
| reinterpret_cast<FixedArray**>(function_tables_[i])); |
| Handle<FixedArray> signature_table( |
| reinterpret_cast<FixedArray**>(signature_tables_[i])); |
| int table_size = static_cast<int>(table.values.size()); |
| for (int j = 0; j < table_size; j++) { |
| WasmFunction& function = test_module_.functions[table.values[j]]; |
| signature_table->set(j, Smi::FromInt(table.map.Find(function.sig))); |
| function_table->set(j, *function_code_[function.func_index]); |
| } |
| } |
| } |
| |
| uint32_t TestingModuleBuilder::AddBytes(Vector<const byte> bytes) { |
| Handle<SeqOneByteString> old_bytes( |
| instance_object_->compiled_module()->module_bytes(), isolate_); |
| uint32_t old_size = static_cast<uint32_t>(old_bytes->length()); |
| // Avoid placing strings at offset 0, this might be interpreted as "not |
| // set", e.g. for function names. |
| uint32_t bytes_offset = old_size ? old_size : 1; |
| ScopedVector<byte> new_bytes(bytes_offset + bytes.length()); |
| memcpy(new_bytes.start(), old_bytes->GetChars(), old_size); |
| memcpy(new_bytes.start() + bytes_offset, bytes.start(), bytes.length()); |
| Handle<SeqOneByteString> new_bytes_str = Handle<SeqOneByteString>::cast( |
| isolate_->factory()->NewStringFromOneByte(new_bytes).ToHandleChecked()); |
| instance_object_->compiled_module()->shared()->set_module_bytes( |
| *new_bytes_str); |
| return bytes_offset; |
| } |
| |
| compiler::ModuleEnv TestingModuleBuilder::CreateModuleEnv() { |
| std::vector<SignatureMap*> signature_maps; |
| for (size_t i = 0; i < test_module_.function_tables.size(); i++) { |
| auto& function_table = test_module_.function_tables[i]; |
| signature_maps.push_back(&function_table.map); |
| } |
| return { |
| &test_module_, |
| function_tables_, |
| signature_tables_, |
| signature_maps, |
| function_code_, |
| Handle<Code>::null(), |
| reinterpret_cast<uintptr_t>(globals_data_), |
| }; |
| } |
| |
| const WasmGlobal* TestingModuleBuilder::AddGlobal(ValueType type) { |
| byte size = WasmOpcodes::MemSize(WasmOpcodes::MachineTypeFor(type)); |
| global_offset = (global_offset + size - 1) & ~(size - 1); // align |
| test_module_.globals.push_back( |
| {type, true, WasmInitExpr(), global_offset, false, false}); |
| global_offset += size; |
| // limit number of globals. |
| CHECK_LT(global_offset, kMaxGlobalsSize); |
| return &test_module_.globals.back(); |
| } |
| |
| Handle<WasmInstanceObject> TestingModuleBuilder::InitInstanceObject() { |
| Handle<SeqOneByteString> empty_string = Handle<SeqOneByteString>::cast( |
| isolate_->factory()->NewStringFromOneByte({}).ToHandleChecked()); |
| // The lifetime of the wasm module is tied to this object's, and we cannot |
| // rely on the mechanics of Managed<T>. |
| Handle<Foreign> module_wrapper = isolate_->factory()->NewForeign( |
| reinterpret_cast<Address>(&test_module_ptr_)); |
| Handle<Script> script = |
| isolate_->factory()->NewScript(isolate_->factory()->empty_string()); |
| script->set_type(Script::TYPE_WASM); |
| Handle<WasmSharedModuleData> shared_module_data = |
| WasmSharedModuleData::New(isolate_, module_wrapper, empty_string, script, |
| Handle<ByteArray>::null()); |
| Handle<FixedArray> code_table = isolate_->factory()->NewFixedArray(0); |
| Handle<FixedArray> export_wrappers = isolate_->factory()->NewFixedArray(0); |
| Handle<WasmCompiledModule> compiled_module = WasmCompiledModule::New( |
| isolate_, shared_module_data, code_table, export_wrappers, |
| function_tables_, signature_tables_); |
| // This method is called when we initialize TestEnvironment. We don't |
| // have a memory yet, so we won't create it here. We'll update the |
| // interpreter when we get a memory. We do have globals, though. |
| WasmCompiledModule::recreate_globals_start( |
| compiled_module, isolate_->factory(), |
| reinterpret_cast<size_t>(globals_data_)); |
| Handle<FixedArray> weak_exported = isolate_->factory()->NewFixedArray(0); |
| compiled_module->set_weak_exported_functions(weak_exported); |
| DCHECK(WasmCompiledModule::IsWasmCompiledModule(*compiled_module)); |
| script->set_wasm_compiled_module(*compiled_module); |
| return WasmInstanceObject::New(isolate_, compiled_module); |
| } |
| |
| void TestBuildingGraph( |
| Zone* zone, compiler::JSGraph* jsgraph, compiler::ModuleEnv* module, |
| FunctionSig* sig, compiler::SourcePositionTable* source_position_table, |
| const byte* start, const byte* end, |
| compiler::RuntimeExceptionSupport runtime_exception_support) { |
| compiler::WasmGraphBuilder builder( |
| module, zone, jsgraph, CEntryStub(jsgraph->isolate(), 1).GetCode(), sig, |
| source_position_table, runtime_exception_support); |
| |
| DecodeResult result = |
| BuildTFGraph(zone->allocator(), &builder, sig, start, end); |
| if (result.failed()) { |
| if (!FLAG_trace_wasm_decoder) { |
| // Retry the compilation with the tracing flag on, to help in debugging. |
| FLAG_trace_wasm_decoder = true; |
| result = BuildTFGraph(zone->allocator(), &builder, sig, start, end); |
| } |
| |
| uint32_t pc = result.error_offset(); |
| std::ostringstream str; |
| str << "Verification failed; pc = +" << pc |
| << ", msg = " << result.error_msg().c_str(); |
| FATAL(str.str().c_str()); |
| } |
| builder.LowerInt64(); |
| if (!CpuFeatures::SupportsWasmSimd128()) { |
| builder.SimdScalarLoweringForTesting(); |
| } |
| } |
| |
| WasmFunctionWrapper::WasmFunctionWrapper(Zone* zone, int num_params) |
| : GraphAndBuilders(zone), |
| inner_code_node_(nullptr), |
| context_address_(nullptr), |
| signature_(nullptr) { |
| // One additional parameter for the pointer to the return value memory. |
| Signature<MachineType>::Builder sig_builder(zone, 1, num_params + 1); |
| |
| sig_builder.AddReturn(MachineType::Int32()); |
| for (int i = 0; i < num_params + 1; i++) { |
| sig_builder.AddParam(MachineType::Pointer()); |
| } |
| signature_ = sig_builder.Build(); |
| } |
| |
| void WasmFunctionWrapper::Init(CallDescriptor* descriptor, |
| MachineType return_type, |
| Vector<MachineType> param_types) { |
| DCHECK_NOT_NULL(descriptor); |
| DCHECK_EQ(signature_->parameter_count(), param_types.length() + 1); |
| |
| // Create the TF graph for the wrapper. |
| |
| // Function, context_address, effect, and control. |
| Node** parameters = zone()->NewArray<Node*>(param_types.length() + 4); |
| graph()->SetStart(graph()->NewNode(common()->Start(7))); |
| Node* effect = graph()->start(); |
| int parameter_count = 0; |
| |
| // Dummy node which gets replaced in SetInnerCode. |
| inner_code_node_ = graph()->NewNode(common()->Int32Constant(0)); |
| parameters[parameter_count++] = inner_code_node_; |
| |
| // Dummy node that gets replaced in SetContextAddress. |
| context_address_ = graph()->NewNode(IntPtrConstant(0)); |
| parameters[parameter_count++] = context_address_; |
| |
| int param_idx = 0; |
| for (MachineType t : param_types) { |
| DCHECK_NE(MachineType::None(), t); |
| parameters[parameter_count] = graph()->NewNode( |
| machine()->Load(t), |
| graph()->NewNode(common()->Parameter(param_idx++), graph()->start()), |
| graph()->NewNode(common()->Int32Constant(0)), effect, graph()->start()); |
| effect = parameters[parameter_count++]; |
| } |
| |
| parameters[parameter_count++] = effect; |
| parameters[parameter_count++] = graph()->start(); |
| Node* call = |
| graph()->NewNode(common()->Call(descriptor), parameter_count, parameters); |
| |
| if (!return_type.IsNone()) { |
| effect = graph()->NewNode( |
| machine()->Store(compiler::StoreRepresentation( |
| return_type.representation(), WriteBarrierKind::kNoWriteBarrier)), |
| graph()->NewNode(common()->Parameter(param_types.length()), |
| graph()->start()), |
| graph()->NewNode(common()->Int32Constant(0)), call, effect, |
| graph()->start()); |
| } |
| Node* zero = graph()->NewNode(common()->Int32Constant(0)); |
| Node* r = graph()->NewNode( |
| common()->Return(), zero, |
| graph()->NewNode(common()->Int32Constant(WASM_WRAPPER_RETURN_VALUE)), |
| effect, graph()->start()); |
| graph()->SetEnd(graph()->NewNode(common()->End(1), r)); |
| } |
| |
| Handle<Code> WasmFunctionWrapper::GetWrapperCode() { |
| if (code_.is_null()) { |
| Isolate* isolate = CcTest::InitIsolateOnce(); |
| |
| CallDescriptor* descriptor = |
| compiler::Linkage::GetSimplifiedCDescriptor(zone(), signature_, true); |
| |
| if (kPointerSize == 4) { |
| size_t num_params = signature_->parameter_count(); |
| // One additional parameter for the pointer of the return value. |
| Signature<MachineRepresentation>::Builder rep_builder(zone(), 1, |
| num_params + 1); |
| |
| rep_builder.AddReturn(MachineRepresentation::kWord32); |
| for (size_t i = 0; i < num_params + 1; i++) { |
| rep_builder.AddParam(MachineRepresentation::kWord32); |
| } |
| compiler::Int64Lowering r(graph(), machine(), common(), zone(), |
| rep_builder.Build()); |
| r.LowerGraph(); |
| } |
| |
| CompilationInfo info(ArrayVector("testing"), isolate, graph()->zone(), |
| Code::STUB); |
| code_ = compiler::Pipeline::GenerateCodeForTesting(&info, descriptor, |
| graph(), nullptr); |
| CHECK(!code_.is_null()); |
| #ifdef ENABLE_DISASSEMBLER |
| if (FLAG_print_opt_code) { |
| OFStream os(stdout); |
| code_->Disassemble("wasm wrapper", os); |
| } |
| #endif |
| } |
| |
| return code_; |
| } |
| |
| void WasmFunctionCompiler::Build(const byte* start, const byte* end) { |
| size_t locals_size = local_decls.Size(); |
| size_t total_size = end - start + locals_size + 1; |
| byte* buffer = static_cast<byte*>(zone()->New(total_size)); |
| // Prepend the local decls to the code. |
| local_decls.Emit(buffer); |
| // Emit the code. |
| memcpy(buffer + locals_size, start, end - start); |
| // Append an extra end opcode. |
| buffer[total_size - 1] = kExprEnd; |
| |
| start = buffer; |
| end = buffer + total_size; |
| |
| CHECK_GE(kMaxInt, end - start); |
| int len = static_cast<int>(end - start); |
| function_->code = {builder_->AddBytes(Vector<const byte>(start, len)), |
| static_cast<uint32_t>(len)}; |
| |
| if (interpreter_) { |
| // Add the code to the interpreter. |
| interpreter_->SetFunctionCodeForTesting(function_, start, end); |
| } |
| |
| Handle<WasmCompiledModule> compiled_module( |
| builder_->instance_object()->compiled_module(), isolate()); |
| Handle<SeqOneByteString> wire_bytes(compiled_module->module_bytes(), |
| isolate()); |
| |
| compiler::ModuleEnv module_env = builder_->CreateModuleEnv(); |
| ErrorThrower thrower(isolate(), "WasmFunctionCompiler::Build"); |
| ScopedVector<uint8_t> func_wire_bytes(function_->code.length()); |
| memcpy(func_wire_bytes.start(), |
| wire_bytes->GetChars() + function_->code.offset(), |
| func_wire_bytes.length()); |
| ScopedVector<char> func_name(function_->name.length()); |
| memcpy(func_name.start(), wire_bytes->GetChars() + function_->name.offset(), |
| func_name.length()); |
| |
| FunctionBody func_body{function_->sig, function_->code.offset(), |
| func_wire_bytes.start(), func_wire_bytes.end()}; |
| compiler::WasmCompilationUnit unit( |
| isolate(), &module_env, func_body, func_name, function_->func_index, |
| CEntryStub(isolate(), 1).GetCode(), isolate()->counters(), |
| builder_->runtime_exception_support()); |
| unit.ExecuteCompilation(); |
| Handle<Code> code = unit.FinishCompilation(&thrower).ToHandleChecked(); |
| CHECK(!thrower.error()); |
| |
| // Manually add the deoptimization info that would otherwise be added |
| // during instantiation. Deopt data holds <WeakCell<wasm_instance>, |
| // func_index>. |
| DCHECK_EQ(0, code->deoptimization_data()->length()); |
| Handle<FixedArray> deopt_data = |
| isolate()->factory()->NewFixedArray(2, TENURED); |
| Handle<Object> weak_instance = |
| isolate()->factory()->NewWeakCell(builder_->instance_object()); |
| deopt_data->set(0, *weak_instance); |
| deopt_data->set(1, Smi::FromInt(static_cast<int>(function_index()))); |
| code->set_deoptimization_data(*deopt_data); |
| |
| // Build the TurboFan graph. |
| builder_->SetFunctionCode(function_index(), code); |
| |
| // Add to code table. |
| Handle<FixedArray> code_table = compiled_module->code_table(); |
| if (static_cast<int>(function_index()) >= code_table->length()) { |
| Handle<FixedArray> new_arr = isolate()->factory()->NewFixedArray( |
| static_cast<int>(function_index()) + 1); |
| code_table->CopyTo(0, *new_arr, 0, code_table->length()); |
| code_table = new_arr; |
| compiled_module->ReplaceCodeTableForTesting(code_table); |
| } |
| DCHECK(code_table->get(static_cast<int>(function_index())) |
| ->IsUndefined(isolate())); |
| code_table->set(static_cast<int>(function_index()), *code); |
| if (trap_handler::UseTrapHandler()) { |
| UnpackAndRegisterProtectedInstructions(isolate(), code_table); |
| } |
| } |
| |
| WasmFunctionCompiler::WasmFunctionCompiler(Zone* zone, FunctionSig* sig, |
| TestingModuleBuilder* builder, |
| const char* name) |
| : GraphAndBuilders(zone), |
| jsgraph(builder->isolate(), this->graph(), this->common(), nullptr, |
| nullptr, this->machine()), |
| sig(sig), |
| descriptor_(nullptr), |
| builder_(builder), |
| local_decls(zone, sig), |
| source_position_table_(this->graph()), |
| interpreter_(builder->interpreter()) { |
| // Get a new function from the testing module. |
| int index = builder->AddFunction(sig, Handle<Code>::null(), name); |
| function_ = builder_->GetFunctionAt(index); |
| } |
| |
| WasmFunctionCompiler::~WasmFunctionCompiler() { |
| if (trap_handler::UseTrapHandler() && |
| !builder_->GetFunctionCode(function_index()).is_null()) { |
| const int handler_index = builder_->GetFunctionCode(function_index()) |
| ->trap_handler_index() |
| ->value(); |
| trap_handler::ReleaseHandlerData(handler_index); |
| } |
| } |
| |
| FunctionSig* WasmRunnerBase::CreateSig(MachineType return_type, |
| Vector<MachineType> param_types) { |
| int return_count = return_type.IsNone() ? 0 : 1; |
| int param_count = param_types.length(); |
| |
| // Allocate storage array in zone. |
| ValueType* sig_types = zone_.NewArray<ValueType>(return_count + param_count); |
| |
| // Convert machine types to local types, and check that there are no |
| // MachineType::None()'s in the parameters. |
| int idx = 0; |
| if (return_count) sig_types[idx++] = WasmOpcodes::ValueTypeFor(return_type); |
| for (MachineType param : param_types) { |
| CHECK_NE(MachineType::None(), param); |
| sig_types[idx++] = WasmOpcodes::ValueTypeFor(param); |
| } |
| return new (&zone_) FunctionSig(return_count, param_count, sig_types); |
| } |
| |
| // static |
| bool WasmRunnerBase::trap_happened; |
| |
| } // namespace wasm |
| } // namespace internal |
| } // namespace v8 |