| // 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.h" |
| #include "src/interpreter/bytecode-label.h" |
| #include "src/interpreter/bytecode-register.h" |
| #include "src/interpreter/constant-array-builder.h" |
| #include "src/log.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), |
| max_register_count_(0), |
| unbound_jumps_(0), |
| source_position_table_builder_(zone, source_position_mode), |
| constant_array_builder_(constant_array_builder) {} |
| |
| // override |
| BytecodeArrayWriter::~BytecodeArrayWriter() {} |
| |
| // override |
| Handle<BytecodeArray> BytecodeArrayWriter::ToBytecodeArray( |
| Isolate* isolate, int fixed_register_count, int parameter_count, |
| Handle<FixedArray> handler_table) { |
| DCHECK_EQ(0, unbound_jumps_); |
| |
| int bytecode_size = static_cast<int>(bytecodes()->size()); |
| |
| // All locals need a frame slot for the debugger, but may not be |
| // present in generated code. |
| int frame_size_for_locals = fixed_register_count * kPointerSize; |
| int frame_size_used = max_register_count() * kPointerSize; |
| int frame_size = std::max(frame_size_for_locals, frame_size_used); |
| Handle<FixedArray> constant_pool = |
| constant_array_builder()->ToFixedArray(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); |
| Handle<ByteArray> source_position_table = |
| source_position_table_builder()->ToSourcePositionTable( |
| isolate, Handle<AbstractCode>::cast(bytecode_array)); |
| bytecode_array->set_source_position_table(*source_position_table); |
| return bytecode_array; |
| } |
| |
| // override |
| void BytecodeArrayWriter::Write(BytecodeNode* node) { |
| DCHECK(!Bytecodes::IsJump(node->bytecode())); |
| UpdateSourcePositionTable(node); |
| EmitBytecode(node); |
| } |
| |
| // override |
| void BytecodeArrayWriter::WriteJump(BytecodeNode* node, BytecodeLabel* label) { |
| DCHECK(Bytecodes::IsJump(node->bytecode())); |
| UpdateSourcePositionTable(node); |
| EmitJump(node, label); |
| } |
| |
| // override |
| 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); |
| } |
| |
| // override |
| 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()); |
| } |
| |
| 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, |
| source_info.source_position(), |
| source_info.is_statement()); |
| } |
| } |
| |
| namespace { |
| |
| OperandScale ScaleForScalableByteOperand(OperandSize operand_size) { |
| STATIC_ASSERT(static_cast<int>(OperandSize::kByte) == |
| static_cast<int>(OperandScale::kSingle)); |
| STATIC_ASSERT(static_cast<int>(OperandSize::kShort) == |
| static_cast<int>(OperandScale::kDouble)); |
| STATIC_ASSERT(static_cast<int>(OperandSize::kQuad) == |
| static_cast<int>(OperandScale::kQuadruple)); |
| return static_cast<OperandScale>(operand_size); |
| } |
| |
| OperandScale OperandScaleForScalableSignedByte(uint32_t operand_value) { |
| int32_t signed_operand = static_cast<int32_t>(operand_value); |
| OperandSize bytes_required = Bytecodes::SizeForSignedOperand(signed_operand); |
| return ScaleForScalableByteOperand(bytes_required); |
| } |
| |
| OperandScale OperandScaleForScalableUnsignedByte(uint32_t operand_value) { |
| OperandSize bytes_required = Bytecodes::SizeForUnsignedOperand(operand_value); |
| return ScaleForScalableByteOperand(bytes_required); |
| } |
| |
| OperandScale GetOperandScale(const BytecodeNode* const node) { |
| const OperandTypeInfo* operand_type_infos = |
| Bytecodes::GetOperandTypeInfos(node->bytecode()); |
| OperandScale operand_scale = OperandScale::kSingle; |
| int operand_count = node->operand_count(); |
| for (int i = 0; i < operand_count; ++i) { |
| switch (operand_type_infos[i]) { |
| case OperandTypeInfo::kScalableSignedByte: { |
| uint32_t operand = node->operand(i); |
| operand_scale = |
| std::max(operand_scale, OperandScaleForScalableSignedByte(operand)); |
| break; |
| } |
| case OperandTypeInfo::kScalableUnsignedByte: { |
| uint32_t operand = node->operand(i); |
| operand_scale = std::max(operand_scale, |
| OperandScaleForScalableUnsignedByte(operand)); |
| break; |
| } |
| case OperandTypeInfo::kFixedUnsignedByte: |
| case OperandTypeInfo::kFixedUnsignedShort: |
| break; |
| case OperandTypeInfo::kNone: |
| UNREACHABLE(); |
| break; |
| } |
| } |
| return operand_scale; |
| } |
| |
| } // namespace |
| |
| void BytecodeArrayWriter::EmitBytecode(const BytecodeNode* const node) { |
| DCHECK_NE(node->bytecode(), Bytecode::kIllegal); |
| |
| uint8_t buffer[kMaxSizeOfPackedBytecode]; |
| uint8_t* buffer_limit = buffer; |
| |
| OperandScale operand_scale = GetOperandScale(node); |
| if (operand_scale != OperandScale::kSingle) { |
| Bytecode prefix = Bytecodes::OperandScaleToPrefixBytecode(operand_scale); |
| *buffer_limit++ = Bytecodes::ToByte(prefix); |
| } |
| |
| Bytecode bytecode = node->bytecode(); |
| *buffer_limit++ = Bytecodes::ToByte(bytecode); |
| |
| const uint32_t* const operands = node->operands(); |
| const OperandType* operand_types = Bytecodes::GetOperandTypes(bytecode); |
| const int operand_count = Bytecodes::NumberOfOperands(bytecode); |
| for (int i = 0; i < operand_count; ++i) { |
| OperandSize operand_size = |
| Bytecodes::SizeOfOperand(operand_types[i], operand_scale); |
| switch (operand_size) { |
| case OperandSize::kNone: |
| UNREACHABLE(); |
| break; |
| case OperandSize::kByte: |
| *buffer_limit++ = static_cast<uint8_t>(operands[i]); |
| break; |
| case OperandSize::kShort: { |
| WriteUnalignedUInt16(buffer_limit, operands[i]); |
| buffer_limit += 2; |
| break; |
| } |
| case OperandSize::kQuad: { |
| WriteUnalignedUInt32(buffer_limit, operands[i]); |
| buffer_limit += 4; |
| break; |
| } |
| } |
| |
| int count = Bytecodes::GetNumberOfRegistersRepresentedBy(operand_types[i]); |
| if (count == 0) { |
| continue; |
| } |
| // NB operand_types is terminated by OperandType::kNone so |
| // operand_types[i + 1] is valid whilst i < operand_count. |
| if (operand_types[i + 1] == OperandType::kRegCount) { |
| count = static_cast<int>(operands[i]); |
| } |
| Register reg = Register::FromOperand(static_cast<int32_t>(operands[i])); |
| max_register_count_ = std::max(max_register_count_, reg.index() + count); |
| } |
| |
| DCHECK_LE(buffer_limit, buffer + sizeof(buffer)); |
| bytecodes()->insert(bytecodes()->end(), buffer, buffer_limit); |
| } |
| |
| // 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::kJumpIfNotHole: |
| return Bytecode::kJumpIfNotHoleConstant; |
| case Bytecode::kJumpIfNull: |
| return Bytecode::kJumpIfNullConstant; |
| case Bytecode::kJumpIfUndefined: |
| return Bytecode::kJumpIfUndefinedConstant; |
| default: |
| UNREACHABLE(); |
| return Bytecode::kIllegal; |
| } |
| } |
| |
| void BytecodeArrayWriter::PatchJumpWith8BitOperand(size_t jump_location, |
| int delta) { |
| Bytecode jump_bytecode = Bytecodes::FromByte(bytecodes()->at(jump_location)); |
| DCHECK(Bytecodes::IsJumpImmediate(jump_bytecode)); |
| size_t operand_location = jump_location + 1; |
| DCHECK_EQ(bytecodes()->at(operand_location), k8BitJumpPlaceholder); |
| if (Bytecodes::SizeForSignedOperand(delta) == OperandSize::kByte) { |
| // The jump fits within the range of an Imm 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 Imm 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_LE(entry, kMaxUInt32); |
| 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::IsJumpImmediate(jump_bytecode)); |
| size_t operand_location = jump_location + 1; |
| uint8_t operand_bytes[2]; |
| if (Bytecodes::SizeForSignedOperand(delta) <= OperandSize::kShort) { |
| constant_array_builder()->DiscardReservedEntry(OperandSize::kShort); |
| WriteUnalignedUInt16(operand_bytes, static_cast<uint16_t>(delta)); |
| } else { |
| jump_bytecode = GetJumpWithConstantOperand(jump_bytecode); |
| bytecodes()->at(jump_location) = Bytecodes::ToByte(jump_bytecode); |
| size_t entry = constant_array_builder()->CommitReservedEntry( |
| OperandSize::kShort, Smi::FromInt(delta)); |
| WriteUnalignedUInt16(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(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(0, 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>(kMaxInt)); |
| // Label has been bound already so this is a backwards jump. |
| size_t abs_delta = current_offset - label->offset(); |
| int delta = -static_cast<int>(abs_delta); |
| OperandSize operand_size = Bytecodes::SizeForSignedOperand(delta); |
| if (operand_size > OperandSize::kByte) { |
| // Adjust for scaling byte prefix for wide jump offset. |
| DCHECK_LE(delta, 0); |
| delta -= 1; |
| } |
| DCHECK_EQ(Bytecode::kJumpLoop, node->bytecode()); |
| node->set_bytecode(node->bytecode(), delta, node->operand(1)); |
| } 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->set_bytecode(node->bytecode(), k8BitJumpPlaceholder); |
| break; |
| case OperandSize::kShort: |
| node->set_bytecode(node->bytecode(), k16BitJumpPlaceholder); |
| break; |
| case OperandSize::kQuad: |
| node->set_bytecode(node->bytecode(), k32BitJumpPlaceholder); |
| break; |
| } |
| } |
| EmitBytecode(node); |
| } |
| |
| } // namespace interpreter |
| } // namespace internal |
| } // namespace v8 |