diff --git a/deps/v8/include/v8-version.h b/deps/v8/include/v8-version.h index aabd09d38d575f..1f55ca3c03cec0 100644 --- a/deps/v8/include/v8-version.h +++ b/deps/v8/include/v8-version.h @@ -11,7 +11,7 @@ #define V8_MAJOR_VERSION 14 #define V8_MINOR_VERSION 2 #define V8_BUILD_NUMBER 231 -#define V8_PATCH_LEVEL 9 +#define V8_PATCH_LEVEL 14 // Use 1 for candidates and 0 otherwise. // (Boolean macro values are not supported by all preprocessors.) diff --git a/deps/v8/src/builtins/loong64/builtins-loong64.cc b/deps/v8/src/builtins/loong64/builtins-loong64.cc index 68dcbc37787142..17afb4d124d8e5 100644 --- a/deps/v8/src/builtins/loong64/builtins-loong64.cc +++ b/deps/v8/src/builtins/loong64/builtins-loong64.cc @@ -3185,12 +3185,9 @@ void ReloadParentStack(MacroAssembler* masm, Register return_reg, Register parent = tmp2; __ Ld_d(parent, MemOperand(active_stack, wasm::kStackParentOffset)); - // Update active stack. - __ StoreRootRelative(IsolateData::active_stack_offset(), parent); - // Switch stack! - SwitchStacks(masm, ExternalReference::wasm_return_stack(), active_stack, - nullptr, no_reg, {return_reg, return_value, context, parent}); + SwitchStacks(masm, ExternalReference::wasm_return_stack(), parent, nullptr, + no_reg, {return_reg, return_value, context, parent}); LoadJumpBuffer(masm, parent, false, tmp3); } @@ -3425,11 +3422,8 @@ void Builtins::Generate_WasmSuspend(MacroAssembler* masm) { ResetWasmJspiFrameStackSlots(masm); Label resume; - DEFINE_REG(stack); - __ LoadRootRelative(stack, IsolateData::active_stack_offset()); DEFINE_REG(scratch); - // Update active stack. DEFINE_REG(parent); __ LoadProtectedPointerField( parent, FieldMemOperand(suspender, WasmSuspenderObject::kParentOffset)); @@ -3437,10 +3431,9 @@ void Builtins::Generate_WasmSuspend(MacroAssembler* masm) { __ LoadExternalPointerField( target_stack, FieldMemOperand(parent, WasmSuspenderObject::kStackOffset), kWasmStackMemoryTag); - __ StoreRootRelative(IsolateData::active_stack_offset(), target_stack); - SwitchStacks(masm, ExternalReference::wasm_suspend_stack(), stack, &resume, - no_reg, {target_stack, suspender, parent}); + SwitchStacks(masm, ExternalReference::wasm_suspend_stack(), target_stack, + &resume, no_reg, {target_stack, suspender, parent}); __ StoreRootRelative(IsolateData::active_suspender_offset(), parent); __ LoadTaggedField( kReturnRegister0, @@ -3512,11 +3505,8 @@ void Generate_WasmResumeHelper(MacroAssembler* masm, wasm::OnResume on_resume) { target_stack, FieldMemOperand(suspender, WasmSuspenderObject::kStackOffset), kWasmStackMemoryTag); - - __ StoreRootRelative(IsolateData::active_stack_offset(), target_stack); - SwitchStacks(masm, ExternalReference::wasm_resume_jspi_stack(), active_stack, + SwitchStacks(masm, ExternalReference::wasm_resume_jspi_stack(), target_stack, &suspend, suspender, {target_stack}); - regs.ResetExcept(target_stack); // ------------------------------------------- @@ -3563,11 +3553,8 @@ void Builtins::Generate_WasmFXResume(MacroAssembler* masm) { __ EnterFrame(StackFrame::WASM_STACK_EXIT); Register target_stack = WasmFXResumeDescriptor::GetRegisterParameter(0); Label suspend; - Register active_stack = a0; - __ LoadRootRelative(active_stack, IsolateData::active_stack_offset()); - __ StoreRootRelative(IsolateData::active_stack_offset(), target_stack); SwitchStacks(masm, ExternalReference::wasm_resume_wasmfx_stack(), - active_stack, &suspend, no_reg, {target_stack}); + target_stack, &suspend, no_reg, {target_stack}); LoadJumpBuffer(masm, target_stack, true, a1); __ Trap(); __ bind(&suspend); @@ -3580,9 +3567,8 @@ void Builtins::Generate_WasmFXReturn(MacroAssembler* masm) { __ LoadRootRelative(active_stack, IsolateData::active_stack_offset()); Register parent = a1; __ Move(parent, MemOperand(active_stack, wasm::kStackParentOffset)); - __ StoreRootRelative(IsolateData::active_stack_offset(), parent); - SwitchStacks(masm, ExternalReference::wasm_return_stack(), active_stack, - nullptr, no_reg, {parent}); + SwitchStacks(masm, ExternalReference::wasm_return_stack(), parent, nullptr, + no_reg, {parent}); LoadJumpBuffer(masm, parent, true, a2); __ Trap(); } @@ -3599,14 +3585,15 @@ void SwitchToAllocatedStack(MacroAssembler* masm, RegisterAllocator& regs, Label* suspend) { ResetWasmJspiFrameStackSlots(masm); DEFINE_SCOPED(scratch) - DEFINE_REG(parent_stack) - __ LoadRootRelative(parent_stack, IsolateData::active_stack_offset()); - __ Ld_d(parent_stack, MemOperand(parent_stack, wasm::kStackParentOffset)); - - SwitchStacks(masm, ExternalReference::wasm_start_stack(), parent_stack, - suspend, no_reg, {wasm_instance, wrapper_buffer}); + DEFINE_REG(stack) + __ LoadRootRelative(stack, IsolateData::active_suspender_offset()); + __ LoadExternalPointerField( + stack, FieldMemOperand(stack, WasmSuspenderObject::kStackOffset), + kWasmStackMemoryTag); + SwitchStacks(masm, ExternalReference::wasm_start_stack(), stack, suspend, + no_reg, {wasm_instance, wrapper_buffer}); - FREE_REG(parent_stack); + FREE_REG(stack); // Save the old stack's fp in t0, and use it to access the parameters in // the parent frame. regs.Pinned(t1, &original_fp); diff --git a/deps/v8/src/codegen/loong64/macro-assembler-loong64.cc b/deps/v8/src/codegen/loong64/macro-assembler-loong64.cc index 0f32a023bffe6a..bf3619d512d10a 100644 --- a/deps/v8/src/codegen/loong64/macro-assembler-loong64.cc +++ b/deps/v8/src/codegen/loong64/macro-assembler-loong64.cc @@ -142,6 +142,58 @@ void MacroAssembler::PushStandardFrame(Register function_reg) { Add_d(fp, sp, Operand(offset)); } +void MacroAssembler::PreCheckSkippedWriteBarrier(Register object, + Register value, + Register scratch, Label* ok) { + ASM_CODE_COMMENT(this); + DCHECK(!AreAliased(object, scratch)); + DCHECK(!AreAliased(value, scratch)); + + // The most common case: Static write barrier elimination is allowed on the + // last young allocation. + { + UseScratchRegisterScope temps(this); + Register scratch1 = temps.Acquire(); + Sub_d(scratch, object, kHeapObjectTag); + Ld_d(scratch1, MemOperand(kRootRegister, + IsolateData::last_young_allocation_offset())); + Branch(ok, Condition::kEqual, scratch, Operand(scratch1)); + } + + // Write barier can also be removed if value is in read-only space. + CheckPageFlag(value, MemoryChunk::kIsInReadOnlyHeapMask, ne, ok); + + Label not_ok; + + // Handle allocation folding, allow WB removal if: + // LAB start <= last_young_allocation_ < (object address+1) < LAB top + // Note that object has tag bit set, so object == object address+1. + { + UseScratchRegisterScope temps(this); + Register scratch1 = temps.Acquire(); + + // Check LAB start <= last_young_allocation_. + Ld_d(scratch, MemOperand(kRootRegister, + IsolateData::new_allocation_info_start_offset())); + Ld_d(scratch1, MemOperand(kRootRegister, + IsolateData::last_young_allocation_offset())); + Branch(¬_ok, Condition::kUnsignedGreaterThan, scratch, + Operand(scratch1)); + + // Check last_young_allocation_ < (object address+1). + Branch(¬_ok, Condition::kUnsignedGreaterThanEqual, scratch1, + Operand(object)); + + // Check (object address+1) < LAB top. + Ld_d(scratch, MemOperand(kRootRegister, + IsolateData::new_allocation_info_top_offset())); + Branch(ok, Condition::kUnsignedLessThan, object, Operand(scratch)); + } + + // Slow path: Potentially check more cases in C++. + bind(¬_ok); +} + // Clobbers object, dst, value, and ra, if (ra_status == kRAHasBeenSaved) // The register 'object' contains a heap object pointer. The heap object // tag is shifted away. @@ -582,11 +634,33 @@ void MacroAssembler::CallVerifySkippedWriteBarrierStubSaveRegisters( void MacroAssembler::CallVerifySkippedWriteBarrierStub(Register object, Register value) { ASM_CODE_COMMENT(this); + UseScratchRegisterScope temps(this); + Register scratch = temps.Acquire(); + PrepareCallCFunction(2, scratch); MovePair(kCArgRegs[0], object, kCArgRegs[1], value); CallCFunction(ExternalReference::verify_skipped_write_barrier(), 2, SetIsolateDataSlots::kNo); } +void MacroAssembler::CallVerifySkippedIndirectWriteBarrierStubSaveRegisters( + Register object, Register value, SaveFPRegsMode fp_mode) { + ASM_CODE_COMMENT(this); + PushCallerSaved(fp_mode); + CallVerifySkippedIndirectWriteBarrierStub(object, value); + PopCallerSaved(fp_mode); +} + +void MacroAssembler::CallVerifySkippedIndirectWriteBarrierStub(Register object, + Register value) { + ASM_CODE_COMMENT(this); + UseScratchRegisterScope temps(this); + Register scratch = temps.Acquire(); + PrepareCallCFunction(2, scratch); + MovePair(kCArgRegs[0], object, kCArgRegs[1], value); + CallCFunction(ExternalReference::verify_skipped_indirect_write_barrier(), 2, + SetIsolateDataSlots::kNo); +} + void MacroAssembler::MoveObjectAndSlot(Register dst_object, Register dst_slot, Register object, Operand offset) { ASM_CODE_COMMENT(this); diff --git a/deps/v8/src/codegen/loong64/macro-assembler-loong64.h b/deps/v8/src/codegen/loong64/macro-assembler-loong64.h index 0e799fb3434ade..a2f57e5ce0e323 100644 --- a/deps/v8/src/codegen/loong64/macro-assembler-loong64.h +++ b/deps/v8/src/codegen/loong64/macro-assembler-loong64.h @@ -181,6 +181,9 @@ class V8_EXPORT_PRIVATE MacroAssembler : public MacroAssemblerBase { void LoadRootRelative(Register destination, int32_t offset) final; void StoreRootRelative(int32_t offset, Register value) final; + void PreCheckSkippedWriteBarrier(Register object, Register value, + Register scratch, Label* ok); + // 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 @@ -360,6 +363,11 @@ class V8_EXPORT_PRIVATE MacroAssembler : public MacroAssemblerBase { SaveFPRegsMode fp_mode); void CallVerifySkippedWriteBarrierStub(Register object, Register value); + void CallVerifySkippedIndirectWriteBarrierStubSaveRegisters( + Register object, Register value, SaveFPRegsMode fp_mode); + void CallVerifySkippedIndirectWriteBarrierStub(Register object, + Register value); + // For a given |object| and |offset|: // - Move |object| to |dst_object|. // - Compute the address of the slot pointed to by |offset| in |object| and diff --git a/deps/v8/src/codegen/source-position-table.h b/deps/v8/src/codegen/source-position-table.h index 284654c3fe0cbd..dc72b8a264ff8f 100644 --- a/deps/v8/src/codegen/source-position-table.h +++ b/deps/v8/src/codegen/source-position-table.h @@ -36,6 +36,8 @@ struct PositionTableEntry { int code_offset; bool is_statement; bool is_breakable; + + bool operator==(const PositionTableEntry&) const = default; }; class V8_EXPORT_PRIVATE SourcePositionTableBuilder { @@ -95,6 +97,8 @@ class V8_EXPORT_PRIVATE SourcePositionTableIterator { PositionTableEntry position_; IterationFilter iteration_filter_; FunctionEntryFilter function_entry_filter_; + + bool operator==(const IndexAndPositionState&) const = default; }; // We expose three flavours of the iterator, depending on the argument passed diff --git a/deps/v8/src/compiler/backend/loong64/code-generator-loong64.cc b/deps/v8/src/compiler/backend/loong64/code-generator-loong64.cc index 855825bc8b61c6..0e14ed1aae7d19 100644 --- a/deps/v8/src/compiler/backend/loong64/code-generator-loong64.cc +++ b/deps/v8/src/compiler/backend/loong64/code-generator-loong64.cc @@ -156,7 +156,7 @@ class OutOfLineRecordWrite final : public OutOfLineCode { #if V8_ENABLE_WEBASSEMBLY stub_mode_(stub_mode), #endif // V8_ENABLE_WEBASSEMBLY - must_save_lr_(!gen->frame_access_state()->has_frame()), + must_save_ra_(!gen->frame_access_state()->has_frame()), zone_(gen->zone()), indirect_pointer_tag_(indirect_pointer_tag) { } @@ -175,7 +175,7 @@ class OutOfLineRecordWrite final : public OutOfLineCode { SaveFPRegsMode const save_fp_mode = frame()->DidAllocateDoubleRegisters() ? SaveFPRegsMode::kSave : SaveFPRegsMode::kIgnore; - if (must_save_lr_) { + if (must_save_ra_) { // We need to save and restore ra if the frame was elided. __ Push(ra); } @@ -196,7 +196,7 @@ class OutOfLineRecordWrite final : public OutOfLineCode { } else { __ CallRecordWriteStubSaveRegisters(object_, offset_, save_fp_mode); } - if (must_save_lr_) { + if (must_save_ra_) { __ Pop(ra); } } @@ -209,7 +209,7 @@ class OutOfLineRecordWrite final : public OutOfLineCode { #if V8_ENABLE_WEBASSEMBLY StubCallMode const stub_mode_; #endif // V8_ENABLE_WEBASSEMBLY - bool must_save_lr_; + bool must_save_ra_; Zone* zone_; IndirectPointerTag indirect_pointer_tag_; }; @@ -294,10 +294,12 @@ void RecordTrapInfoIfNeeded(Zone* zone, CodeGenerator* codegen, class OutOfLineVerifySkippedWriteBarrier final : public OutOfLineCode { public: OutOfLineVerifySkippedWriteBarrier(CodeGenerator* gen, Register object, - Register value) + Register value, Register scratch) : OutOfLineCode(gen), object_(object), value_(value), + scratch_(scratch), + must_save_ra_(!gen->frame_access_state()->has_frame()), zone_(gen->zone()) {} void Generate() final { @@ -305,12 +307,49 @@ class OutOfLineVerifySkippedWriteBarrier final : public OutOfLineCode { __ DecompressTagged(value_, value_); } + if (must_save_ra_) { + // We need to save and restore ra if the frame was elided. + __ Push(ra); + } + + __ PreCheckSkippedWriteBarrier(object_, value_, scratch_, exit()); + SaveFPRegsMode const save_fp_mode = frame()->DidAllocateDoubleRegisters() ? SaveFPRegsMode::kSave : SaveFPRegsMode::kIgnore; __ CallVerifySkippedWriteBarrierStubSaveRegisters(object_, value_, save_fp_mode); + + if (must_save_ra_) { + __ Pop(ra); + } + } + + private: + Register const object_; + Register const value_; + Register const scratch_; + const bool must_save_ra_; + Zone* zone_; +}; + +class OutOfLineVerifySkippedIndirectWriteBarrier final : public OutOfLineCode { + public: + OutOfLineVerifySkippedIndirectWriteBarrier(CodeGenerator* gen, + Register object, Register value) + : OutOfLineCode(gen), + object_(object), + value_(value), + zone_(gen->zone()) {} + + void Generate() final { + SaveFPRegsMode const save_fp_mode = frame()->DidAllocateDoubleRegisters() + ? SaveFPRegsMode::kSave + : SaveFPRegsMode::kIgnore; + + __ CallVerifySkippedIndirectWriteBarrierStubSaveRegisters(object_, value_, + save_fp_mode); } private: @@ -1025,12 +1064,12 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction( Operand(kClearedWeakHeapObjectLower32)); } - if (v8_flags.verify_write_barriers) { - auto ool = zone()->New(this, object, - value); - __ JumpIfNotSmi(value, ool->entry()); - __ bind(ool->exit()); - } + DCHECK(v8_flags.verify_write_barriers); + Register scratch = i.TempRegister(0); + auto ool = zone()->New( + this, object, value, scratch); + __ JumpIfNotSmi(value, ool->entry()); + __ bind(ool->exit()); MacroAssemblerBase::BlockTrampolinePoolScope block_trampoline_pool( masm()); @@ -1085,12 +1124,12 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction( Register temp = i.TempRegister(0); __ Add_d(temp, object, offset); - if (v8_flags.verify_write_barriers) { - auto ool = zone()->New(this, object, - value); - __ JumpIfNotSmi(value, ool->entry()); - __ bind(ool->exit()); - } + DCHECK(v8_flags.verify_write_barriers); + Register scratch = i.TempRegister(1); + auto ool = zone()->New( + this, object, value, scratch); + __ JumpIfNotSmi(value, ool->entry()); + __ bind(ool->exit()); MacroAssemblerBase::BlockTrampolinePoolScope block_trampoline_pool( masm()); @@ -1150,6 +1189,12 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction( DCHECK(IsValidIndirectPointerTag(tag)); #endif // DEBUG + DCHECK(v8_flags.verify_write_barriers); + auto ool = zone()->New( + this, object, value); + __ jmp(ool->entry()); + __ bind(ool->exit()); + MacroAssemblerBase::BlockTrampolinePoolScope block_trampoline_pool( masm()); Operand offset(0); diff --git a/deps/v8/src/compiler/backend/loong64/instruction-selector-loong64.cc b/deps/v8/src/compiler/backend/loong64/instruction-selector-loong64.cc index eb543ba3993d1d..61030234482d99 100644 --- a/deps/v8/src/compiler/backend/loong64/instruction-selector-loong64.cc +++ b/deps/v8/src/compiler/backend/loong64/instruction-selector-loong64.cc @@ -89,6 +89,7 @@ class Loong64OperandGenerator final : public OperandGenerator { bool CanBeImmediate(int64_t value, InstructionCode opcode) { switch (ArchOpcodeField::decode(opcode)) { case kArchAtomicStoreWithWriteBarrier: + case kArchAtomicStoreSkippedWriteBarrier: return false; case kLoong64Cmp32: case kLoong64Cmp64: @@ -619,7 +620,9 @@ void InstructionSelector::VisitStore(OpIndex node) { DCHECK(write_barrier_kind == kIndirectPointerWriteBarrier || write_barrier_kind == kSkippedWriteBarrier); // In this case we need to add the IndirectPointerTag as additional input. - code = kArchStoreIndirectWithWriteBarrier; + code = write_barrier_kind == kSkippedWriteBarrier + ? kArchStoreIndirectSkippedWriteBarrier + : kArchStoreIndirectWithWriteBarrier; code |= RecordWriteModeField::encode( RecordWriteMode::kValueIsIndirectPointer); IndirectPointerTag tag = store_view.indirect_pointer_tag(); @@ -636,7 +639,13 @@ void InstructionSelector::VisitStore(OpIndex node) { if (store_view.is_store_trap_on_null()) { code |= AccessModeField::encode(kMemoryAccessProtectedNullDereference); } - Emit(code, 0, nullptr, input_count, inputs); + + InstructionOperand temps[1]; + size_t temp_count = 0; + if (write_barrier_kind == kSkippedWriteBarrier) { + temps[temp_count++] = g.TempRegister(); + } + Emit(code, 0, nullptr, input_count, inputs, temp_count, temps); return; } @@ -1794,6 +1803,10 @@ void VisitAtomicStore(InstructionSelector* selector, OpIndex node, write_barrier_kind = kFullWriteBarrier; } + InstructionOperand inputs[] = {g.UseRegister(base), g.UseRegister(index), + g.UseRegisterOrImmediateZero(value)}; + InstructionOperand temps[2] = {}; + size_t temp_count = 0; InstructionCode code; if (write_barrier_kind != kNoWriteBarrier && @@ -1804,6 +1817,8 @@ void VisitAtomicStore(InstructionSelector* selector, OpIndex node, if (write_barrier_kind == kSkippedWriteBarrier) { code = kArchAtomicStoreSkippedWriteBarrier; code |= RecordWriteModeField::encode(RecordWriteMode::kValueIsAny); + temps[temp_count++] = g.TempRegister(); + temps[temp_count++] = g.TempRegister(); } else { RecordWriteMode record_write_mode = WriteBarrierKindToRecordWriteMode(write_barrier_kind); @@ -1849,15 +1864,14 @@ void VisitAtomicStore(InstructionSelector* selector, OpIndex node, } if (g.CanBeImmediate(index, code)) { + inputs[1] = g.UseImmediate(index); selector->Emit(code | AddressingModeField::encode(kMode_MRI) | AtomicWidthField::encode(width), - g.NoOutput(), g.UseRegister(base), g.UseImmediate(index), - g.UseRegisterOrImmediateZero(value)); + 0, nullptr, arraysize(inputs), inputs, temp_count, temps); } else { selector->Emit(code | AddressingModeField::encode(kMode_MRR) | AtomicWidthField::encode(width), - g.NoOutput(), g.UseRegister(base), g.UseRegister(index), - g.UseRegisterOrImmediateZero(value)); + 0, nullptr, arraysize(inputs), inputs, temp_count, temps); } } diff --git a/deps/v8/src/json/json-parser.cc b/deps/v8/src/json/json-parser.cc index a8dda4183e47db..03aa95bed73a44 100644 --- a/deps/v8/src/json/json-parser.cc +++ b/deps/v8/src/json/json-parser.cc @@ -130,6 +130,15 @@ static const constexpr uint8_t character_json_scan_flags[256] = { #undef CALL_GET_SCAN_FLAGS }; +#define EXPECT_RETURN_ON_ERROR(token, msg, ret) \ + if (V8_UNLIKELY(!Expect(token, msg))) { \ + return ret; \ + } +#define EXPECT_NEXT_RETURN_ON_ERROR(token, msg, ret) \ + if (V8_UNLIKELY(!ExpectNext(token, msg))) { \ + return ret; \ + } + } // namespace MaybeHandle JsonParseInternalizer::Internalize( @@ -1506,8 +1515,9 @@ bool JsonParser::FastKeyMatch(const uint8_t* key_chars, template bool JsonParser::ParseJsonPropertyValue(const JsonString& key) { - ExpectNext(JsonToken::COLON, - MessageTemplate::kJsonParseExpectedColonAfterPropertyName); + EXPECT_NEXT_RETURN_ON_ERROR( + JsonToken::COLON, + MessageTemplate::kJsonParseExpectedColonAfterPropertyName, false); Handle value; if (V8_UNLIKELY(!ParseJsonValueRecursive().ToHandle(&value))) return false; property_stack_.emplace_back(key, value); @@ -1553,7 +1563,7 @@ bool JsonParser::ParseJsonObjectProperties( using FastIterableState = DescriptorArray::FastIterableState; if constexpr (fast_iterable_state == FastIterableState::kJsonSlow) { do { - ExpectNext(JsonToken::STRING, first_token_msg); + EXPECT_NEXT_RETURN_ON_ERROR(JsonToken::STRING, first_token_msg, false); first_token_msg = MessageTemplate::kJsonParseExpectedDoubleQuotedPropertyName; JsonString key = ScanJsonPropertyKey(cont); @@ -1563,7 +1573,7 @@ bool JsonParser::ParseJsonObjectProperties( DCHECK_GT(descriptors->number_of_descriptors(), 0); InternalIndex idx{0}; do { - ExpectNext(JsonToken::STRING, first_token_msg); + EXPECT_NEXT_RETURN_ON_ERROR(JsonToken::STRING, first_token_msg, false); first_token_msg = MessageTemplate::kJsonParseExpectedDoubleQuotedPropertyName; bool key_match; @@ -1731,7 +1741,8 @@ MaybeHandle JsonParser::ParseJsonObject(Handle feedback) { return {}; } - Expect(JsonToken::RBRACE, MessageTemplate::kJsonParseExpectedCommaOrRBrace); + EXPECT_RETURN_ON_ERROR(JsonToken::RBRACE, + MessageTemplate::kJsonParseExpectedCommaOrRBrace, {}); Handle result = BuildJsonObject(cont, feedback); property_stack_.resize(cont.index); return cont.scope.CloseAndEscape(result); @@ -1777,8 +1788,9 @@ MaybeHandle JsonParser::ParseJsonArray() { SkipWhitespace(); continue; } else { - Expect(JsonToken::RBRACK, - MessageTemplate::kJsonParseExpectedCommaOrRBrack); + EXPECT_RETURN_ON_ERROR(JsonToken::RBRACK, + MessageTemplate::kJsonParseExpectedCommaOrRBrack, + {}); success = true; break; } @@ -1846,7 +1858,8 @@ MaybeHandle JsonParser::ParseJsonArray() { element_stack_.emplace_back(value); } - Expect(JsonToken::RBRACK, MessageTemplate::kJsonParseExpectedCommaOrRBrack); + EXPECT_RETURN_ON_ERROR(JsonToken::RBRACK, + MessageTemplate::kJsonParseExpectedCommaOrRBrack, {}); Handle result = BuildJsonArray(start); element_stack_.resize(start); return handle_scope.CloseAndEscape(result); @@ -1956,15 +1969,17 @@ MaybeHandle JsonParser::ParseJsonValue() { property_stack_.size()); // Parse the property key. - ExpectNext(JsonToken::STRING, - MessageTemplate::kJsonParseExpectedPropNameOrRBrace); + EXPECT_NEXT_RETURN_ON_ERROR( + JsonToken::STRING, + MessageTemplate::kJsonParseExpectedPropNameOrRBrace, {}); property_stack_.emplace_back(ScanJsonPropertyKey(&cont)); if constexpr (should_track_json_source) { property_val_node_stack.emplace_back(Handle()); } - ExpectNext(JsonToken::COLON, - MessageTemplate::kJsonParseExpectedColonAfterPropertyName); + EXPECT_NEXT_RETURN_ON_ERROR( + JsonToken::COLON, + MessageTemplate::kJsonParseExpectedColonAfterPropertyName, {}); // Continue to start producing the first property value. continue; @@ -2060,17 +2075,18 @@ MaybeHandle JsonParser::ParseJsonValue() { if (V8_LIKELY(Check(JsonToken::COMMA))) { // Parse the property key. - ExpectNext( + EXPECT_NEXT_RETURN_ON_ERROR( JsonToken::STRING, - MessageTemplate::kJsonParseExpectedDoubleQuotedPropertyName); + MessageTemplate::kJsonParseExpectedDoubleQuotedPropertyName, + {}); property_stack_.emplace_back(ScanJsonPropertyKey(&cont)); if constexpr (should_track_json_source) { property_val_node_stack.emplace_back(Handle()); } - ExpectNext( + EXPECT_NEXT_RETURN_ON_ERROR( JsonToken::COLON, - MessageTemplate::kJsonParseExpectedColonAfterPropertyName); + MessageTemplate::kJsonParseExpectedColonAfterPropertyName, {}); // Break to start producing the subsequent property value. break; @@ -2090,8 +2106,9 @@ MaybeHandle JsonParser::ParseJsonValue() { } } value = BuildJsonObject(cont, feedback); - Expect(JsonToken::RBRACE, - MessageTemplate::kJsonParseExpectedCommaOrRBrace); + EXPECT_RETURN_ON_ERROR( + JsonToken::RBRACE, + MessageTemplate::kJsonParseExpectedCommaOrRBrace, {}); // Return the object. if constexpr (should_track_json_source) { size_t start = cont.index; @@ -2141,8 +2158,9 @@ MaybeHandle JsonParser::ParseJsonValue() { if (V8_LIKELY(Check(JsonToken::COMMA))) break; value = BuildJsonArray(cont.index); - Expect(JsonToken::RBRACK, - MessageTemplate::kJsonParseExpectedCommaOrRBrack); + EXPECT_RETURN_ON_ERROR( + JsonToken::RBRACK, + MessageTemplate::kJsonParseExpectedCommaOrRBrack, {}); // Return the array. if constexpr (should_track_json_source) { size_t start = cont.index; diff --git a/deps/v8/src/json/json-parser.h b/deps/v8/src/json/json-parser.h index c9f98688acfed3..bb9fe714d85923 100644 --- a/deps/v8/src/json/json-parser.h +++ b/deps/v8/src/json/json-parser.h @@ -242,23 +242,26 @@ class JsonParser final { advance(); } - void Expect(JsonToken token, - std::optional errorMessage = std::nullopt) { + V8_WARN_UNUSED_RESULT bool Expect( + JsonToken token, + std::optional errorMessage = std::nullopt) { if (V8_LIKELY(peek() == token)) { advance(); - } else { - errorMessage ? ReportUnexpectedToken(peek(), errorMessage.value()) - : ReportUnexpectedToken(peek()); + return true; } + errorMessage ? ReportUnexpectedToken(peek(), errorMessage.value()) + : ReportUnexpectedToken(peek()); + return false; } - void ExpectNext(JsonToken token, - std::optional errorMessage = std::nullopt) { + V8_WARN_UNUSED_RESULT bool ExpectNext( + JsonToken token, + std::optional errorMessage = std::nullopt) { SkipWhitespace(); - errorMessage ? Expect(token, errorMessage.value()) : Expect(token); + return errorMessage ? Expect(token, errorMessage.value()) : Expect(token); } - bool Check(JsonToken token) { + V8_WARN_UNUSED_RESULT bool Check(JsonToken token) { SkipWhitespace(); if (next_ != token) return false; advance(); diff --git a/deps/v8/src/maglev/maglev-graph-builder.cc b/deps/v8/src/maglev/maglev-graph-builder.cc index 182ba58903f71e..016506393b9171 100644 --- a/deps/v8/src/maglev/maglev-graph-builder.cc +++ b/deps/v8/src/maglev/maglev-graph-builder.cc @@ -6924,15 +6924,18 @@ MaglevGraphBuilder::FindContinuationForPolymorphicPropertyLoad() { } int start_offset = iterator_.current_offset(); +#ifdef DEBUG SourcePositionTableIterator::IndexAndPositionState start_source_position_iterator_state = source_position_iterator_.GetState(); +#endif std::optional continuation = FindContinuationForPolymorphicPropertyLoadImpl(); iterator_.SetOffset(start_offset); - source_position_iterator_.RestoreState(start_source_position_iterator_state); + DCHECK_EQ(start_source_position_iterator_state, + source_position_iterator_.GetState()); return continuation; } @@ -6949,11 +6952,64 @@ MaglevGraphBuilder::FindContinuationForPolymorphicPropertyLoadImpl() { // Where are: // - not affecting control flow // - not storing into REG + // - not the start or end of a try block // and the continuation is limited in length. - // Skip GetnamedProperty. + // Try-block starts are not visible as control flow or basic blocks, so detect + // them using the bytecode offset. + int next_handler_change = kMaxInt; + HandlerTable table(*bytecode().object()); + if (next_handler_table_index_ < table.NumberOfRangeEntries()) { + next_handler_change = table.GetRangeStart(next_handler_table_index_); + } + // Try-block ends are detected via the top end offset in the current handler + // stack. + if (IsInsideTryBlock()) { + const HandlerTableEntry& entry = catch_block_stack_.top(); + next_handler_change = std::min(next_handler_change, entry.end); + } + + auto IsOffsetAPolymorphicContinuationInterrupt = + [this, next_handler_change](int offset) { + // We can't continue a polymorphic load over a merge, since the + // other side of the merge will observe the call without the load. + // + // TODO(leszeks): I guess we could split that merge if we wanted to, + // introducing a new merge that has the polymorphic loads+calls on one + // side and the generic call on the other. + if (IsOffsetAMergePoint(offset)) return true; + + // We currently can't continue a polymorphic load across a peeled + // loop header -- not because of any actual semantic reason, a peeled + // loop should be just like straightline code, but just because this + // iteration isn't compatible with the PeelLoop iteration. + // + // TODO(leszeks): We could probably make loop peeling work happen on the + // JumpLoop rather than loop header, and then this continuation code + // would work. Only for the first peeled iteration though, not for + // speeling. + if (loop_headers_to_peel_.Contains(offset)) return true; + + // Loop peeling should be the only reason there was no merge point for a + // loop header. + DCHECK(!bytecode_analysis_.IsLoopHeader(offset)); + + // We can't currently continue a polymorphic load over a try-catch + // start/end -- again, not for any semantic reason, but just because + // this iteration doesn't consider the catch handler stack. + // + // TODO(leszeks): If this saved/restore the handler stack, it would + // probably work, but we'd need to confirm that later phases don't need + // strict nesting of handlers (since the first polymorphic call would + // be inside the handler range, but the second polymorphic load after it + // in linear scan order would be outside of the handler range). + if (offset >= next_handler_change) return true; + return false; + }; + + // Skip GetNamedProperty. iterator_.Advance(); - if (IsOffsetAMergePointOrLoopHeapder(iterator_.current_offset())) { + if (IsOffsetAPolymorphicContinuationInterrupt(iterator_.current_offset())) { return {}; } @@ -6975,7 +7031,7 @@ MaglevGraphBuilder::FindContinuationForPolymorphicPropertyLoadImpl() { int limit = 20; while (--limit > 0) { iterator_.Advance(); - if (IsOffsetAMergePointOrLoopHeapder(iterator_.current_offset())) { + if (IsOffsetAPolymorphicContinuationInterrupt(iterator_.current_offset())) { return {}; } diff --git a/deps/v8/src/maglev/maglev-graph-builder.h b/deps/v8/src/maglev/maglev-graph-builder.h index 26888249559845..6c604235e3c7ac 100644 --- a/deps/v8/src/maglev/maglev-graph-builder.h +++ b/deps/v8/src/maglev/maglev-graph-builder.h @@ -441,15 +441,10 @@ class MaglevGraphBuilder { // Return true if the given offset is a merge point, i.e. there are jumps // targetting it. - bool IsOffsetAMergePoint(int offset) { + bool IsOffsetAMergePoint(int offset) const { return merge_states_[offset] != nullptr; } - bool IsOffsetAMergePointOrLoopHeapder(int offset) { - return IsOffsetAMergePoint(offset) || - bytecode_analysis().IsLoopHeader(offset); - } - ValueNode* GetContextAtDepth(ValueNode* context, size_t depth); bool CheckContextExtensions(size_t depth); diff --git a/deps/v8/src/regexp/regexp-bytecode-generator.cc b/deps/v8/src/regexp/regexp-bytecode-generator.cc index 319d4d1a196abc..4328106d36c1d9 100644 --- a/deps/v8/src/regexp/regexp-bytecode-generator.cc +++ b/deps/v8/src/regexp/regexp-bytecode-generator.cc @@ -204,8 +204,7 @@ void RegExpBytecodeGenerator::LoadCurrentCharacterImpl(int cp_offset, check_bounds = false; // Load below doesn't need to check. } - DCHECK_LE(kMinCPOffset, cp_offset); - DCHECK_GE(kMaxCPOffset, cp_offset); + CHECK(base::IsInRange(cp_offset, kMinCPOffset, kMaxCPOffset)); int bytecode; if (check_bounds) { if (characters == 4) { diff --git a/deps/v8/src/regexp/regexp-compiler.cc b/deps/v8/src/regexp/regexp-compiler.cc index a110f21b8ce720..8d52d19e12aa53 100644 --- a/deps/v8/src/regexp/regexp-compiler.cc +++ b/deps/v8/src/regexp/regexp-compiler.cc @@ -2313,6 +2313,7 @@ void AssertionNode::BacktrackIfPrevious( // If we've already checked that we are not at the start of input, it's okay // to load the previous character without bounds checks. const bool can_skip_bounds_check = !may_be_at_or_before_subject_string_start; + static_assert(Trace::kCPOffsetSlack == 1); assembler->LoadCurrentCharacter(new_trace.cp_offset() - 1, non_word, can_skip_bounds_check); EmitWordCheck(assembler, word, non_word, backtrack_if_previous == kIsNonWord); @@ -2567,6 +2568,7 @@ void TextNode::Emit(RegExpCompiler* compiler, Trace* trace) { } bool first_elt_done = false; + static_assert(Trace::kCPOffsetSlack == 1); int bound_checked_to = trace->cp_offset() - 1; bound_checked_to += trace->bound_checked_up_to(); @@ -2611,7 +2613,10 @@ void Trace::AdvanceCurrentPositionInTrace(int by, RegExpCompiler* compiler) { // characters by means of mask and compare. quick_check_performed_.Advance(by, compiler->one_byte()); cp_offset_ += by; - if (cp_offset_ > RegExpMacroAssembler::kMaxCPOffset) { + static_assert(RegExpMacroAssembler::kMaxCPOffset == + -RegExpMacroAssembler::kMinCPOffset); + if (std::abs(cp_offset_) + kCPOffsetSlack > + RegExpMacroAssembler::kMaxCPOffset) { compiler->SetRegExpTooBig(); cp_offset_ = 0; } diff --git a/deps/v8/src/regexp/regexp-compiler.h b/deps/v8/src/regexp/regexp-compiler.h index 1905031ebbaca8..102525c00b4670 100644 --- a/deps/v8/src/regexp/regexp-compiler.h +++ b/deps/v8/src/regexp/regexp-compiler.h @@ -278,7 +278,17 @@ class Trace { }; void Flush(RegExpCompiler* compiler, RegExpNode* successor, FlushMode mode = kFlushFull); + + // Some callers add/subtract 1 from cp_offset, assuming that the result is + // still valid. That's obviously not the case when our `cp_offset` is only + // checked against kMinCPOffset/kMaxCPOffset, so we need to apply the some + // slack. + // TODO(jgruber): It would be better if all callers checked against limits + // themselves when doing so; but unfortunately not all callers have + // abort-compilation mechanisms. + static constexpr int kCPOffsetSlack = 1; int cp_offset() const { return cp_offset_; } + // Does any trace in the chain have an action? bool has_any_actions() const { return has_any_actions_; } // Does this particular trace object have an action? diff --git a/deps/v8/src/regexp/regexp-macro-assembler.cc b/deps/v8/src/regexp/regexp-macro-assembler.cc index 69f3051ad99dc6..ae00ecf77604f2 100644 --- a/deps/v8/src/regexp/regexp-macro-assembler.cc +++ b/deps/v8/src/regexp/regexp-macro-assembler.cc @@ -261,7 +261,7 @@ void NativeRegExpMacroAssembler::LoadCurrentCharacterImpl( // path requires a large number of characters, but not the reverse. DCHECK_GE(eats_at_least, characters); - DCHECK(base::IsInRange(cp_offset, kMinCPOffset, kMaxCPOffset)); + CHECK(base::IsInRange(cp_offset, kMinCPOffset, kMaxCPOffset)); if (check_bounds) { if (cp_offset >= 0) { CheckPosition(cp_offset + eats_at_least - 1, on_end_of_input); diff --git a/deps/v8/test/mjsunit/regress/regress-449549329.js b/deps/v8/test/mjsunit/regress/regress-449549329.js new file mode 100644 index 00000000000000..2e2e1e6cb376d3 --- /dev/null +++ b/deps/v8/test/mjsunit/regress/regress-449549329.js @@ -0,0 +1,49 @@ +// Copyright 2025 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. +// +// Flags: --allow-natives-syntax + +// Create two distinct iterator types which both have a getter for `next`. +class Iterator1 { + get next() { + return () => ({ done: true }); + } +} +class Iterator2 { + get next() { + return () => ({ done: true }); + } +} + +// Create two iterables which return instances of these two distinct iterators. +const iterable1 = { + [Symbol.iterator]() { + return new Iterator1(); + }, +}; +const iterable2 = { + [Symbol.iterator]() { + return new Iterator2(); + }, +}; + +// Iterate the iterable using for-of. +function foo(iterable) { + for (const x of iterable) { + return x; + } +} + +// Make foo polymorphic in the iterator, specifically so that the feedback for +// the iterator.next named load is polymorphic, with the feedback being two +// distinct getters. +%PrepareFunctionForOptimization(foo); +foo(iterable1); +foo(iterable2); + +// The optimization should be successful and not trigger any DCHECKs, despite +// the iterator.next load being before the for-of's implicit try block, and the +// iterator.next() call being inside it. +%OptimizeMaglevOnNextCall(foo); +foo(iterable1); diff --git a/deps/v8/test/mjsunit/regress/regress-451663011.js b/deps/v8/test/mjsunit/regress/regress-451663011.js new file mode 100644 index 00000000000000..64ad81154e8ab9 --- /dev/null +++ b/deps/v8/test/mjsunit/regress/regress-451663011.js @@ -0,0 +1,9 @@ +// Copyright 2025 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. + +const length = 32767; +const pattern_body = "^" + "a".repeat(length); +const pattern = new RegExp("(?<=" + pattern_body + ")", "m"); +const input = "a".repeat(length) + "b" + '\n'; +assertThrows(() => pattern.exec(input));