283791d250
See https://docs.google.com/document/d/1dJHFRXKE4NUchvYweuyzsolXDEWACr-jJZEPyC6f9EQ/edit?usp=sharing Change-Id: Ie5b30db30d55ba701a336d8a59dbff7771276e96 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3936281 Reviewed-by: Leszek Swirski <leszeks@chromium.org> Reviewed-by: Maya Lekova <mslekova@chromium.org> Commit-Queue: Marja Hölttä <marja@chromium.org> Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Cr-Commit-Position: refs/heads/main@{#83593}
868 lines
31 KiB
C++
868 lines
31 KiB
C++
// Copyright 2014 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-builder.h"
|
|
|
|
#include <limits>
|
|
|
|
#include "src/ast/scopes.h"
|
|
#include "src/init/v8.h"
|
|
#include "src/interpreter/bytecode-array-iterator.h"
|
|
#include "src/interpreter/bytecode-jump-table.h"
|
|
#include "src/interpreter/bytecode-label.h"
|
|
#include "src/interpreter/bytecode-register-allocator.h"
|
|
#include "src/numbers/hash-seed-inl.h"
|
|
#include "src/objects/objects-inl.h"
|
|
#include "src/objects/smi.h"
|
|
#include "test/unittests/interpreter/bytecode-utils.h"
|
|
#include "test/unittests/test-utils.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
namespace interpreter {
|
|
|
|
class BytecodeArrayBuilderTest : public TestWithIsolateAndZone {
|
|
public:
|
|
BytecodeArrayBuilderTest() = default;
|
|
~BytecodeArrayBuilderTest() override = default;
|
|
};
|
|
|
|
using ToBooleanMode = BytecodeArrayBuilder::ToBooleanMode;
|
|
|
|
TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
|
|
FeedbackVectorSpec feedback_spec(zone());
|
|
BytecodeArrayBuilder builder(zone(), 1, 131, &feedback_spec);
|
|
Factory* factory = isolate()->factory();
|
|
AstValueFactory ast_factory(zone(), isolate()->ast_string_constants(),
|
|
HashSeed(isolate()));
|
|
DeclarationScope scope(zone(), &ast_factory);
|
|
|
|
CHECK_EQ(builder.locals_count(), 131);
|
|
CHECK_EQ(builder.fixed_register_count(), 131);
|
|
|
|
Register reg(0);
|
|
Register other(reg.index() + 1);
|
|
Register wide(128);
|
|
RegisterList empty;
|
|
RegisterList single = BytecodeUtils::NewRegisterList(0, 1);
|
|
RegisterList pair = BytecodeUtils::NewRegisterList(0, 2);
|
|
RegisterList triple = BytecodeUtils::NewRegisterList(0, 3);
|
|
RegisterList reg_list = BytecodeUtils::NewRegisterList(0, 10);
|
|
|
|
// Emit argument creation operations.
|
|
builder.CreateArguments(CreateArgumentsType::kMappedArguments)
|
|
.CreateArguments(CreateArgumentsType::kUnmappedArguments)
|
|
.CreateArguments(CreateArgumentsType::kRestParameter);
|
|
|
|
// Emit constant loads.
|
|
builder.LoadLiteral(Smi::zero())
|
|
.StoreAccumulatorInRegister(reg)
|
|
.LoadLiteral(Smi::FromInt(8))
|
|
.CompareOperation(Token::Value::EQ, reg,
|
|
1) // Prevent peephole optimization
|
|
// LdaSmi, Star -> LdrSmi.
|
|
.StoreAccumulatorInRegister(reg)
|
|
.LoadLiteral(Smi::FromInt(10000000))
|
|
.StoreAccumulatorInRegister(reg)
|
|
.LoadLiteral(ast_factory.GetOneByteString("A constant"))
|
|
.StoreAccumulatorInRegister(reg)
|
|
.LoadUndefined()
|
|
.StoreAccumulatorInRegister(reg)
|
|
.LoadNull()
|
|
.StoreAccumulatorInRegister(reg)
|
|
.LoadTheHole()
|
|
.StoreAccumulatorInRegister(reg)
|
|
.LoadTrue()
|
|
.StoreAccumulatorInRegister(reg)
|
|
.LoadFalse()
|
|
.StoreAccumulatorInRegister(wide);
|
|
|
|
// Emit Ldar and Star taking care to foil the register optimizer.
|
|
builder.LoadAccumulatorWithRegister(other)
|
|
.BinaryOperation(Token::ADD, reg, 1)
|
|
.StoreAccumulatorInRegister(reg)
|
|
.LoadNull();
|
|
|
|
// The above had a lot of Star0, but we must also emit the rest of
|
|
// the short-star codes.
|
|
for (int i = 1; i < 16; ++i) {
|
|
builder.StoreAccumulatorInRegister(Register(i));
|
|
}
|
|
|
|
// Emit register-register transfer.
|
|
builder.MoveRegister(reg, other);
|
|
builder.MoveRegister(reg, wide);
|
|
|
|
FeedbackSlot load_global_slot =
|
|
feedback_spec.AddLoadGlobalICSlot(TypeofMode::kNotInside);
|
|
FeedbackSlot load_global_typeof_slot =
|
|
feedback_spec.AddLoadGlobalICSlot(TypeofMode::kInside);
|
|
FeedbackSlot sloppy_store_global_slot =
|
|
feedback_spec.AddStoreGlobalICSlot(LanguageMode::kSloppy);
|
|
FeedbackSlot load_slot = feedback_spec.AddLoadICSlot();
|
|
FeedbackSlot call_slot = feedback_spec.AddCallICSlot();
|
|
FeedbackSlot keyed_load_slot = feedback_spec.AddKeyedLoadICSlot();
|
|
FeedbackSlot sloppy_store_slot =
|
|
feedback_spec.AddStoreICSlot(LanguageMode::kSloppy);
|
|
FeedbackSlot strict_store_slot =
|
|
feedback_spec.AddStoreICSlot(LanguageMode::kStrict);
|
|
FeedbackSlot sloppy_keyed_store_slot =
|
|
feedback_spec.AddKeyedStoreICSlot(LanguageMode::kSloppy);
|
|
FeedbackSlot strict_keyed_store_slot =
|
|
feedback_spec.AddKeyedStoreICSlot(LanguageMode::kStrict);
|
|
FeedbackSlot define_named_own_slot = feedback_spec.AddDefineNamedOwnICSlot();
|
|
FeedbackSlot store_array_element_slot =
|
|
feedback_spec.AddStoreInArrayLiteralICSlot();
|
|
|
|
// Emit global load / store operations.
|
|
const AstRawString* name = ast_factory.GetOneByteString("var_name");
|
|
builder.LoadGlobal(name, load_global_slot.ToInt(), TypeofMode::kNotInside)
|
|
.LoadGlobal(name, load_global_typeof_slot.ToInt(), TypeofMode::kInside)
|
|
.StoreGlobal(name, sloppy_store_global_slot.ToInt());
|
|
|
|
// Emit context operations.
|
|
builder.PushContext(reg)
|
|
.PopContext(reg)
|
|
.LoadContextSlot(reg, 1, 0, BytecodeArrayBuilder::kMutableSlot)
|
|
.StoreContextSlot(reg, 1, 0)
|
|
.LoadContextSlot(reg, 2, 0, BytecodeArrayBuilder::kImmutableSlot)
|
|
.StoreContextSlot(reg, 3, 0);
|
|
|
|
// Emit context operations which operate on the local context.
|
|
builder
|
|
.LoadContextSlot(Register::current_context(), 1, 0,
|
|
BytecodeArrayBuilder::kMutableSlot)
|
|
.StoreContextSlot(Register::current_context(), 1, 0)
|
|
.LoadContextSlot(Register::current_context(), 2, 0,
|
|
BytecodeArrayBuilder::kImmutableSlot)
|
|
.StoreContextSlot(Register::current_context(), 3, 0);
|
|
|
|
// Emit load / store property operations.
|
|
builder.LoadNamedProperty(reg, name, load_slot.ToInt())
|
|
.LoadNamedPropertyFromSuper(reg, name, load_slot.ToInt())
|
|
.LoadKeyedProperty(reg, keyed_load_slot.ToInt())
|
|
.SetNamedProperty(reg, name, sloppy_store_slot.ToInt(),
|
|
LanguageMode::kSloppy)
|
|
.SetKeyedProperty(reg, reg, sloppy_keyed_store_slot.ToInt(),
|
|
LanguageMode::kSloppy)
|
|
.SetNamedProperty(reg, name, strict_store_slot.ToInt(),
|
|
LanguageMode::kStrict)
|
|
.SetKeyedProperty(reg, reg, strict_keyed_store_slot.ToInt(),
|
|
LanguageMode::kStrict)
|
|
.DefineNamedOwnProperty(reg, name, define_named_own_slot.ToInt())
|
|
.DefineKeyedOwnProperty(reg, reg, define_named_own_slot.ToInt())
|
|
.StoreInArrayLiteral(reg, reg, store_array_element_slot.ToInt());
|
|
|
|
// Emit Iterator-protocol operations
|
|
builder.GetIterator(reg, load_slot.ToInt(), call_slot.ToInt());
|
|
|
|
// Emit load / store lookup slots.
|
|
builder.LoadLookupSlot(name, TypeofMode::kNotInside)
|
|
.LoadLookupSlot(name, TypeofMode::kInside)
|
|
.StoreLookupSlot(name, LanguageMode::kSloppy, LookupHoistingMode::kNormal)
|
|
.StoreLookupSlot(name, LanguageMode::kSloppy,
|
|
LookupHoistingMode::kLegacySloppy)
|
|
.StoreLookupSlot(name, LanguageMode::kStrict,
|
|
LookupHoistingMode::kNormal);
|
|
|
|
// Emit load / store lookup slots with context fast paths.
|
|
builder.LoadLookupContextSlot(name, TypeofMode::kNotInside, 1, 0)
|
|
.LoadLookupContextSlot(name, TypeofMode::kInside, 1, 0);
|
|
|
|
// Emit load / store lookup slots with global fast paths.
|
|
builder.LoadLookupGlobalSlot(name, TypeofMode::kNotInside, 1, 0)
|
|
.LoadLookupGlobalSlot(name, TypeofMode::kInside, 1, 0);
|
|
|
|
// Emit closure operations.
|
|
builder.CreateClosure(0, 1, static_cast<int>(AllocationType::kYoung));
|
|
|
|
// Emit create context operation.
|
|
builder.CreateBlockContext(&scope);
|
|
builder.CreateCatchContext(reg, &scope);
|
|
builder.CreateFunctionContext(&scope, 1);
|
|
builder.CreateEvalContext(&scope, 1);
|
|
builder.CreateWithContext(reg, &scope);
|
|
|
|
// Emit literal creation operations.
|
|
builder.CreateRegExpLiteral(ast_factory.GetOneByteString("a"), 0, 0);
|
|
builder.CreateArrayLiteral(0, 0, 0);
|
|
builder.CreateObjectLiteral(0, 0, 0);
|
|
|
|
// Emit tagged template operations.
|
|
builder.GetTemplateObject(0, 0);
|
|
|
|
// Call operations.
|
|
builder.CallAnyReceiver(reg, reg_list, 1)
|
|
.CallProperty(reg, reg_list, 1)
|
|
.CallProperty(reg, single, 1)
|
|
.CallProperty(reg, pair, 1)
|
|
.CallProperty(reg, triple, 1)
|
|
.CallUndefinedReceiver(reg, reg_list, 1)
|
|
.CallUndefinedReceiver(reg, empty, 1)
|
|
.CallUndefinedReceiver(reg, single, 1)
|
|
.CallUndefinedReceiver(reg, pair, 1)
|
|
.CallRuntime(Runtime::kIsArray, reg)
|
|
.CallRuntimeForPair(Runtime::kLoadLookupSlotForCall, reg_list, pair)
|
|
.CallJSRuntime(Context::PROMISE_THEN_INDEX, reg_list)
|
|
.CallWithSpread(reg, reg_list, 1);
|
|
|
|
// Emit binary operator invocations.
|
|
builder.BinaryOperation(Token::Value::ADD, reg, 1)
|
|
.BinaryOperation(Token::Value::SUB, reg, 2)
|
|
.BinaryOperation(Token::Value::MUL, reg, 3)
|
|
.BinaryOperation(Token::Value::DIV, reg, 4)
|
|
.BinaryOperation(Token::Value::MOD, reg, 5)
|
|
.BinaryOperation(Token::Value::EXP, reg, 6);
|
|
|
|
// Emit bitwise operator invocations
|
|
builder.BinaryOperation(Token::Value::BIT_OR, reg, 6)
|
|
.BinaryOperation(Token::Value::BIT_XOR, reg, 7)
|
|
.BinaryOperation(Token::Value::BIT_AND, reg, 8);
|
|
|
|
// Emit shift operator invocations
|
|
builder.BinaryOperation(Token::Value::SHL, reg, 9)
|
|
.BinaryOperation(Token::Value::SAR, reg, 10)
|
|
.BinaryOperation(Token::Value::SHR, reg, 11);
|
|
|
|
// Emit Smi binary operations.
|
|
builder.BinaryOperationSmiLiteral(Token::Value::ADD, Smi::FromInt(42), 2)
|
|
.BinaryOperationSmiLiteral(Token::Value::SUB, Smi::FromInt(42), 2)
|
|
.BinaryOperationSmiLiteral(Token::Value::MUL, Smi::FromInt(42), 2)
|
|
.BinaryOperationSmiLiteral(Token::Value::DIV, Smi::FromInt(42), 2)
|
|
.BinaryOperationSmiLiteral(Token::Value::MOD, Smi::FromInt(42), 2)
|
|
.BinaryOperationSmiLiteral(Token::Value::EXP, Smi::FromInt(42), 2)
|
|
.BinaryOperationSmiLiteral(Token::Value::BIT_OR, Smi::FromInt(42), 2)
|
|
.BinaryOperationSmiLiteral(Token::Value::BIT_XOR, Smi::FromInt(42), 2)
|
|
.BinaryOperationSmiLiteral(Token::Value::BIT_AND, Smi::FromInt(42), 2)
|
|
.BinaryOperationSmiLiteral(Token::Value::SHL, Smi::FromInt(42), 2)
|
|
.BinaryOperationSmiLiteral(Token::Value::SAR, Smi::FromInt(42), 2)
|
|
.BinaryOperationSmiLiteral(Token::Value::SHR, Smi::FromInt(42), 2);
|
|
|
|
// Emit unary and count operator invocations.
|
|
builder.UnaryOperation(Token::Value::INC, 1)
|
|
.UnaryOperation(Token::Value::DEC, 1)
|
|
.UnaryOperation(Token::Value::ADD, 1)
|
|
.UnaryOperation(Token::Value::SUB, 1)
|
|
.UnaryOperation(Token::Value::BIT_NOT, 1);
|
|
|
|
// Emit unary operator invocations.
|
|
builder.LogicalNot(ToBooleanMode::kConvertToBoolean)
|
|
.LogicalNot(ToBooleanMode::kAlreadyBoolean)
|
|
.TypeOf();
|
|
|
|
// Emit delete
|
|
builder.Delete(reg, LanguageMode::kSloppy).Delete(reg, LanguageMode::kStrict);
|
|
|
|
// Emit construct.
|
|
builder.Construct(reg, reg_list, 1).ConstructWithSpread(reg, reg_list, 1);
|
|
|
|
// Emit test operator invocations.
|
|
builder.CompareOperation(Token::Value::EQ, reg, 1)
|
|
.CompareOperation(Token::Value::EQ_STRICT, reg, 2)
|
|
.CompareOperation(Token::Value::LT, reg, 3)
|
|
.CompareOperation(Token::Value::GT, reg, 4)
|
|
.CompareOperation(Token::Value::LTE, reg, 5)
|
|
.CompareOperation(Token::Value::GTE, reg, 6)
|
|
.CompareTypeOf(TestTypeOfFlags::LiteralFlag::kNumber)
|
|
.CompareOperation(Token::Value::INSTANCEOF, reg, 7)
|
|
.CompareOperation(Token::Value::IN, reg, 8)
|
|
.CompareReference(reg)
|
|
.CompareUndetectable()
|
|
.CompareUndefined()
|
|
.CompareNull();
|
|
|
|
// Emit conversion operator invocations.
|
|
builder.ToNumber(1).ToNumeric(1).ToObject(reg).ToName(reg).ToString();
|
|
|
|
// Emit GetSuperConstructor.
|
|
builder.GetSuperConstructor(reg);
|
|
|
|
// Constructor check for GetSuperConstructor.
|
|
builder.ThrowIfNotSuperConstructor(reg);
|
|
|
|
// Hole checks.
|
|
builder.ThrowReferenceErrorIfHole(name)
|
|
.ThrowSuperAlreadyCalledIfNotHole()
|
|
.ThrowSuperNotCalledIfHole();
|
|
|
|
// Short jumps with Imm8 operands
|
|
{
|
|
BytecodeLoopHeader loop_header;
|
|
BytecodeLabel after_jump1, after_jump2, after_jump3, after_jump4,
|
|
after_jump5, after_jump6, after_jump7, after_jump8, after_jump9,
|
|
after_jump10, after_jump11, after_loop;
|
|
builder.JumpIfNull(&after_loop)
|
|
.Bind(&loop_header)
|
|
.Jump(&after_jump1)
|
|
.Bind(&after_jump1)
|
|
.JumpIfNull(&after_jump2)
|
|
.Bind(&after_jump2)
|
|
.JumpIfNotNull(&after_jump3)
|
|
.Bind(&after_jump3)
|
|
.JumpIfUndefined(&after_jump4)
|
|
.Bind(&after_jump4)
|
|
.JumpIfNotUndefined(&after_jump5)
|
|
.Bind(&after_jump5)
|
|
.JumpIfUndefinedOrNull(&after_jump6)
|
|
.Bind(&after_jump6)
|
|
.JumpIfJSReceiver(&after_jump7)
|
|
.Bind(&after_jump7)
|
|
.JumpIfTrue(ToBooleanMode::kConvertToBoolean, &after_jump8)
|
|
.Bind(&after_jump8)
|
|
.JumpIfTrue(ToBooleanMode::kAlreadyBoolean, &after_jump9)
|
|
.Bind(&after_jump9)
|
|
.JumpIfFalse(ToBooleanMode::kConvertToBoolean, &after_jump10)
|
|
.Bind(&after_jump10)
|
|
.JumpIfFalse(ToBooleanMode::kAlreadyBoolean, &after_jump11)
|
|
.Bind(&after_jump11)
|
|
.JumpLoop(&loop_header, 0, 0, 0)
|
|
.Bind(&after_loop);
|
|
}
|
|
|
|
BytecodeLabel end[11];
|
|
{
|
|
// Longer jumps with constant operands
|
|
BytecodeLabel after_jump;
|
|
builder.JumpIfNull(&after_jump)
|
|
.Jump(&end[0])
|
|
.Bind(&after_jump)
|
|
.JumpIfTrue(ToBooleanMode::kConvertToBoolean, &end[1])
|
|
.JumpIfTrue(ToBooleanMode::kAlreadyBoolean, &end[2])
|
|
.JumpIfFalse(ToBooleanMode::kConvertToBoolean, &end[3])
|
|
.JumpIfFalse(ToBooleanMode::kAlreadyBoolean, &end[4])
|
|
.JumpIfNull(&end[5])
|
|
.JumpIfNotNull(&end[6])
|
|
.JumpIfUndefined(&end[7])
|
|
.JumpIfNotUndefined(&end[8])
|
|
.JumpIfUndefinedOrNull(&end[9])
|
|
.LoadLiteral(ast_factory.prototype_string())
|
|
.JumpIfJSReceiver(&end[10]);
|
|
}
|
|
|
|
// Emit Smi table switch bytecode.
|
|
BytecodeJumpTable* jump_table = builder.AllocateJumpTable(1, 0);
|
|
builder.SwitchOnSmiNoFeedback(jump_table).Bind(jump_table, 0);
|
|
|
|
// Emit set pending message bytecode.
|
|
builder.SetPendingMessage();
|
|
|
|
// Emit throw and re-throw in it's own basic block so that the rest of the
|
|
// code isn't omitted due to being dead.
|
|
BytecodeLabel after_throw, after_rethrow;
|
|
builder.JumpIfNull(&after_throw).Throw().Bind(&after_throw);
|
|
builder.JumpIfNull(&after_rethrow).ReThrow().Bind(&after_rethrow);
|
|
|
|
builder.ForInEnumerate(reg)
|
|
.ForInPrepare(triple, 1)
|
|
.ForInContinue(reg, reg)
|
|
.ForInNext(reg, reg, pair, 1)
|
|
.ForInStep(reg);
|
|
|
|
// Wide constant pool loads
|
|
for (int i = 0; i < 256; i++) {
|
|
// Emit junk in constant pool to force wide constant pool index.
|
|
builder.LoadLiteral(2.5321 + i);
|
|
}
|
|
builder.LoadLiteral(Smi::FromInt(20000000));
|
|
const AstRawString* wide_name = ast_factory.GetOneByteString("var_wide_name");
|
|
|
|
builder.DefineKeyedOwnPropertyInLiteral(
|
|
reg, reg, DefineKeyedOwnPropertyInLiteralFlag::kNoFlags, 0);
|
|
|
|
// Emit wide context operations.
|
|
builder.LoadContextSlot(reg, 1024, 0, BytecodeArrayBuilder::kMutableSlot)
|
|
.StoreContextSlot(reg, 1024, 0);
|
|
|
|
// Emit wide load / store lookup slots.
|
|
builder.LoadLookupSlot(wide_name, TypeofMode::kNotInside)
|
|
.LoadLookupSlot(wide_name, TypeofMode::kInside)
|
|
.StoreLookupSlot(wide_name, LanguageMode::kSloppy,
|
|
LookupHoistingMode::kNormal)
|
|
.StoreLookupSlot(wide_name, LanguageMode::kSloppy,
|
|
LookupHoistingMode::kLegacySloppy)
|
|
.StoreLookupSlot(wide_name, LanguageMode::kStrict,
|
|
LookupHoistingMode::kNormal);
|
|
|
|
// CreateClosureWide
|
|
builder.CreateClosure(1000, 321, static_cast<int>(AllocationType::kYoung));
|
|
|
|
// Emit wide variant of literal creation operations.
|
|
builder
|
|
.CreateRegExpLiteral(ast_factory.GetOneByteString("wide_literal"), 0, 0)
|
|
.CreateArrayLiteral(0, 0, 0)
|
|
.CreateEmptyArrayLiteral(0)
|
|
.CreateArrayFromIterable()
|
|
.CreateObjectLiteral(0, 0, 0)
|
|
.CreateEmptyObjectLiteral()
|
|
.CloneObject(reg, 0, 0);
|
|
|
|
// Emit load and store operations for module variables.
|
|
builder.LoadModuleVariable(-1, 42)
|
|
.LoadModuleVariable(0, 42)
|
|
.LoadModuleVariable(1, 42)
|
|
.StoreModuleVariable(-1, 42)
|
|
.StoreModuleVariable(0, 42)
|
|
.StoreModuleVariable(1, 42);
|
|
|
|
// Emit generator operations.
|
|
{
|
|
// We have to skip over suspend because it returns and marks the remaining
|
|
// bytecode dead.
|
|
BytecodeLabel after_suspend;
|
|
builder.JumpIfTrue(ToBooleanMode::kAlreadyBoolean, &after_suspend)
|
|
.SuspendGenerator(reg, reg_list, 0)
|
|
.Bind(&after_suspend)
|
|
.ResumeGenerator(reg, reg_list);
|
|
}
|
|
BytecodeJumpTable* gen_jump_table = builder.AllocateJumpTable(1, 0);
|
|
builder.SwitchOnGeneratorState(reg, gen_jump_table).Bind(gen_jump_table, 0);
|
|
|
|
// Intrinsics handled by the interpreter.
|
|
builder.CallRuntime(Runtime::kInlineAsyncFunctionReject, reg_list);
|
|
|
|
// Emit debugger bytecode.
|
|
builder.Debugger();
|
|
|
|
// Emit abort bytecode.
|
|
BytecodeLabel after_abort;
|
|
builder.JumpIfNull(&after_abort)
|
|
.Abort(AbortReason::kOperandIsASmi)
|
|
.Bind(&after_abort);
|
|
|
|
// Insert dummy ops to force longer jumps.
|
|
for (int i = 0; i < 256; i++) {
|
|
builder.Debugger();
|
|
}
|
|
|
|
// Emit block counter increments.
|
|
builder.IncBlockCounter(0);
|
|
|
|
// Bind labels for long jumps at the very end.
|
|
for (size_t i = 0; i < arraysize(end); i++) {
|
|
builder.Bind(&end[i]);
|
|
}
|
|
|
|
// Return must be the last instruction.
|
|
builder.Return();
|
|
|
|
// Generate BytecodeArray.
|
|
Handle<ScopeInfo> scope_info =
|
|
factory->NewScopeInfo(ScopeInfo::kVariablePartIndex);
|
|
int flags = ScopeInfo::IsEmptyBit::encode(true);
|
|
scope_info->set_flags(flags);
|
|
scope_info->set_context_local_count(0);
|
|
scope_info->set_parameter_count(0);
|
|
scope.SetScriptScopeInfo(scope_info);
|
|
|
|
ast_factory.Internalize(isolate());
|
|
Handle<BytecodeArray> the_array = builder.ToBytecodeArray(isolate());
|
|
CHECK_EQ(the_array->frame_size(),
|
|
builder.total_register_count() * kSystemPointerSize);
|
|
|
|
// Build scorecard of bytecodes encountered in the BytecodeArray.
|
|
std::vector<int> scorecard(Bytecodes::ToByte(Bytecode::kLast) + 1);
|
|
|
|
Bytecode final_bytecode = Bytecode::kLdaZero;
|
|
int i = 0;
|
|
while (i < the_array->length()) {
|
|
uint8_t code = the_array->get(i);
|
|
scorecard[code] += 1;
|
|
final_bytecode = Bytecodes::FromByte(code);
|
|
OperandScale operand_scale = OperandScale::kSingle;
|
|
int prefix_offset = 0;
|
|
if (Bytecodes::IsPrefixScalingBytecode(final_bytecode)) {
|
|
operand_scale = Bytecodes::PrefixBytecodeToOperandScale(final_bytecode);
|
|
prefix_offset = 1;
|
|
code = the_array->get(i + 1);
|
|
scorecard[code] += 1;
|
|
final_bytecode = Bytecodes::FromByte(code);
|
|
}
|
|
i += prefix_offset + Bytecodes::Size(final_bytecode, operand_scale);
|
|
}
|
|
|
|
// Insert entry for illegal bytecode as this is never willingly emitted.
|
|
scorecard[Bytecodes::ToByte(Bytecode::kIllegal)] = 1;
|
|
|
|
// This bytecode is too inconvenient to test manually.
|
|
scorecard[Bytecodes::ToByte(
|
|
Bytecode::kFindNonDefaultConstructorOrConstruct)] = 1;
|
|
|
|
// Check return occurs at the end and only once in the BytecodeArray.
|
|
CHECK_EQ(final_bytecode, Bytecode::kReturn);
|
|
CHECK_EQ(scorecard[Bytecodes::ToByte(final_bytecode)], 1);
|
|
|
|
#define CHECK_BYTECODE_PRESENT(Name, ...) \
|
|
/* Check Bytecode is marked in scorecard, unless it's a debug break */ \
|
|
if (!Bytecodes::IsDebugBreak(Bytecode::k##Name)) { \
|
|
EXPECT_GE(scorecard[Bytecodes::ToByte(Bytecode::k##Name)], 1); \
|
|
}
|
|
BYTECODE_LIST(CHECK_BYTECODE_PRESENT)
|
|
#undef CHECK_BYTECODE_PRESENT
|
|
}
|
|
|
|
TEST_F(BytecodeArrayBuilderTest, FrameSizesLookGood) {
|
|
for (int locals = 0; locals < 5; locals++) {
|
|
for (int temps = 0; temps < 3; temps++) {
|
|
BytecodeArrayBuilder builder(zone(), 1, locals);
|
|
BytecodeRegisterAllocator* allocator(builder.register_allocator());
|
|
for (int i = 0; i < locals; i++) {
|
|
builder.LoadLiteral(Smi::zero());
|
|
builder.StoreAccumulatorInRegister(Register(i));
|
|
}
|
|
for (int i = 0; i < temps; i++) {
|
|
Register temp = allocator->NewRegister();
|
|
builder.LoadLiteral(Smi::zero());
|
|
builder.StoreAccumulatorInRegister(temp);
|
|
// Ensure temporaries are used so not optimized away by the
|
|
// register optimizer.
|
|
builder.ToName(temp);
|
|
}
|
|
builder.Return();
|
|
|
|
Handle<BytecodeArray> the_array = builder.ToBytecodeArray(isolate());
|
|
int total_registers = locals + temps;
|
|
CHECK_EQ(the_array->frame_size(), total_registers * kSystemPointerSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_F(BytecodeArrayBuilderTest, RegisterValues) {
|
|
int index = 1;
|
|
|
|
Register the_register(index);
|
|
CHECK_EQ(the_register.index(), index);
|
|
|
|
int actual_operand = the_register.ToOperand();
|
|
int actual_index = Register::FromOperand(actual_operand).index();
|
|
CHECK_EQ(actual_index, index);
|
|
}
|
|
|
|
TEST_F(BytecodeArrayBuilderTest, Parameters) {
|
|
BytecodeArrayBuilder builder(zone(), 10, 0);
|
|
|
|
Register receiver(builder.Receiver());
|
|
Register param8(builder.Parameter(8));
|
|
CHECK_EQ(receiver.index() - param8.index(), 9);
|
|
}
|
|
|
|
TEST_F(BytecodeArrayBuilderTest, Constants) {
|
|
BytecodeArrayBuilder builder(zone(), 1, 0);
|
|
AstValueFactory ast_factory(zone(), isolate()->ast_string_constants(),
|
|
HashSeed(isolate()));
|
|
|
|
double heap_num_1 = 3.14;
|
|
double heap_num_2 = 5.2;
|
|
double nan = std::numeric_limits<double>::quiet_NaN();
|
|
const AstRawString* string = ast_factory.GetOneByteString("foo");
|
|
const AstRawString* string_copy = ast_factory.GetOneByteString("foo");
|
|
|
|
builder.LoadLiteral(heap_num_1)
|
|
.LoadLiteral(heap_num_2)
|
|
.LoadLiteral(string)
|
|
.LoadLiteral(heap_num_1)
|
|
.LoadLiteral(heap_num_1)
|
|
.LoadLiteral(nan)
|
|
.LoadLiteral(string_copy)
|
|
.LoadLiteral(heap_num_2)
|
|
.LoadLiteral(nan)
|
|
.Return();
|
|
|
|
ast_factory.Internalize(isolate());
|
|
Handle<BytecodeArray> array = builder.ToBytecodeArray(isolate());
|
|
// Should only have one entry for each identical constant.
|
|
EXPECT_EQ(4, array->constant_pool().length());
|
|
}
|
|
|
|
TEST_F(BytecodeArrayBuilderTest, ForwardJumps) {
|
|
static const int kFarJumpDistance = 256 + 20;
|
|
|
|
BytecodeArrayBuilder builder(zone(), 1, 1);
|
|
|
|
Register reg(0);
|
|
BytecodeLabel far0, far1, far2, far3, far4;
|
|
BytecodeLabel near0, near1, near2, near3, near4;
|
|
BytecodeLabel after_jump_near0, after_jump_far0;
|
|
|
|
builder.JumpIfNull(&after_jump_near0)
|
|
.Jump(&near0)
|
|
.Bind(&after_jump_near0)
|
|
.CompareOperation(Token::Value::EQ, reg, 1)
|
|
.JumpIfTrue(ToBooleanMode::kAlreadyBoolean, &near1)
|
|
.CompareOperation(Token::Value::EQ, reg, 2)
|
|
.JumpIfFalse(ToBooleanMode::kAlreadyBoolean, &near2)
|
|
.BinaryOperation(Token::Value::ADD, reg, 1)
|
|
.JumpIfTrue(ToBooleanMode::kConvertToBoolean, &near3)
|
|
.BinaryOperation(Token::Value::ADD, reg, 2)
|
|
.JumpIfFalse(ToBooleanMode::kConvertToBoolean, &near4)
|
|
.Bind(&near0)
|
|
.Bind(&near1)
|
|
.Bind(&near2)
|
|
.Bind(&near3)
|
|
.Bind(&near4)
|
|
.JumpIfNull(&after_jump_far0)
|
|
.Jump(&far0)
|
|
.Bind(&after_jump_far0)
|
|
.CompareOperation(Token::Value::EQ, reg, 3)
|
|
.JumpIfTrue(ToBooleanMode::kAlreadyBoolean, &far1)
|
|
.CompareOperation(Token::Value::EQ, reg, 4)
|
|
.JumpIfFalse(ToBooleanMode::kAlreadyBoolean, &far2)
|
|
.BinaryOperation(Token::Value::ADD, reg, 3)
|
|
.JumpIfTrue(ToBooleanMode::kConvertToBoolean, &far3)
|
|
.BinaryOperation(Token::Value::ADD, reg, 4)
|
|
.JumpIfFalse(ToBooleanMode::kConvertToBoolean, &far4);
|
|
for (int i = 0; i < kFarJumpDistance - 22; i++) {
|
|
builder.Debugger();
|
|
}
|
|
builder.Bind(&far0).Bind(&far1).Bind(&far2).Bind(&far3).Bind(&far4);
|
|
builder.Return();
|
|
|
|
Handle<BytecodeArray> array = builder.ToBytecodeArray(isolate());
|
|
DCHECK_EQ(array->length(), 48 + kFarJumpDistance - 22 + 1);
|
|
|
|
BytecodeArrayIterator iterator(array);
|
|
|
|
// Ignore JumpIfNull operation.
|
|
iterator.Advance();
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kJump);
|
|
CHECK_EQ(iterator.GetUnsignedImmediateOperand(0), 22);
|
|
iterator.Advance();
|
|
|
|
// Ignore compare operation.
|
|
iterator.Advance();
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpIfTrue);
|
|
CHECK_EQ(iterator.GetUnsignedImmediateOperand(0), 17);
|
|
iterator.Advance();
|
|
|
|
// Ignore compare operation.
|
|
iterator.Advance();
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpIfFalse);
|
|
CHECK_EQ(iterator.GetUnsignedImmediateOperand(0), 12);
|
|
iterator.Advance();
|
|
|
|
// Ignore add operation.
|
|
iterator.Advance();
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpIfToBooleanTrue);
|
|
CHECK_EQ(iterator.GetUnsignedImmediateOperand(0), 7);
|
|
iterator.Advance();
|
|
|
|
// Ignore add operation.
|
|
iterator.Advance();
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpIfToBooleanFalse);
|
|
CHECK_EQ(iterator.GetUnsignedImmediateOperand(0), 2);
|
|
iterator.Advance();
|
|
|
|
// Ignore JumpIfNull operation.
|
|
iterator.Advance();
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpConstant);
|
|
CHECK_EQ(*(iterator.GetConstantForIndexOperand(0, isolate())),
|
|
Smi::FromInt(kFarJumpDistance));
|
|
iterator.Advance();
|
|
|
|
// Ignore compare operation.
|
|
iterator.Advance();
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpIfTrueConstant);
|
|
CHECK_EQ(*(iterator.GetConstantForIndexOperand(0, isolate())),
|
|
Smi::FromInt(kFarJumpDistance - 5));
|
|
iterator.Advance();
|
|
|
|
// Ignore compare operation.
|
|
iterator.Advance();
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpIfFalseConstant);
|
|
CHECK_EQ(*(iterator.GetConstantForIndexOperand(0, isolate())),
|
|
Smi::FromInt(kFarJumpDistance - 10));
|
|
iterator.Advance();
|
|
|
|
// Ignore add operation.
|
|
iterator.Advance();
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpIfToBooleanTrueConstant);
|
|
CHECK_EQ(*(iterator.GetConstantForIndexOperand(0, isolate())),
|
|
Smi::FromInt(kFarJumpDistance - 15));
|
|
iterator.Advance();
|
|
|
|
// Ignore add operation.
|
|
iterator.Advance();
|
|
|
|
CHECK_EQ(iterator.current_bytecode(),
|
|
Bytecode::kJumpIfToBooleanFalseConstant);
|
|
CHECK_EQ(*(iterator.GetConstantForIndexOperand(0, isolate())),
|
|
Smi::FromInt(kFarJumpDistance - 20));
|
|
iterator.Advance();
|
|
}
|
|
|
|
TEST_F(BytecodeArrayBuilderTest, BackwardJumps) {
|
|
BytecodeArrayBuilder builder(zone(), 1, 1);
|
|
|
|
BytecodeLabel end;
|
|
builder.JumpIfNull(&end);
|
|
|
|
BytecodeLabel after_loop;
|
|
// Conditional jump to force the code after the JumpLoop to be live.
|
|
// Technically this jump is illegal because it's jumping into the middle of
|
|
// the subsequent loops, but that's ok for this unit test.
|
|
BytecodeLoopHeader loop_header;
|
|
builder.JumpIfNull(&after_loop)
|
|
.Bind(&loop_header)
|
|
.JumpLoop(&loop_header, 0, 0, 0)
|
|
.Bind(&after_loop);
|
|
for (int i = 0; i < 42; i++) {
|
|
BytecodeLabel also_after_loop;
|
|
// Conditional jump to force the code after the JumpLoop to be live.
|
|
builder.JumpIfNull(&also_after_loop)
|
|
.JumpLoop(&loop_header, 0, 0, 0)
|
|
.Bind(&also_after_loop);
|
|
}
|
|
|
|
// Add padding to force wide backwards jumps.
|
|
for (int i = 0; i < 256; i++) {
|
|
builder.Debugger();
|
|
}
|
|
|
|
builder.JumpLoop(&loop_header, 0, 0, 0);
|
|
builder.Bind(&end);
|
|
builder.Return();
|
|
|
|
Handle<BytecodeArray> array = builder.ToBytecodeArray(isolate());
|
|
BytecodeArrayIterator iterator(array);
|
|
// Ignore the JumpIfNull to the end
|
|
iterator.Advance();
|
|
// Ignore the JumpIfNull to after the first JumpLoop
|
|
iterator.Advance();
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpLoop);
|
|
CHECK_EQ(iterator.GetUnsignedImmediateOperand(0), 0);
|
|
iterator.Advance();
|
|
for (unsigned i = 0; i < 42; i++) {
|
|
// Ignore the JumpIfNull to after the JumpLoop
|
|
iterator.Advance();
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpLoop);
|
|
CHECK_EQ(iterator.current_operand_scale(), OperandScale::kSingle);
|
|
// offset of 6 (because kJumpLoop takes three immediate operands and
|
|
// JumpIfNull takes 1)
|
|
CHECK_EQ(Bytecodes::NumberOfOperands(Bytecode::kJumpLoop), 3);
|
|
CHECK_EQ(iterator.GetUnsignedImmediateOperand(0), i * 6 + 6);
|
|
iterator.Advance();
|
|
}
|
|
// Check padding to force wide backwards jumps.
|
|
for (int i = 0; i < 256; i++) {
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kDebugger);
|
|
iterator.Advance();
|
|
}
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpLoop);
|
|
CHECK_EQ(iterator.current_operand_scale(), OperandScale::kDouble);
|
|
CHECK_EQ(iterator.GetUnsignedImmediateOperand(0), 42 * 6 + 1 + 256 + 4);
|
|
iterator.Advance();
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kReturn);
|
|
iterator.Advance();
|
|
CHECK(iterator.done());
|
|
}
|
|
|
|
TEST_F(BytecodeArrayBuilderTest, SmallSwitch) {
|
|
BytecodeArrayBuilder builder(zone(), 1, 1);
|
|
|
|
// Small jump table that fits into the single-size constant pool
|
|
int small_jump_table_size = 5;
|
|
int small_jump_table_base = -2;
|
|
BytecodeJumpTable* small_jump_table =
|
|
builder.AllocateJumpTable(small_jump_table_size, small_jump_table_base);
|
|
|
|
builder.LoadLiteral(Smi::FromInt(7)).SwitchOnSmiNoFeedback(small_jump_table);
|
|
for (int i = 0; i < small_jump_table_size; i++) {
|
|
builder.Bind(small_jump_table, small_jump_table_base + i).Debugger();
|
|
}
|
|
builder.Return();
|
|
|
|
Handle<BytecodeArray> array = builder.ToBytecodeArray(isolate());
|
|
BytecodeArrayIterator iterator(array);
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kLdaSmi);
|
|
iterator.Advance();
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kSwitchOnSmiNoFeedback);
|
|
CHECK_EQ(iterator.current_operand_scale(), OperandScale::kSingle);
|
|
{
|
|
int i = 0;
|
|
int switch_end =
|
|
iterator.current_offset() + iterator.current_bytecode_size();
|
|
|
|
for (JumpTableTargetOffset entry : iterator.GetJumpTableTargetOffsets()) {
|
|
CHECK_EQ(entry.case_value, small_jump_table_base + i);
|
|
CHECK_EQ(entry.target_offset, switch_end + i);
|
|
|
|
i++;
|
|
}
|
|
CHECK_EQ(i, small_jump_table_size);
|
|
}
|
|
iterator.Advance();
|
|
|
|
for (int i = 0; i < small_jump_table_size; i++) {
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kDebugger);
|
|
iterator.Advance();
|
|
}
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kReturn);
|
|
iterator.Advance();
|
|
CHECK(iterator.done());
|
|
}
|
|
|
|
TEST_F(BytecodeArrayBuilderTest, WideSwitch) {
|
|
BytecodeArrayBuilder builder(zone(), 1, 1);
|
|
|
|
// Large jump table that requires a wide Switch bytecode.
|
|
int large_jump_table_size = 256;
|
|
int large_jump_table_base = -10;
|
|
BytecodeJumpTable* large_jump_table =
|
|
builder.AllocateJumpTable(large_jump_table_size, large_jump_table_base);
|
|
|
|
builder.LoadLiteral(Smi::FromInt(7)).SwitchOnSmiNoFeedback(large_jump_table);
|
|
for (int i = 0; i < large_jump_table_size; i++) {
|
|
builder.Bind(large_jump_table, large_jump_table_base + i).Debugger();
|
|
}
|
|
builder.Return();
|
|
|
|
Handle<BytecodeArray> array = builder.ToBytecodeArray(isolate());
|
|
BytecodeArrayIterator iterator(array);
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kLdaSmi);
|
|
iterator.Advance();
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kSwitchOnSmiNoFeedback);
|
|
CHECK_EQ(iterator.current_operand_scale(), OperandScale::kDouble);
|
|
{
|
|
int i = 0;
|
|
int switch_end =
|
|
iterator.current_offset() + iterator.current_bytecode_size();
|
|
|
|
for (JumpTableTargetOffset entry : iterator.GetJumpTableTargetOffsets()) {
|
|
CHECK_EQ(entry.case_value, large_jump_table_base + i);
|
|
CHECK_EQ(entry.target_offset, switch_end + i);
|
|
|
|
i++;
|
|
}
|
|
CHECK_EQ(i, large_jump_table_size);
|
|
}
|
|
iterator.Advance();
|
|
|
|
for (int i = 0; i < large_jump_table_size; i++) {
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kDebugger);
|
|
iterator.Advance();
|
|
}
|
|
|
|
CHECK_EQ(iterator.current_bytecode(), Bytecode::kReturn);
|
|
iterator.Advance();
|
|
CHECK(iterator.done());
|
|
}
|
|
|
|
} // namespace interpreter
|
|
} // namespace internal
|
|
} // namespace v8
|