diff options
Diffstat (limited to 'deps/v8/src/compiler/wasm-inlining-into-js.cc')
-rw-r--r-- | deps/v8/src/compiler/wasm-inlining-into-js.cc | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/deps/v8/src/compiler/wasm-inlining-into-js.cc b/deps/v8/src/compiler/wasm-inlining-into-js.cc new file mode 100644 index 0000000000..ac52e9ee52 --- /dev/null +++ b/deps/v8/src/compiler/wasm-inlining-into-js.cc @@ -0,0 +1,346 @@ +// Copyright 2023 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/compiler/wasm-inlining-into-js.h" + +#include "src/compiler/wasm-compiler-definitions.h" +#include "src/compiler/wasm-compiler.h" +#include "src/compiler/wasm-graph-assembler.h" +#include "src/wasm/decoder.h" +#include "src/wasm/wasm-linkage.h" +#include "src/wasm/wasm-opcodes-inl.h" +#include "src/wasm/wasm-subtyping.h" + +namespace v8::internal::compiler { + +namespace { + +using wasm::WasmOpcode; +using wasm::WasmOpcodes; + +class WasmIntoJSInlinerImpl : private wasm::Decoder { + using ValidationTag = NoValidationTag; + + struct Value { + Node* node = nullptr; + wasm::ValueType type = wasm::kWasmBottom; + }; + + public: + WasmIntoJSInlinerImpl(Zone* zone, const wasm::WasmModule* module, + MachineGraph* mcgraph, const wasm::FunctionBody& body, + const base::Vector<const byte>& bytes) + : wasm::Decoder(bytes.begin(), bytes.end()), + module_(module), + mcgraph_(mcgraph), + body_(body), + graph_(mcgraph->graph()), + gasm_(mcgraph, zone) { + // +1 for instance node. + size_t params = body.sig->parameter_count() + 1; + Node* start = + graph_->NewNode(mcgraph->common()->Start(static_cast<int>(params))); + graph_->SetStart(start); + graph_->SetEnd(graph_->NewNode(mcgraph->common()->End(0))); + gasm_.InitializeEffectControl(start, start); + + // Initialize parameter nodes. + // We have to add another +1 as the minimum parameter index is actually + // -1, not 0... + size_t params_extended = params + 1; + parameters_ = zone->NewArray<Node*>(params_extended); + for (unsigned i = 0; i < params_extended; i++) { + parameters_[i] = nullptr; + } + // Instance node at parameter 0. + instance_node_ = Param(wasm::kWasmInstanceParameterIndex); + } + + Node* Param(int index, const char* debug_name = nullptr) { + DCHECK_NOT_NULL(graph_->start()); + // Turbofan allows negative parameter indices. + static constexpr int kMinParameterIndex = -1; + DCHECK_GE(index, kMinParameterIndex); + int array_index = index - kMinParameterIndex; + if (parameters_[array_index] == nullptr) { + parameters_[array_index] = graph_->NewNode( + mcgraph_->common()->Parameter(index, debug_name), graph_->start()); + } + return parameters_[array_index]; + } + + bool TryInlining() { + if (body_.sig->return_count() > 1) { + return false; // Multi-return is not supported. + } + // Parse locals. + if (consume_u32v() != 0) { + // Functions with locals are not supported. + return false; + } + // Parse body. + // TODO(mliedtke): Use zone vector? + base::SmallVector<Value, 4> stack; + while (is_inlineable_) { + WasmOpcode opcode = ReadOpcode(); + switch (opcode) { + case wasm::kExprExternInternalize: + DCHECK(!stack.empty()); + stack.back() = ParseExternInternalize(stack.back()); + continue; + case wasm::kExprExternExternalize: + DCHECK(!stack.empty()); + stack.back() = ParseExternExternalize(stack.back()); + continue; + case wasm::kExprRefCast: + case wasm::kExprRefCastNull: + DCHECK(!stack.empty()); + stack.back() = + ParseRefCast(stack.back(), opcode == wasm::kExprRefCastNull); + continue; + case wasm::kExprArrayLen: + DCHECK(!stack.empty()); + stack.back() = ParseArrayLen(stack.back()); + continue; + case wasm::kExprArrayGet: + case wasm::kExprArrayGetS: + case wasm::kExprArrayGetU: { + DCHECK_GE(stack.size(), 2); + Value index = stack.back(); + stack.pop_back(); + Value array = stack.back(); + stack.back() = ParseArrayGet(array, index, opcode); + continue; + } + case wasm::kExprArraySet: { + DCHECK_GE(stack.size(), 3); + Value value = stack.back(); + stack.pop_back(); + Value index = stack.back(); + stack.pop_back(); + Value array = stack.back(); + stack.pop_back(); + ParseArraySet(array, index, value); + continue; + } + case wasm::kExprStructGet: + case wasm::kExprStructGetS: + case wasm::kExprStructGetU: + DCHECK(!stack.empty()); + stack.back() = ParseStructGet(stack.back(), opcode); + continue; + case wasm::kExprStructSet: { + DCHECK_GE(stack.size(), 2); + Value value = stack.back(); + stack.pop_back(); + Value wasm_struct = stack.back(); + stack.pop_back(); + ParseStructSet(wasm_struct, value); + continue; + } + case wasm::kExprLocalGet: + stack.push_back(ParseLocalGet()); + continue; + case wasm::kExprDrop: + DCHECK(!stack.empty()); + stack.pop_back(); + continue; + case wasm::kExprEnd: { + DCHECK_LT(stack.size(), 2); + int return_count = static_cast<int>(stack.size()); + base::SmallVector<Node*, 8> buf(return_count + 3); + buf[0] = mcgraph_->Int32Constant(0); + if (return_count) { + buf[1] = stack.back().node; + } + buf[return_count + 1] = gasm_.effect(); + buf[return_count + 2] = gasm_.control(); + Node* ret = graph_->NewNode(mcgraph_->common()->Return(return_count), + return_count + 3, buf.data()); + + gasm_.MergeControlToEnd(ret); + return true; + } + default: + // Instruction not supported for inlining. + return false; + } + } + // The decoder found an instruction it couldn't inline successfully. + return false; + } + + private: + Value ParseExternInternalize(Value input) { + DCHECK(input.type.is_reference_to(wasm::HeapType::kExtern) || + input.type.is_reference_to(wasm::HeapType::kNoExtern)); + wasm::ValueType result_type = wasm::ValueType::RefMaybeNull( + wasm::HeapType::kAny, input.type.is_nullable() + ? wasm::Nullability::kNullable + : wasm::Nullability::kNonNullable); + Node* internalized = gasm_.WasmExternInternalize(input.node); + return {internalized, result_type}; + } + + Value ParseExternExternalize(Value input) { + DCHECK(input.type.is_reference()); + wasm::ValueType result_type = wasm::ValueType::RefMaybeNull( + wasm::HeapType::kExtern, input.type.is_nullable() + ? wasm::Nullability::kNullable + : wasm::Nullability::kNonNullable); + Node* internalized = gasm_.WasmExternExternalize(input.node); + return {internalized, result_type}; + } + + Value ParseLocalGet() { + uint32_t index = consume_u32v(); + DCHECK_LT(index, body_.sig->parameter_count()); + return {Param(index + 1), body_.sig->GetParam(index)}; + } + + Value ParseStructGet(Value struct_val, WasmOpcode opcode) { + uint32_t struct_index = consume_u32v(); + DCHECK(module_->has_struct(struct_index)); + const wasm::StructType* struct_type = module_->struct_type(struct_index); + uint32_t field_index = consume_u32v(); + DCHECK_GT(struct_type->field_count(), field_index); + const bool is_signed = opcode == wasm::kExprStructGetS; + const CheckForNull null_check = + struct_val.type.is_nullable() ? kWithNullCheck : kWithoutNullCheck; + Node* member = gasm_.StructGet(struct_val.node, struct_type, field_index, + is_signed, null_check); + return {member, struct_type->field(field_index).Unpacked()}; + } + + void ParseStructSet(Value wasm_struct, Value value) { + uint32_t struct_index = consume_u32v(); + DCHECK(module_->has_struct(struct_index)); + const wasm::StructType* struct_type = module_->struct_type(struct_index); + uint32_t field_index = consume_u32v(); + DCHECK_GT(struct_type->field_count(), field_index); + const CheckForNull null_check = + wasm_struct.type.is_nullable() ? kWithNullCheck : kWithoutNullCheck; + gasm_.StructSet(wasm_struct.node, value.node, struct_type, field_index, + null_check); + } + + Value ParseRefCast(Value input, bool null_succeeds) { + auto [heap_index, length] = read_i33v<ValidationTag>(pc_); + pc_ += length; + if (heap_index < 0) { + if ((heap_index & 0x7f) != wasm::kArrayRefCode) { + // Abstract casts for non array type are not supported. + is_inlineable_ = false; + return {}; + } + auto done = gasm_.MakeLabel(); + // Abstract cast to array. + if (input.type.is_nullable() && null_succeeds) { + gasm_.GotoIf(gasm_.IsNull(input.node, input.type), &done); + } + gasm_.TrapIf(gasm_.IsSmi(input.node), TrapId::kTrapIllegalCast); + gasm_.TrapUnless(gasm_.HasInstanceType(input.node, WASM_ARRAY_TYPE), + TrapId::kTrapIllegalCast); + gasm_.Goto(&done); + gasm_.Bind(&done); + // Add TypeGuard for graph typing. + Graph* graph = mcgraph_->graph(); + wasm::ValueType result_type = wasm::ValueType::RefMaybeNull( + wasm::HeapType::kArray, + null_succeeds ? wasm::kNullable : wasm::kNonNullable); + Node* type_guard = + graph->NewNode(mcgraph_->common()->TypeGuard( + Type::Wasm(result_type, module_, graph->zone())), + input.node, gasm_.effect(), gasm_.control()); + gasm_.InitializeEffectControl(type_guard, gasm_.control()); + return {type_guard, result_type}; + } + if (module_->has_signature(static_cast<uint32_t>(heap_index))) { + is_inlineable_ = false; + return {}; + } + wasm::ValueType target_type = wasm::ValueType::RefMaybeNull( + static_cast<uint32_t>(heap_index), + null_succeeds ? wasm::kNullable : wasm::kNonNullable); + Node* rtt = mcgraph_->graph()->NewNode( + gasm_.simplified()->RttCanon(target_type.ref_index()), instance_node_); + Node* cast = gasm_.WasmTypeCast(input.node, rtt, {input.type, target_type}); + return {cast, target_type}; + } + + Value ParseArrayLen(Value input) { + DCHECK(wasm::IsHeapSubtypeOf(input.type.heap_type(), + wasm::HeapType(wasm::HeapType::kArray), + module_)); + const CheckForNull null_check = + input.type.is_nullable() ? kWithNullCheck : kWithoutNullCheck; + Node* len = gasm_.ArrayLength(input.node, null_check); + return {len, wasm::kWasmI32}; + } + + Value ParseArrayGet(Value array, Value index, WasmOpcode opcode) { + uint32_t array_index = consume_u32v(); + DCHECK(module_->has_array(array_index)); + const wasm::ArrayType* array_type = module_->array_type(array_index); + const bool is_signed = opcode == WasmOpcode::kExprArrayGetS; + const CheckForNull null_check = + array.type.is_nullable() ? kWithNullCheck : kWithoutNullCheck; + // Perform bounds check. + Node* length = gasm_.ArrayLength(array.node, null_check); + gasm_.TrapUnless(gasm_.Uint32LessThan(index.node, length), + TrapId::kTrapArrayOutOfBounds); + // Perform array.get. + Node* element = + gasm_.ArrayGet(array.node, index.node, array_type, is_signed); + return {element, array_type->element_type().Unpacked()}; + } + + void ParseArraySet(Value array, Value index, Value value) { + uint32_t array_index = consume_u32v(); + DCHECK(module_->has_array(array_index)); + const wasm::ArrayType* array_type = module_->array_type(array_index); + const CheckForNull null_check = + array.type.is_nullable() ? kWithNullCheck : kWithoutNullCheck; + // Perform bounds check. + Node* length = gasm_.ArrayLength(array.node, null_check); + gasm_.TrapUnless(gasm_.Uint32LessThan(index.node, length), + TrapId::kTrapArrayOutOfBounds); + // Perform array.set. + gasm_.ArraySet(array.node, index.node, value.node, array_type); + } + + WasmOpcode ReadOpcode() { + DCHECK_LT(pc_, end_); + WasmOpcode opcode = static_cast<WasmOpcode>(*pc_); + if (!WasmOpcodes::IsPrefixOpcode(opcode)) { + ++pc_; + return opcode; + } + auto [opcode_with_prefix, length] = + read_prefixed_opcode<ValidationTag>(pc_); + pc_ += length; + return opcode_with_prefix; + } + + const wasm::WasmModule* module_; + MachineGraph* mcgraph_; + const wasm::FunctionBody& body_; + Node** parameters_; + Graph* graph_; + Node* instance_node_; + WasmGraphAssembler gasm_; + bool is_inlineable_ = true; +}; + +} // anonymous namespace + +bool WasmIntoJSInliner::TryInlining(Zone* zone, const wasm::WasmModule* module, + MachineGraph* mcgraph, + const wasm::FunctionBody& body, + const base::Vector<const byte>& bytes) { + WasmIntoJSInlinerImpl inliner(zone, module, mcgraph, body, bytes); + return inliner.TryInlining(); +} + +} // namespace v8::internal::compiler |