| // Copyright 2015 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/interpreter/bytecode-array-writer.h" | |
| #include "src/api-inl.h" | |
| #include "src/interpreter/bytecode-jump-table.h" | |
| #include "src/interpreter/bytecode-label.h" | |
| #include "src/interpreter/bytecode-node.h" | |
| #include "src/interpreter/bytecode-register.h" | |
| #include "src/interpreter/bytecode-source-info.h" | |
| #include "src/interpreter/constant-array-builder.h" | |
| #include "src/log.h" | |
| #include "src/objects-inl.h" | |
| namespace v8 { | |
| namespace internal { | |
| namespace interpreter { | |
| STATIC_CONST_MEMBER_DEFINITION const size_t | |
| BytecodeArrayWriter::kMaxSizeOfPackedBytecode; | |
| BytecodeArrayWriter::BytecodeArrayWriter( | |
| Zone* zone, ConstantArrayBuilder* constant_array_builder, | |
| SourcePositionTableBuilder::RecordingMode source_position_mode) | |
| : bytecodes_(zone), | |
| unbound_jumps_(0), | |
| source_position_table_builder_(source_position_mode), | |
| constant_array_builder_(constant_array_builder), | |
| last_bytecode_(Bytecode::kIllegal), | |
| last_bytecode_offset_(0), | |
| last_bytecode_had_source_info_(false), | |
| elide_noneffectful_bytecodes_(FLAG_ignition_elide_noneffectful_bytecodes), | |
| exit_seen_in_block_(false) { | |
| bytecodes_.reserve(512); // Derived via experimentation. | |
| } | |
| Handle<BytecodeArray> BytecodeArrayWriter::ToBytecodeArray( | |
| Isolate* isolate, int register_count, int parameter_count, | |
| Handle<ByteArray> handler_table) { | |
| DCHECK_EQ(0, unbound_jumps_); | |
| int bytecode_size = static_cast<int>(bytecodes()->size()); | |
| int frame_size = register_count * kPointerSize; | |
| Handle<FixedArray> constant_pool = | |
| constant_array_builder()->ToFixedArray(isolate); | |
| Handle<ByteArray> source_position_table = | |
| source_position_table_builder()->ToSourcePositionTable(isolate); | |
| Handle<BytecodeArray> bytecode_array = isolate->factory()->NewBytecodeArray( | |
| bytecode_size, &bytecodes()->front(), frame_size, parameter_count, | |
| constant_pool); | |
| bytecode_array->set_handler_table(*handler_table); | |
| bytecode_array->set_source_position_table(*source_position_table); | |
| LOG_CODE_EVENT(isolate, CodeLinePosInfoRecordEvent( | |
| bytecode_array->GetFirstBytecodeAddress(), | |
| *source_position_table)); | |
| return bytecode_array; | |
| } | |
| void BytecodeArrayWriter::Write(BytecodeNode* node) { | |
| DCHECK(!Bytecodes::IsJump(node->bytecode())); | |
| if (exit_seen_in_block_) return; // Don't emit dead code. | |
| UpdateExitSeenInBlock(node->bytecode()); | |
| MaybeElideLastBytecode(node->bytecode(), node->source_info().is_valid()); | |
| UpdateSourcePositionTable(node); | |
| EmitBytecode(node); | |
| } | |
| void BytecodeArrayWriter::WriteJump(BytecodeNode* node, BytecodeLabel* label) { | |
| DCHECK(Bytecodes::IsJump(node->bytecode())); | |
| // TODO(rmcilroy): For forward jumps we could also mark the label as dead, | |
| // thereby avoiding emitting dead code when we bind the label. | |
| if (exit_seen_in_block_) return; // Don't emit dead code. | |
| UpdateExitSeenInBlock(node->bytecode()); | |
| MaybeElideLastBytecode(node->bytecode(), node->source_info().is_valid()); | |
| UpdateSourcePositionTable(node); | |
| EmitJump(node, label); | |
| } | |
| void BytecodeArrayWriter::WriteSwitch(BytecodeNode* node, | |
| BytecodeJumpTable* jump_table) { | |
| DCHECK(Bytecodes::IsSwitch(node->bytecode())); | |
| // TODO(rmcilroy): For jump tables we could also mark the table as dead, | |
| // thereby avoiding emitting dead code when we bind the entries. | |
| if (exit_seen_in_block_) return; // Don't emit dead code. | |
| UpdateExitSeenInBlock(node->bytecode()); | |
| MaybeElideLastBytecode(node->bytecode(), node->source_info().is_valid()); | |
| UpdateSourcePositionTable(node); | |
| EmitSwitch(node, jump_table); | |
| } | |
| void BytecodeArrayWriter::BindLabel(BytecodeLabel* label) { | |
| size_t current_offset = bytecodes()->size(); | |
| if (label->is_forward_target()) { | |
| // An earlier jump instruction refers to this label. Update it's location. | |
| PatchJump(current_offset, label->offset()); | |
| // Now treat as if the label will only be back referred to. | |
| } | |
| label->bind_to(current_offset); | |
| InvalidateLastBytecode(); | |
| exit_seen_in_block_ = false; // Starting a new basic block. | |
| } | |
| void BytecodeArrayWriter::BindLabel(const BytecodeLabel& target, | |
| BytecodeLabel* label) { | |
| DCHECK(!label->is_bound()); | |
| DCHECK(target.is_bound()); | |
| if (label->is_forward_target()) { | |
| // An earlier jump instruction refers to this label. Update it's location. | |
| PatchJump(target.offset(), label->offset()); | |
| // Now treat as if the label will only be back referred to. | |
| } | |
| label->bind_to(target.offset()); | |
| InvalidateLastBytecode(); | |
| // exit_seen_in_block_ was reset when target was bound, so shouldn't be | |
| // changed here. | |
| } | |
| void BytecodeArrayWriter::BindJumpTableEntry(BytecodeJumpTable* jump_table, | |
| int case_value) { | |
| DCHECK(!jump_table->is_bound(case_value)); | |
| size_t current_offset = bytecodes()->size(); | |
| size_t relative_jump = current_offset - jump_table->switch_bytecode_offset(); | |
| constant_array_builder()->SetJumpTableSmi( | |
| jump_table->ConstantPoolEntryFor(case_value), | |
| Smi::FromInt(static_cast<int>(relative_jump))); | |
| jump_table->mark_bound(case_value); | |
| InvalidateLastBytecode(); | |
| exit_seen_in_block_ = false; // Starting a new basic block. | |
| } | |
| void BytecodeArrayWriter::UpdateSourcePositionTable( | |
| const BytecodeNode* const node) { | |
| int bytecode_offset = static_cast<int>(bytecodes()->size()); | |
| const BytecodeSourceInfo& source_info = node->source_info(); | |
| if (source_info.is_valid()) { | |
| source_position_table_builder()->AddPosition( | |
| bytecode_offset, SourcePosition(source_info.source_position()), | |
| source_info.is_statement()); | |
| } | |
| } | |
| void BytecodeArrayWriter::UpdateExitSeenInBlock(Bytecode bytecode) { | |
| switch (bytecode) { | |
| case Bytecode::kReturn: | |
| case Bytecode::kThrow: | |
| case Bytecode::kReThrow: | |
| case Bytecode::kAbort: | |
| case Bytecode::kJump: | |
| case Bytecode::kJumpConstant: | |
| case Bytecode::kSuspendGenerator: | |
| exit_seen_in_block_ = true; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| void BytecodeArrayWriter::MaybeElideLastBytecode(Bytecode next_bytecode, | |
| bool has_source_info) { | |
| if (!elide_noneffectful_bytecodes_) return; | |
| // If the last bytecode loaded the accumulator without any external effect, | |
| // and the next bytecode clobbers this load without reading the accumulator, | |
| // then the previous bytecode can be elided as it has no effect. | |
| if (Bytecodes::IsAccumulatorLoadWithoutEffects(last_bytecode_) && | |
| Bytecodes::GetAccumulatorUse(next_bytecode) == AccumulatorUse::kWrite && | |
| (!last_bytecode_had_source_info_ || !has_source_info)) { | |
| DCHECK_GT(bytecodes()->size(), last_bytecode_offset_); | |
| bytecodes()->resize(last_bytecode_offset_); | |
| // If the last bytecode had source info we will transfer the source info | |
| // to this bytecode. | |
| has_source_info |= last_bytecode_had_source_info_; | |
| } | |
| last_bytecode_ = next_bytecode; | |
| last_bytecode_had_source_info_ = has_source_info; | |
| last_bytecode_offset_ = bytecodes()->size(); | |
| } | |
| void BytecodeArrayWriter::InvalidateLastBytecode() { | |
| last_bytecode_ = Bytecode::kIllegal; | |
| } | |
| void BytecodeArrayWriter::EmitBytecode(const BytecodeNode* const node) { | |
| DCHECK_NE(node->bytecode(), Bytecode::kIllegal); | |
| Bytecode bytecode = node->bytecode(); | |
| OperandScale operand_scale = node->operand_scale(); | |
| if (operand_scale != OperandScale::kSingle) { | |
| Bytecode prefix = Bytecodes::OperandScaleToPrefixBytecode(operand_scale); | |
| bytecodes()->push_back(Bytecodes::ToByte(prefix)); | |
| } | |
| bytecodes()->push_back(Bytecodes::ToByte(bytecode)); | |
| const uint32_t* const operands = node->operands(); | |
| const int operand_count = node->operand_count(); | |
| const OperandSize* operand_sizes = | |
| Bytecodes::GetOperandSizes(bytecode, operand_scale); | |
| for (int i = 0; i < operand_count; ++i) { | |
| switch (operand_sizes[i]) { | |
| case OperandSize::kNone: | |
| UNREACHABLE(); | |
| break; | |
| case OperandSize::kByte: | |
| bytecodes()->push_back(static_cast<uint8_t>(operands[i])); | |
| break; | |
| case OperandSize::kShort: { | |
| uint16_t operand = static_cast<uint16_t>(operands[i]); | |
| const uint8_t* raw_operand = reinterpret_cast<const uint8_t*>(&operand); | |
| bytecodes()->push_back(raw_operand[0]); | |
| bytecodes()->push_back(raw_operand[1]); | |
| break; | |
| } | |
| case OperandSize::kQuad: { | |
| const uint8_t* raw_operand = | |
| reinterpret_cast<const uint8_t*>(&operands[i]); | |
| bytecodes()->push_back(raw_operand[0]); | |
| bytecodes()->push_back(raw_operand[1]); | |
| bytecodes()->push_back(raw_operand[2]); | |
| bytecodes()->push_back(raw_operand[3]); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| // static | |
| Bytecode GetJumpWithConstantOperand(Bytecode jump_bytecode) { | |
| switch (jump_bytecode) { | |
| case Bytecode::kJump: | |
| return Bytecode::kJumpConstant; | |
| case Bytecode::kJumpIfTrue: | |
| return Bytecode::kJumpIfTrueConstant; | |
| case Bytecode::kJumpIfFalse: | |
| return Bytecode::kJumpIfFalseConstant; | |
| case Bytecode::kJumpIfToBooleanTrue: | |
| return Bytecode::kJumpIfToBooleanTrueConstant; | |
| case Bytecode::kJumpIfToBooleanFalse: | |
| return Bytecode::kJumpIfToBooleanFalseConstant; | |
| case Bytecode::kJumpIfNull: | |
| return Bytecode::kJumpIfNullConstant; | |
| case Bytecode::kJumpIfNotNull: | |
| return Bytecode::kJumpIfNotNullConstant; | |
| case Bytecode::kJumpIfUndefined: | |
| return Bytecode::kJumpIfUndefinedConstant; | |
| case Bytecode::kJumpIfNotUndefined: | |
| return Bytecode::kJumpIfNotUndefinedConstant; | |
| case Bytecode::kJumpIfJSReceiver: | |
| return Bytecode::kJumpIfJSReceiverConstant; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| void BytecodeArrayWriter::PatchJumpWith8BitOperand(size_t jump_location, | |
| int delta) { | |
| Bytecode jump_bytecode = Bytecodes::FromByte(bytecodes()->at(jump_location)); | |
| DCHECK(Bytecodes::IsForwardJump(jump_bytecode)); | |
| DCHECK(Bytecodes::IsJumpImmediate(jump_bytecode)); | |
| DCHECK_EQ(Bytecodes::GetOperandType(jump_bytecode, 0), OperandType::kUImm); | |
| DCHECK_GT(delta, 0); | |
| size_t operand_location = jump_location + 1; | |
| DCHECK_EQ(bytecodes()->at(operand_location), k8BitJumpPlaceholder); | |
| if (Bytecodes::ScaleForUnsignedOperand(delta) == OperandScale::kSingle) { | |
| // The jump fits within the range of an UImm8 operand, so cancel | |
| // the reservation and jump directly. | |
| constant_array_builder()->DiscardReservedEntry(OperandSize::kByte); | |
| bytecodes()->at(operand_location) = static_cast<uint8_t>(delta); | |
| } else { | |
| // The jump does not fit within the range of an UImm8 operand, so | |
| // commit reservation putting the offset into the constant pool, | |
| // and update the jump instruction and operand. | |
| size_t entry = constant_array_builder()->CommitReservedEntry( | |
| OperandSize::kByte, Smi::FromInt(delta)); | |
| DCHECK_EQ(Bytecodes::SizeForUnsignedOperand(static_cast<uint32_t>(entry)), | |
| OperandSize::kByte); | |
| jump_bytecode = GetJumpWithConstantOperand(jump_bytecode); | |
| bytecodes()->at(jump_location) = Bytecodes::ToByte(jump_bytecode); | |
| bytecodes()->at(operand_location) = static_cast<uint8_t>(entry); | |
| } | |
| } | |
| void BytecodeArrayWriter::PatchJumpWith16BitOperand(size_t jump_location, | |
| int delta) { | |
| Bytecode jump_bytecode = Bytecodes::FromByte(bytecodes()->at(jump_location)); | |
| DCHECK(Bytecodes::IsForwardJump(jump_bytecode)); | |
| DCHECK(Bytecodes::IsJumpImmediate(jump_bytecode)); | |
| DCHECK_EQ(Bytecodes::GetOperandType(jump_bytecode, 0), OperandType::kUImm); | |
| DCHECK_GT(delta, 0); | |
| size_t operand_location = jump_location + 1; | |
| uint8_t operand_bytes[2]; | |
| if (Bytecodes::ScaleForUnsignedOperand(delta) <= OperandScale::kDouble) { | |
| // The jump fits within the range of an Imm16 operand, so cancel | |
| // the reservation and jump directly. | |
| constant_array_builder()->DiscardReservedEntry(OperandSize::kShort); | |
| WriteUnalignedUInt16(reinterpret_cast<Address>(operand_bytes), | |
| static_cast<uint16_t>(delta)); | |
| } else { | |
| // The jump does not fit within the range of an Imm16 operand, so | |
| // commit reservation putting the offset into the constant pool, | |
| // and update the jump instruction and operand. | |
| size_t entry = constant_array_builder()->CommitReservedEntry( | |
| OperandSize::kShort, Smi::FromInt(delta)); | |
| jump_bytecode = GetJumpWithConstantOperand(jump_bytecode); | |
| bytecodes()->at(jump_location) = Bytecodes::ToByte(jump_bytecode); | |
| WriteUnalignedUInt16(reinterpret_cast<Address>(operand_bytes), | |
| static_cast<uint16_t>(entry)); | |
| } | |
| DCHECK(bytecodes()->at(operand_location) == k8BitJumpPlaceholder && | |
| bytecodes()->at(operand_location + 1) == k8BitJumpPlaceholder); | |
| bytecodes()->at(operand_location++) = operand_bytes[0]; | |
| bytecodes()->at(operand_location) = operand_bytes[1]; | |
| } | |
| void BytecodeArrayWriter::PatchJumpWith32BitOperand(size_t jump_location, | |
| int delta) { | |
| DCHECK(Bytecodes::IsJumpImmediate( | |
| Bytecodes::FromByte(bytecodes()->at(jump_location)))); | |
| constant_array_builder()->DiscardReservedEntry(OperandSize::kQuad); | |
| uint8_t operand_bytes[4]; | |
| WriteUnalignedUInt32(reinterpret_cast<Address>(operand_bytes), | |
| static_cast<uint32_t>(delta)); | |
| size_t operand_location = jump_location + 1; | |
| DCHECK(bytecodes()->at(operand_location) == k8BitJumpPlaceholder && | |
| bytecodes()->at(operand_location + 1) == k8BitJumpPlaceholder && | |
| bytecodes()->at(operand_location + 2) == k8BitJumpPlaceholder && | |
| bytecodes()->at(operand_location + 3) == k8BitJumpPlaceholder); | |
| bytecodes()->at(operand_location++) = operand_bytes[0]; | |
| bytecodes()->at(operand_location++) = operand_bytes[1]; | |
| bytecodes()->at(operand_location++) = operand_bytes[2]; | |
| bytecodes()->at(operand_location) = operand_bytes[3]; | |
| } | |
| void BytecodeArrayWriter::PatchJump(size_t jump_target, size_t jump_location) { | |
| Bytecode jump_bytecode = Bytecodes::FromByte(bytecodes()->at(jump_location)); | |
| int delta = static_cast<int>(jump_target - jump_location); | |
| int prefix_offset = 0; | |
| OperandScale operand_scale = OperandScale::kSingle; | |
| if (Bytecodes::IsPrefixScalingBytecode(jump_bytecode)) { | |
| // If a prefix scaling bytecode is emitted the target offset is one | |
| // less than the case of no prefix scaling bytecode. | |
| delta -= 1; | |
| prefix_offset = 1; | |
| operand_scale = Bytecodes::PrefixBytecodeToOperandScale(jump_bytecode); | |
| jump_bytecode = | |
| Bytecodes::FromByte(bytecodes()->at(jump_location + prefix_offset)); | |
| } | |
| DCHECK(Bytecodes::IsJump(jump_bytecode)); | |
| switch (operand_scale) { | |
| case OperandScale::kSingle: | |
| PatchJumpWith8BitOperand(jump_location, delta); | |
| break; | |
| case OperandScale::kDouble: | |
| PatchJumpWith16BitOperand(jump_location + prefix_offset, delta); | |
| break; | |
| case OperandScale::kQuadruple: | |
| PatchJumpWith32BitOperand(jump_location + prefix_offset, delta); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| unbound_jumps_--; | |
| } | |
| void BytecodeArrayWriter::EmitJump(BytecodeNode* node, BytecodeLabel* label) { | |
| DCHECK(Bytecodes::IsJump(node->bytecode())); | |
| DCHECK_EQ(0u, node->operand(0)); | |
| size_t current_offset = bytecodes()->size(); | |
| if (label->is_bound()) { | |
| CHECK_GE(current_offset, label->offset()); | |
| CHECK_LE(current_offset, static_cast<size_t>(kMaxUInt32)); | |
| // Label has been bound already so this is a backwards jump. | |
| uint32_t delta = static_cast<uint32_t>(current_offset - label->offset()); | |
| OperandScale operand_scale = Bytecodes::ScaleForUnsignedOperand(delta); | |
| if (operand_scale > OperandScale::kSingle) { | |
| // Adjust for scaling byte prefix for wide jump offset. | |
| delta += 1; | |
| } | |
| DCHECK_EQ(Bytecode::kJumpLoop, node->bytecode()); | |
| node->update_operand0(delta); | |
| } else { | |
| // The label has not yet been bound so this is a forward reference | |
| // that will be patched when the label is bound. We create a | |
| // reservation in the constant pool so the jump can be patched | |
| // when the label is bound. The reservation means the maximum size | |
| // of the operand for the constant is known and the jump can | |
| // be emitted into the bytecode stream with space for the operand. | |
| unbound_jumps_++; | |
| label->set_referrer(current_offset); | |
| OperandSize reserved_operand_size = | |
| constant_array_builder()->CreateReservedEntry(); | |
| DCHECK_NE(Bytecode::kJumpLoop, node->bytecode()); | |
| switch (reserved_operand_size) { | |
| case OperandSize::kNone: | |
| UNREACHABLE(); | |
| break; | |
| case OperandSize::kByte: | |
| node->update_operand0(k8BitJumpPlaceholder); | |
| break; | |
| case OperandSize::kShort: | |
| node->update_operand0(k16BitJumpPlaceholder); | |
| break; | |
| case OperandSize::kQuad: | |
| node->update_operand0(k32BitJumpPlaceholder); | |
| break; | |
| } | |
| } | |
| EmitBytecode(node); | |
| } | |
| void BytecodeArrayWriter::EmitSwitch(BytecodeNode* node, | |
| BytecodeJumpTable* jump_table) { | |
| DCHECK(Bytecodes::IsSwitch(node->bytecode())); | |
| size_t current_offset = bytecodes()->size(); | |
| if (node->operand_scale() > OperandScale::kSingle) { | |
| // Adjust for scaling byte prefix. | |
| current_offset += 1; | |
| } | |
| jump_table->set_switch_bytecode_offset(current_offset); | |
| EmitBytecode(node); | |
| } | |
| } // namespace interpreter | |
| } // namespace internal | |
| } // namespace v8 |