// 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), execution_mode_(mode), runtime_exception_support_(exception_support), lower_simd_(mode == kExecuteSimdLowered) { WasmJs::Install(isolate_, true); test_module_.globals_size = kMaxGlobalsSize; memset(globals_data_, 0, sizeof(globals_data_)); instance_object_ = InitInstanceObject(); if (mode == kExecuteInterpreter) { 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_object()); test_module_.has_memory = true; const bool enable_guard_regions = trap_handler::IsTrapHandlerEnabled() && 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()); mem_start_ = reinterpret_cast<byte*>(new_buffer->backing_store()); mem_size_ = size; CHECK(size == 0 || mem_start_); memset(mem_start_, 0, 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()->get()->mem_start = mem_start_; instance_object_->wasm_context()->get()->mem_size = mem_size_; return mem_start_; } uint32_t TestingModuleBuilder::AddFunction(FunctionSig* sig, 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()); if (FLAG_wasm_jit_to_native) { native_module_->ResizeCodeTableForTest(index); } 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(Handle<Code>::null()); 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, nullptr); js_imports_table->set(0, *isolate_->native_context()); if (FLAG_wasm_jit_to_native) { native_module_->ResizeCodeTableForTest(index); Handle<Code> wrapper = compiler::CompileWasmToJSWrapper( isolate_, jsfunc, sig, index, test_module_.origin(), trap_handler::IsTrapHandlerEnabled(), js_imports_table); native_module_->AddCodeCopy(wrapper, wasm::WasmCode::kWasmToJsWrapper, index); } else { // TODO(6792): No longer needed once WebAssembly code is off heap. CodeSpaceMemoryModificationScope modification_scope(isolate_->heap()); Handle<Code> code = compiler::CompileWasmToJSWrapper( isolate_, jsfunc, sig, index, test_module_.origin(), trap_handler::IsTrapHandlerEnabled(), 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. Link(); WasmCodeWrapper code = FLAG_wasm_jit_to_native ? WasmCodeWrapper(native_module_->GetCode(index)) : WasmCodeWrapper(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, trap_handler::IsTrapHandlerEnabled()); 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]); } 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(test_module_.signature_map.Find(function.sig))); if (FLAG_wasm_jit_to_native) { Handle<Foreign> foreign_holder = isolate_->factory()->NewForeign( native_module_->GetCode(function.func_index) ->instructions() .start(), TENURED); function_table->set(j, *foreign_holder); } else { 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() { return {&test_module_, function_tables_, signature_tables_, function_code_, Handle<Code>::null(), trap_handler::IsTrapHandlerEnabled()}; } 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_, test_module_ptr_, code_table, export_wrappers, function_tables_, signature_tables_, trap_handler::IsTrapHandlerEnabled()); compiled_module->OnWasmModuleDecodingComplete(shared_module_data); // 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. native_module_ = compiled_module->GetNativeModule(); 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); auto instance = WasmInstanceObject::New(isolate_, compiled_module); instance->wasm_context()->get()->globals_start = globals_data_; Handle<WeakCell> weak_instance = isolate()->factory()->NewWeakCell(instance); compiled_module->set_weak_owning_instance(weak_instance); return instance; } void TestBuildingGraphWithBuilder(compiler::WasmGraphBuilder* builder, Zone* zone, FunctionSig* sig, const byte* start, const byte* end) { DecodeResult result = BuildTFGraph(zone->allocator(), builder, sig, start, end); if (result.failed()) { #ifdef DEBUG 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); } #endif 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(); } } 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) { if (module) { compiler::WasmGraphBuilder builder( module, zone, jsgraph, CEntryStub(jsgraph->isolate(), 1).GetCode(), sig, source_position_table, runtime_exception_support); TestBuildingGraphWithBuilder(&builder, zone, sig, start, end); } else { compiler::WasmGraphBuilder builder( trap_handler::IsTrapHandlerEnabled(), zone, jsgraph, CEntryStub(jsgraph->isolate(), 1).GetCode(), sig, source_position_table, runtime_exception_support); TestBuildingGraphWithBuilder(&builder, zone, sig, start, end); } } 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"), graph()->zone(), Code::C_WASM_ENTRY); code_ = compiler::Pipeline::GenerateCodeForTesting( &info, isolate, 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()); NativeModule* native_module = compiled_module->GetNativeModule(); if (FLAG_wasm_jit_to_native) { native_module->ResizeCodeTableForTest(function_->func_index); } 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::CompilationMode comp_mode = builder_->execution_mode() == WasmExecutionMode::kExecuteLiftoff ? compiler::WasmCompilationUnit::CompilationMode::kLiftoff : compiler::WasmCompilationUnit::CompilationMode::kTurbofan; compiler::WasmCompilationUnit unit( isolate(), &module_env, native_module, func_body, func_name, function_->func_index, CEntryStub(isolate(), 1).GetCode(), comp_mode, isolate()->counters(), builder_->runtime_exception_support(), builder_->lower_simd()); unit.ExecuteCompilation(); WasmCodeWrapper code_wrapper = unit.FinishCompilation(&thrower); CHECK(!thrower.error()); if (!FLAG_wasm_jit_to_native) { Handle<Code> code = code_wrapper.GetCode(); // TODO(6792): No longer needed once WebAssembly code is off heap. CodeSpaceMemoryModificationScope modification_scope(isolate()->heap()); // 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::IsTrapHandlerEnabled()) { UnpackAndRegisterProtectedInstructionsGC(isolate(), code_table); } } else { if (trap_handler::IsTrapHandlerEnabled()) { UnpackAndRegisterProtectedInstructions(isolate(), native_module); } } } 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, name); function_ = builder_->GetFunctionAt(index); } WasmFunctionCompiler::~WasmFunctionCompiler() { if (!FLAG_wasm_jit_to_native) { if (trap_handler::IsTrapHandlerEnabled() && !builder_->GetFunctionCode(function_index()).is_null()) { const int handler_index = builder_->GetFunctionCode(function_index()) .GetCode() ->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