| // 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/compiler-source-position-table.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/struct-types.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; |
| |
| static constexpr bool kNotShared = false; |
| |
| 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, |
| base::Vector<const uint8_t> bytes, |
| SourcePositionTable* source_position_table, |
| int inlining_id) |
| : wasm::Decoder(bytes.begin(), bytes.end()), |
| module_(module), |
| mcgraph_(mcgraph), |
| body_(body), |
| graph_(mcgraph->graph()), |
| gasm_(mcgraph, zone), |
| source_position_table_(source_position_table), |
| inlining_id_(inlining_id) { |
| // +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->AllocateArray<Node*>(params_extended); |
| for (unsigned i = 0; i < params_extended; i++) { |
| parameters_[i] = nullptr; |
| } |
| // Instance node at parameter 0. |
| trusted_data_node_ = Param(wasm::kWasmInstanceDataParameterIndex); |
| } |
| |
| Node* Param(int index, const char* debug_name = nullptr) { |
| DCHECK_NOT_NULL(graph_->start()); |
| // Turbofan allows negative parameter indices. |
| DCHECK_GE(index, kMinParameterIndex); |
| int array_index = index - kMinParameterIndex; |
| if (parameters_[array_index] == nullptr) { |
| Node* param = graph_->NewNode( |
| mcgraph_->common()->Parameter(index, debug_name), graph_->start()); |
| if (index > wasm::kWasmInstanceDataParameterIndex) { |
| // Add a type guard to keep type information based on the inlinee's |
| // signature. |
| wasm::ValueType type = body_.sig->GetParam(index - 1); |
| Type tf_type = compiler::Type::Wasm(type, module_, graph_->zone()); |
| param = gasm_.TypeGuard(tf_type, param); |
| } |
| parameters_[array_index] = param; |
| } |
| 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. |
| base::SmallVector<Value, 4> stack; |
| while (is_inlineable_) { |
| WasmOpcode opcode = ReadOpcode(); |
| switch (opcode) { |
| case wasm::kExprAnyConvertExtern: |
| DCHECK(!stack.empty()); |
| stack.back() = ParseAnyConvertExtern(stack.back()); |
| continue; |
| case wasm::kExprExternConvertAny: |
| DCHECK(!stack.empty()); |
| stack.back() = ParseExternConvertAny(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 ParseAnyConvertExtern(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::Generic( |
| wasm::GenericKind::kAny, input.type.nullability(), kNotShared); |
| Node* internalized = gasm_.WasmAnyConvertExtern(input.node); |
| return TypeNode(internalized, result_type); |
| } |
| |
| Value ParseExternConvertAny(Value input) { |
| DCHECK(input.type.is_reference()); |
| wasm::ValueType result_type = wasm::ValueType::Generic( |
| wasm::GenericKind::kExtern, input.type.nullability(), kNotShared); |
| Node* internalized = gasm_.WasmExternConvertAny(input.node); |
| return TypeNode(internalized, result_type); |
| } |
| |
| Value ParseLocalGet() { |
| uint32_t index = consume_u32v(); |
| DCHECK_LT(index, body_.sig->parameter_count()); |
| return TypeNode(Param(index + 1), body_.sig->GetParam(index)); |
| } |
| |
| Value ParseStructGet(Value struct_val, WasmOpcode opcode) { |
| wasm::ModuleTypeIndex 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); |
| SetSourcePosition(member); |
| return TypeNode(member, struct_type->field(field_index).Unpacked()); |
| } |
| |
| void ParseStructSet(Value wasm_struct, Value value) { |
| wasm::ModuleTypeIndex 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); |
| SetSourcePosition(gasm_.effect()); |
| } |
| |
| // TODO(14616): Implement for shared types. |
| 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); |
| SetSourcePosition(gasm_.effect()); |
| gasm_.Goto(&done); |
| gasm_.Bind(&done); |
| // Add TypeGuard for graph typing. |
| TFGraph* graph = mcgraph_->graph(); |
| wasm::ValueType result_type = wasm::ValueType::Generic( |
| wasm::GenericKind::kArray, |
| null_succeeds ? wasm::kNullable : wasm::kNonNullable, kNotShared); |
| 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 TypeNode(type_guard, result_type); |
| } |
| wasm::ModuleTypeIndex target_type_index{static_cast<uint32_t>(heap_index)}; |
| if (module_->has_signature(target_type_index)) { |
| is_inlineable_ = false; |
| return {}; |
| } |
| wasm::ValueType target_type = wasm::ValueType::RefMaybeNull( |
| module_->heap_type(target_type_index), |
| null_succeeds ? wasm::kNullable : wasm::kNonNullable); |
| Node* rtt = mcgraph_->graph()->NewNode( |
| gasm_.simplified()->RttCanon(target_type.ref_index()), |
| trusted_data_node_); |
| // Technically this is incorrect: the {rtt} node doesn't hold a reference |
| // to an object of type {target_type}, but to such an object's map. But |
| // we only need this type annotation so {ReduceWasmTypeCast} can get to |
| // the {ref_index}, we never need the type's {kind()}. |
| TypeNode(rtt, wasm::ValueType::Ref(target_type.heap_type())); |
| Node* cast = gasm_.WasmTypeCast( |
| input.node, rtt, |
| {input.type, target_type, |
| module_->type(target_type_index).is_final ? kExactMatchOnly |
| : kMayBeSubtype}); |
| SetSourcePosition(cast); |
| return TypeNode(cast, target_type); |
| } |
| |
| Value ParseArrayLen(Value input) { |
| DCHECK(wasm::IsHeapSubtypeOf(input.type.heap_type(), |
| wasm::kWasmArrayRef.heap_type(), module_)); |
| const CheckForNull null_check = |
| input.type.is_nullable() ? kWithNullCheck : kWithoutNullCheck; |
| Node* len = gasm_.ArrayLength(input.node, null_check); |
| SetSourcePosition(len); |
| return TypeNode(len, wasm::kWasmI32); |
| } |
| |
| Value ParseArrayGet(Value array, Value index, WasmOpcode opcode) { |
| wasm::ModuleTypeIndex 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); |
| SetSourcePosition(length); |
| gasm_.TrapUnless(gasm_.Uint32LessThan(index.node, length), |
| TrapId::kTrapArrayOutOfBounds); |
| SetSourcePosition(gasm_.effect()); |
| // Perform array.get. |
| Node* element = |
| gasm_.ArrayGet(array.node, index.node, array_type, is_signed); |
| return TypeNode(element, array_type->element_type().Unpacked()); |
| } |
| |
| void ParseArraySet(Value array, Value index, Value value) { |
| wasm::ModuleTypeIndex 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); |
| SetSourcePosition(length); |
| gasm_.TrapUnless(gasm_.Uint32LessThan(index.node, length), |
| TrapId::kTrapArrayOutOfBounds); |
| SetSourcePosition(gasm_.effect()); |
| // Perform array.set. |
| gasm_.ArraySet(array.node, index.node, value.node, array_type); |
| } |
| |
| WasmOpcode ReadOpcode() { |
| DCHECK_LT(pc_, end_); |
| instruction_start_ = pc(); |
| 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; |
| } |
| |
| Value TypeNode(Node* node, wasm::ValueType type) { |
| compiler::NodeProperties::SetType( |
| node, compiler::Type::Wasm(type, module_, graph_->zone())); |
| return {node, type}; |
| } |
| |
| void SetSourcePosition(Node* node) { |
| if (!source_position_table_->IsEnabled()) return; |
| int offset = static_cast<int>(instruction_start_ - start()); |
| source_position_table_->SetSourcePosition( |
| node, SourcePosition(offset, inlining_id_)); |
| } |
| |
| const wasm::WasmModule* module_; |
| MachineGraph* mcgraph_; |
| const wasm::FunctionBody& body_; |
| Node** parameters_; |
| TFGraph* graph_; |
| Node* trusted_data_node_; |
| WasmGraphAssembler gasm_; |
| SourcePositionTable* source_position_table_ = nullptr; |
| const uint8_t* instruction_start_ = pc_; |
| int inlining_id_; |
| bool is_inlineable_ = true; |
| }; |
| |
| } // anonymous namespace |
| |
| bool WasmIntoJSInliner::TryInlining(Zone* zone, const wasm::WasmModule* module, |
| MachineGraph* mcgraph, |
| const wasm::FunctionBody& body, |
| base::Vector<const uint8_t> bytes, |
| SourcePositionTable* source_position_table, |
| int inlining_id) { |
| WasmIntoJSInlinerImpl inliner(zone, module, mcgraph, body, bytes, |
| source_position_table, inlining_id); |
| return inliner.TryInlining(); |
| } |
| |
| } // namespace v8::internal::compiler |