/* * Copyright (c) 2021-2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include "assembly-parser.h" #include "libpandabase/mem/pool_manager.h" #include "libpandabase/utils/utf.h" #include "libpandafile/bytecode_emitter.h" #include "libpandafile/file.h" #include "libpandafile/file_items.h" #include "libpandafile/value.h" #include "macros.h" #include "runtime/bridge/bridge.h" #include "runtime/include/class_linker.h" #include "runtime/include/compiler_interface.h" #include "runtime/include/mem/allocator.h" #include "runtime/include/method.h" #include "runtime/include/runtime.h" #include "runtime/include/runtime_options.h" #include "runtime/include/value-inl.h" #include "runtime/interpreter/frame.h" #include "runtime/mem/gc/gc.h" #include "runtime/mem/internal_allocator.h" #include "runtime/core/core_class_linker_extension.h" #include "runtime/tests/class_linker_test_extension.h" #include "runtime/tests/interpreter/test_interpreter.h" #include "runtime/tests/interpreter/test_runtime_interface.h" #include "runtime/include/coretypes/dyn_objects.h" #include "runtime/include/hclass.h" #include "runtime/handle_base-inl.h" #include "runtime/handle_scope-inl.h" #include "runtime/include/coretypes/native_pointer.h" namespace panda::interpreter { namespace test { using DynClass = panda::coretypes::DynClass; using DynObject = panda::coretypes::DynObject; class InterpreterTest : public testing::Test { public: InterpreterTest() { RuntimeOptions options; options.SetShouldLoadBootPandaFiles(false); options.SetShouldInitializeIntrinsics(false); options.SetRunGcInPlace(true); options.SetVerifyCallStack(false); options.SetGcType("epsilon"); Runtime::Create(options); thread_ = panda::MTManagedThread::GetCurrent(); thread_->ManagedCodeBegin(); } ~InterpreterTest() { thread_->ManagedCodeEnd(); Runtime::Destroy(); } protected: panda::MTManagedThread *thread_ {nullptr}; }; auto CreateFrame(size_t nregs, Method *method, Frame *prev) { auto frame_deleter = [](Frame *frame) { RuntimeInterface::FreeFrame(frame); }; std::unique_ptr frame(RuntimeInterface::CreateFrame(nregs, method, prev), frame_deleter); return frame; } static void InitializeFrame(Frame *f) { ManagedThread::GetCurrent()->SetCurrentFrame(f); for (size_t i = 0; i < f->GetSize(); i++) { f->GetVReg(i).SetValue(static_cast(0)); f->GetVReg(i).SetTag(static_cast(0)); } } static std::unique_ptr CreateClass(panda_file::SourceLang lang) { const std::string class_name("Foo"); auto cls = std::make_unique(reinterpret_cast(class_name.data()), lang, 0, 0, AlignUp(sizeof(Class), OBJECT_POINTER_SIZE)); return cls; } static std::pair, std::unique_ptr> CreateMethod( Class *klass, uint32_t access_flags, uint32_t nargs, uint32_t nregs, uint16_t *shorty, const std::vector &bytecode) { // Create panda_file panda_file::ItemContainer container; panda_file::ClassItem *class_item = container.GetOrCreateGlobalClassItem(); class_item->SetAccessFlags(ACC_PUBLIC); panda_file::StringItem *method_name = container.GetOrCreateStringItem("test"); panda_file::PrimitiveTypeItem *ret_type = container.CreateItem(panda_file::Type::TypeId::VOID); std::vector params; panda_file::ProtoItem *proto_item = container.GetOrCreateProtoItem(ret_type, params); panda_file::MethodItem *method_item = class_item->AddMethod(method_name, proto_item, ACC_PUBLIC | ACC_STATIC, params); panda_file::CodeItem *code_item = container.CreateItem(nregs, nargs, bytecode); method_item->SetCode(code_item); panda_file::MemoryWriter mem_writer; container.Write(&mem_writer); auto data = mem_writer.GetData(); auto allocator = Runtime::GetCurrent()->GetInternalAllocator(); auto buf = allocator->AllocArray(data.size()); (void)memcpy_s(buf, data.size(), data.data(), data.size()); os::mem::ConstBytePtr ptr(reinterpret_cast(buf), data.size(), [](std::byte *buffer, size_t) noexcept { auto a = Runtime::GetCurrent()->GetInternalAllocator(); a->Free(buffer); }); auto pf = panda_file::File::OpenFromMemory(std::move(ptr)); // Create method auto method = MakePandaUnique(klass, pf.get(), method_item->GetFileId(), code_item->GetFileId(), access_flags | ACC_PUBLIC | ACC_STATIC, nargs, shorty); method->SetInterpreterEntryPoint(); return {std::move(method), std::move(pf)}; } static std::pair, std::unique_ptr> CreateMethod( Class *klass, Frame *f, const std::vector &bytecode) { return CreateMethod(klass, 0, 0, f->GetSize(), nullptr, bytecode); } static std::unique_ptr CreateClassLinker([[maybe_unused]] ManagedThread *thread) { std::vector> extensions; extensions.push_back(std::make_unique()); auto allocator = Runtime::GetCurrent()->GetInternalAllocator(); auto class_linker = std::make_unique(allocator, std::move(extensions)); if (!class_linker->Initialize()) { return nullptr; } return class_linker; } static ObjectHeader *CreateException(ManagedThread *thread) { auto class_linker = CreateClassLinker(thread); LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY); Class *cls = class_linker->GetExtension(ctx)->GetClassRoot(ClassRoot::OBJECT); ObjectHeader *exception = ObjectHeader::Create(cls); return exception; } TEST_F(InterpreterTest, TestMov) { BytecodeEmitter emitter; constexpr int64_t IMM4_MAX = 7; constexpr int64_t IMM8_MAX = std::numeric_limits::max(); constexpr int64_t IMM16_MAX = std::numeric_limits::max(); constexpr int64_t IMM32_MAX = std::numeric_limits::max(); constexpr int64_t IMM64_MAX = std::numeric_limits::max(); constexpr uint16_t V4_MAX = 15; constexpr uint16_t V8_MAX = std::numeric_limits::max(); constexpr uint16_t V16_MAX = std::numeric_limits::max(); ObjectHeader *obj1 = ToPointer(0xaabbccdd); ObjectHeader *obj2 = ToPointer(0xaabbccdd + 0x100); ObjectHeader *obj3 = ToPointer(0xaabbccdd + 0x200); emitter.Movi(0, IMM4_MAX); emitter.Movi(1, IMM8_MAX); emitter.Movi(2U, IMM16_MAX); emitter.Movi(3U, IMM32_MAX); emitter.MoviWide(4U, IMM64_MAX); emitter.Mov(V4_MAX, V4_MAX - 1); emitter.Mov(V8_MAX, V8_MAX - 1); emitter.Mov(V16_MAX, V16_MAX - 1); emitter.MovWide(V4_MAX - 2U, V4_MAX - 3U); emitter.MovWide(V16_MAX - 2U, V16_MAX - 3U); emitter.MovObj(V4_MAX - 4U, V4_MAX - 5U); emitter.MovObj(V8_MAX - 4U, V8_MAX - 5U); emitter.MovObj(V16_MAX - 4U, V16_MAX - 5U); emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(std::numeric_limits::max() + 1, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetVReg(V4_MAX - 1).SetPrimitive(IMM64_MAX - 1); f->GetVReg(V8_MAX - 1).SetPrimitive(IMM64_MAX - 2U); f->GetVReg(V16_MAX - 1).SetPrimitive(IMM64_MAX - 3U); f->GetVReg(V4_MAX - 3U).SetPrimitive(IMM64_MAX - 4U); f->GetVReg(V16_MAX - 3U).SetPrimitive(IMM64_MAX - 5U); f->GetVReg(V4_MAX - 5U).SetReference(obj1); f->GetVReg(V8_MAX - 5U).SetReference(obj2); f->GetVReg(V16_MAX - 5U).SetReference(obj3); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); // Check movi EXPECT_EQ(f->GetVReg(0).GetLong(), IMM4_MAX); EXPECT_FALSE(f->GetVReg(0).HasObject()); EXPECT_EQ(f->GetVReg(1).GetLong(), IMM8_MAX); EXPECT_FALSE(f->GetVReg(1).HasObject()); EXPECT_EQ(f->GetVReg(2U).GetLong(), IMM16_MAX); EXPECT_FALSE(f->GetVReg(2U).HasObject()); EXPECT_EQ(f->GetVReg(3U).GetLong(), IMM32_MAX); EXPECT_FALSE(f->GetVReg(3U).HasObject()); EXPECT_EQ(f->GetVReg(4U).GetLong(), IMM64_MAX); EXPECT_FALSE(f->GetVReg(4U).HasObject()); // Check mov EXPECT_EQ(f->GetVReg(V4_MAX).Get(), static_cast(IMM64_MAX - 1)); EXPECT_FALSE(f->GetVReg(V4_MAX).HasObject()); EXPECT_EQ(f->GetVReg(V8_MAX).Get(), static_cast(IMM64_MAX - 2U)); EXPECT_FALSE(f->GetVReg(V8_MAX).HasObject()); EXPECT_EQ(f->GetVReg(V16_MAX).Get(), static_cast(IMM64_MAX - 3U)); EXPECT_FALSE(f->GetVReg(V16_MAX).HasObject()); // Check mov.64 EXPECT_EQ(f->GetVReg(V4_MAX - 2U).GetLong(), IMM64_MAX - 4U); EXPECT_FALSE(f->GetVReg(V4_MAX - 2U).HasObject()); EXPECT_EQ(f->GetVReg(V16_MAX - 2U).GetLong(), IMM64_MAX - 5U); EXPECT_FALSE(f->GetVReg(V16_MAX - 2U).HasObject()); // Check mov.obj EXPECT_EQ(f->GetVReg(V4_MAX - 4U).GetReference(), obj1); EXPECT_TRUE(f->GetVReg(V4_MAX - 4U).HasObject()); EXPECT_EQ(f->GetVReg(V8_MAX - 4U).GetReference(), obj2); EXPECT_TRUE(f->GetVReg(V8_MAX - 4U).HasObject()); EXPECT_EQ(f->GetVReg(V16_MAX - 4U).GetReference(), obj3); EXPECT_TRUE(f->GetVReg(V16_MAX - 4U).HasObject()); } TEST_F(InterpreterTest, TestLoadStoreAccumulator) { BytecodeEmitter emitter; constexpr int64_t IMM8_MAX = std::numeric_limits::max(); constexpr int64_t IMM16_MAX = std::numeric_limits::max(); constexpr int64_t IMM32_MAX = std::numeric_limits::max(); constexpr int64_t IMM64_MAX = std::numeric_limits::max(); ObjectHeader *obj = ToPointer(0xaabbccdd); emitter.Ldai(IMM8_MAX); emitter.Sta(0); emitter.Ldai(IMM16_MAX); emitter.Sta(1); emitter.Ldai(IMM32_MAX); emitter.Sta(2U); emitter.LdaiWide(IMM64_MAX); emitter.StaWide(3); emitter.Lda(4); emitter.Sta(5); emitter.LdaWide(6); emitter.StaWide(7); emitter.LdaObj(8); emitter.StaObj(9); emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetVReg(4U).SetPrimitive(IMM64_MAX - 1); f->GetVReg(6U).SetPrimitive(IMM64_MAX - 2U); f->GetVReg(8U).SetReference(obj); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); EXPECT_EQ(f->GetVReg(0).Get(), static_cast(IMM8_MAX)); EXPECT_FALSE(f->GetVReg(0).HasObject()); EXPECT_EQ(f->GetVReg(1).Get(), static_cast(IMM16_MAX)); EXPECT_FALSE(f->GetVReg(1).HasObject()); EXPECT_EQ(f->GetVReg(2U).Get(), static_cast(IMM32_MAX)); EXPECT_FALSE(f->GetVReg(2U).HasObject()); EXPECT_EQ(f->GetVReg(3U).GetLong(), IMM64_MAX); EXPECT_FALSE(f->GetVReg(3U).HasObject()); EXPECT_EQ(f->GetVReg(5U).Get(), static_cast(IMM64_MAX - 1)); EXPECT_FALSE(f->GetVReg(5U).HasObject()); EXPECT_EQ(f->GetVReg(7U).GetLong(), IMM64_MAX - 2U); EXPECT_FALSE(f->GetVReg(7U).HasObject()); EXPECT_EQ(f->GetVReg(9U).GetReference(), obj); EXPECT_TRUE(f->GetVReg(9U).HasObject()); } TEST_F(InterpreterTest, TestLoadString) { BytecodeEmitter emitter; emitter.LdaStr(RuntimeInterface::STRING_ID.AsFileId().GetOffset()); emitter.ReturnObj(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); EXPECT_EQ(f->GetAcc().GetReference(), RuntimeInterface::ResolveString(thread_->GetVM(), *method, RuntimeInterface::STRING_ID)); EXPECT_TRUE(f->GetAcc().HasObject()); } void TestUnimpelemented(std::function emit) { BytecodeEmitter emitter; emit(&emitter); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); EXPECT_DEATH_IF_SUPPORTED(Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()), ""); } TEST_F(InterpreterTest, LoadType) { BytecodeEmitter emitter; pandasm::Parser p; auto source = R"( .record R {} )"; auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *thread = ManagedThread::GetCurrent(); auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *object_class = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R"), &descriptor)); ASSERT_TRUE(class_linker->InitializeClass(thread, object_class)); emitter.LdaType(RuntimeInterface::TYPE_ID.AsIndex()); emitter.ReturnObj(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); RuntimeInterface::SetupResolvedClass(object_class); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); RuntimeInterface::SetupResolvedClass(nullptr); EXPECT_EQ(coretypes::Class::FromRuntimeClass(object_class), f->GetAcc().GetReference()); } void TestFcmp(double v1, double v2, int64_t value, bool is_cmpg = false) { std::ostringstream ss; if (is_cmpg) { ss << "Test fcmpg.64"; } else { ss << "Test fcmpl.64"; } ss << ", v1 = " << v1 << ", v2 = " << v2; BytecodeEmitter emitter; if (is_cmpg) { emitter.FcmpgWide(0); } else { emitter.FcmplWide(0); } emitter.ReturnWide(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetAcc().SetPrimitive(v1); f->GetVReg(0).SetPrimitive(v2); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); EXPECT_EQ(f->GetAcc().GetLong(), value) << ss.str(); EXPECT_FALSE(f->GetAcc().HasObject()) << ss.str(); } TEST_F(InterpreterTest, TestFcmp) { TestFcmp(nan(""), 1.0, 1, true); TestFcmp(1.0, nan(""), 1, true); TestFcmp(nan(""), nan(""), 1, true); TestFcmp(1.0, 2.0, -1, true); TestFcmp(1.0, 1.0, 0, true); TestFcmp(3.0, 2.0, 1, true); TestFcmp(nan(""), 1.0, -1); TestFcmp(1.0, nan(""), -1); TestFcmp(nan(""), nan(""), -1); TestFcmp(1.0, 2.0, -1); TestFcmp(1.0, 1.0, 0); TestFcmp(3.0, 2.0, 1); } void TestConditionalJmp(const std::string &mnemonic, int64_t v1, int64_t v2, int64_t r, std::function emit) { std::ostringstream ss; ss << "Test " << mnemonic << " with v1 = " << v1 << ", v2 = " << v2; { BytecodeEmitter emitter; Label label = emitter.CreateLabel(); emit(&emitter, 0, label); emitter.MoviWide(1, -1); emitter.ReturnVoid(); emitter.Bind(label); emitter.MoviWide(1, 1); emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetAcc().SetPrimitive(v1); f->GetVReg(0).SetPrimitive(v2); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); EXPECT_EQ(f->GetVReg(1).GetLong(), r) << ss.str(); } { BytecodeEmitter emitter; Label label1 = emitter.CreateLabel(); Label label2 = emitter.CreateLabel(); emitter.Jmp(label1); emitter.Bind(label2); emitter.MoviWide(1, 1); emitter.ReturnVoid(); emitter.Bind(label1); emit(&emitter, 0, label2); emitter.MoviWide(1, -1); emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetAcc().SetPrimitive(v1); f->GetVReg(0).SetPrimitive(v2); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); EXPECT_EQ(f->GetVReg(1).GetLong(), r) << ss.str(); if (ManagedThread::GetCurrent()->GetLanguageContext().GetLanguage() != panda_file::SourceLang::ECMASCRIPT) { EXPECT_EQ(method->GetHotnessCounter(), r == 1 ? 1U : 0U) << ss.str(); } } } void TestConditionalJmpz(const std::string &mnemonic, int64_t v, int64_t r, std::function emit) { std::ostringstream ss; ss << "Test " << mnemonic << " with v = " << v; { BytecodeEmitter emitter; Label label = emitter.CreateLabel(); emit(&emitter, label); emitter.MoviWide(0, -1); emitter.ReturnVoid(); emitter.Bind(label); emitter.MoviWide(0, 1); emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetAcc().SetPrimitive(v); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); EXPECT_EQ(f->GetVReg(0).GetLong(), r) << ss.str(); } { BytecodeEmitter emitter; Label label1 = emitter.CreateLabel(); Label label2 = emitter.CreateLabel(); emitter.Jmp(label1); emitter.Bind(label2); emitter.MoviWide(0, 1); emitter.ReturnVoid(); emitter.Bind(label1); emit(&emitter, label2); emitter.MoviWide(0, -1); emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetAcc().SetPrimitive(v); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); EXPECT_EQ(f->GetVReg(0).GetLong(), r) << ss.str(); if (ManagedThread::GetCurrent()->GetLanguageContext().GetLanguage() != panda_file::SourceLang::ECMASCRIPT) { EXPECT_EQ(method->GetHotnessCounter(), r == 1 ? 1U : 0U) << ss.str(); } } } TEST_F(InterpreterTest, TestConditionalJumps) { // Test jmpz TestConditionalJmpz("jeqz", 0, 1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jeqz(label); }); TestConditionalJmpz("jeqz", 1, -1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jeqz(label); }); TestConditionalJmpz("jeqz", -1, -1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jeqz(label); }); TestConditionalJmpz("jnez", 0, -1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jnez(label); }); TestConditionalJmpz("jnez", 1, 1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jnez(label); }); TestConditionalJmpz("jnez", -1, 1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jnez(label); }); TestConditionalJmpz("jltz", -1, 1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jltz(label); }); TestConditionalJmpz("jltz", 0, -1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jltz(label); }); TestConditionalJmpz("jltz", 1, -1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jltz(label); }); TestConditionalJmpz("jgtz", 1, 1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jgtz(label); }); TestConditionalJmpz("jgtz", 0, -1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jgtz(label); }); TestConditionalJmpz("jgtz", -1, -1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jgtz(label); }); TestConditionalJmpz("jlez", -1, 1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jlez(label); }); TestConditionalJmpz("jlez", 0, 1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jlez(label); }); TestConditionalJmpz("jlez", 1, -1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jlez(label); }); TestConditionalJmpz("jgez", 1, 1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jgez(label); }); TestConditionalJmpz("jgez", 0, 1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jgez(label); }); TestConditionalJmpz("jgez", -1, -1, [](BytecodeEmitter *emitter, const Label &label) { emitter->Jgez(label); }); // Test jmp TestConditionalJmp("jeq", 2, 2, 1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jeq(reg, label); }); TestConditionalJmp("jeq", 1, 2, -1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jeq(reg, label); }); TestConditionalJmp("jeq", 2, 1, -1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jeq(reg, label); }); TestConditionalJmp("jne", 2, 2, -1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jne(reg, label); }); TestConditionalJmp("jne", 1, 2, 1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jne(reg, label); }); TestConditionalJmp("jne", 2, 1, 1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jne(reg, label); }); TestConditionalJmp("jlt", 2, 2, -1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jlt(reg, label); }); TestConditionalJmp("jlt", 1, 2, 1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jlt(reg, label); }); TestConditionalJmp("jlt", 2, 1, -1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jlt(reg, label); }); TestConditionalJmp("jgt", 2, 2, -1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jgt(reg, label); }); TestConditionalJmp("jgt", 1, 2, -1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jgt(reg, label); }); TestConditionalJmp("jgt", 2, 1, 1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jgt(reg, label); }); TestConditionalJmp("jle", 2, 2, 1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jle(reg, label); }); TestConditionalJmp("jle", 1, 2, 1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jle(reg, label); }); TestConditionalJmp("jle", 2, 1, -1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jle(reg, label); }); TestConditionalJmp("jge", 2, 2, 1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jge(reg, label); }); TestConditionalJmp("jge", 1, 2, -1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jge(reg, label); }); TestConditionalJmp("jge", 2, 1, 1, [](BytecodeEmitter *emitter, uint8_t reg, const Label &label) { emitter->Jge(reg, label); }); } template void TestBinOp2(const std::string &mnemonic, T v1, T v2, T r, std::function emit, bool is_div = false) { std::ostringstream ss; ss << "Test " << mnemonic << " with sizeof(T) = " << sizeof(T); ss << ", v1 = " << v1 << ", v2 = " << v2; BytecodeEmitter emitter; emit(&emitter, 0); emitter.ReturnWide(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); bool is_arithmetic_exception_expected = is_div && v2 == 0; if (is_arithmetic_exception_expected) { RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); } auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetAcc().SetPrimitive(v1); f->GetVReg(0).SetPrimitive(v2); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); if (is_arithmetic_exception_expected) { RuntimeInterface::SetArithmeticExceptionData({true}); thread->SetException(exception); } Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); if (is_arithmetic_exception_expected) { RuntimeInterface::SetArithmeticExceptionData({false}); auto *curr_thread = ManagedThread::GetCurrent(); ASSERT_FALSE(curr_thread->HasPendingException()) << ss.str(); ASSERT_EQ(f->GetAcc().GetReference(), exception) << ss.str(); } else { EXPECT_EQ(f->GetAcc().GetAs(), r) << ss.str(); } } TEST_F(InterpreterTest, TestBinOp2) { constexpr size_t BITWIDTH = std::numeric_limits::digits; constexpr int64_t I32_MAX = std::numeric_limits::max(); constexpr int64_t I16_MAX = std::numeric_limits::max(); TestBinOp2("add2", I32_MAX, 2, I32_MAX + 2, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Add2Wide(reg); }); TestBinOp2("add2", I16_MAX, 2, I16_MAX + 2, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Add2(reg); }); TestBinOp2("fadd2", 1.0, 2.0, 1.0 + 2.0, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Fadd2Wide(reg); }); TestBinOp2("sub2", 1, 2, 1 - 2, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Sub2Wide(reg); }); TestBinOp2("sub2", 1, 2, 1 - 2, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Sub2(reg); }); TestBinOp2("fsub2", 1.0, 2.0, 1.0 - 2.0, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Fsub2Wide(reg); }); TestBinOp2("mul2", I32_MAX, 3, I32_MAX * 3, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Mul2Wide(reg); }); TestBinOp2("mul2", I16_MAX, 3, I16_MAX * 3, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Mul2(reg); }); TestBinOp2("fmul2", 2.0, 3.0, 2.0 * 3.0, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Fmul2Wide(reg); }); TestBinOp2("fdiv2", 5.0, 2.0, 5.0 / 2.0, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Fdiv2Wide(reg); }); TestBinOp2("fmod2", 10.0, 3.3, fmod(10.0, 3.3), [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Fmod2Wide(reg); }); TestBinOp2("and2", 0xaabbccdd11223344, 0xffffffff00000000, 0xaabbccdd00000000, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->And2Wide(reg); }); TestBinOp2("or2", 0xaabbccdd, 0xffff00000000, 0xffffaabbccdd, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Or2Wide(reg); }); TestBinOp2("xor2", 0xaabbccdd11223344, 0xffffffffffffffff, 0xaabbccdd11223344 ^ 0xffffffffffffffff, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Xor2Wide(reg); }); TestBinOp2("shl2", 0xaabbccdd, 16, 0xaabbccdd0000, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Shl2Wide(reg); }); TestBinOp2("shl2", 0xaabbccdd, BITWIDTH + 16, 0xaabbccdd0000, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Shl2Wide(reg); }); TestBinOp2("shr2", 0xaabbccdd11223344, 32, 0xaabbccdd, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Shr2Wide(reg); }); TestBinOp2("shr2", 0xaabbccdd11223344, BITWIDTH + 32, 0xaabbccdd, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Shr2Wide(reg); }); TestBinOp2("ashr2", 0xaabbccdd11223344, 32, 0xffffffffaabbccdd, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Ashr2Wide(reg); }); TestBinOp2("ashr2", 0xaabbccdd11223344, BITWIDTH + 32, 0xffffffffaabbccdd, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Ashr2Wide(reg); }); TestBinOp2( "div2", 0xabbccdd11223344, 32, 0x55de66e889119a, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Div2Wide(reg); }, true); TestBinOp2( "div2", 0xabbccdd11223344, 0, 0, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Div2Wide(reg); }, true); TestBinOp2( "mod2", 0xabbccdd11223344, 32, 4, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Mod2Wide(reg); }, true); TestBinOp2( "mod2", 0xabbccdd11223344, 0, 0, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Mod2Wide(reg); }, true); TestBinOp2("and", 0xaabbccdd, 0xffff, 0xccdd, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->And2(reg); }); TestBinOp2("or", 0xaabbccdd, 0xffff, 0xaabbffff, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Or2(reg); }); TestBinOp2("xor2", 0xaabbccdd, 0xffffffff, 0xaabbccdd ^ 0xffffffff, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Xor2(reg); }); TestBinOp2("shl2", 0xaabbccdd, 16, 0xccdd0000, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Shl2(reg); }); TestBinOp2("shl2", 0xaabbccdd, BITWIDTH + 16, 0xccdd0000, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Shl2(reg); }); TestBinOp2("shr2", 0xaabbccdd, 16, 0xaabb, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Shr2(reg); }); TestBinOp2("shr2", 0xaabbccdd, BITWIDTH + 16, 0xaabb, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Shr2(reg); }); TestBinOp2("ashr2", 0xaabbccdd, 16, 0xffffaabb, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Ashr2(reg); }); TestBinOp2("ashr2", 0xaabbccdd, BITWIDTH + 16, 0xffffaabb, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Ashr2(reg); }); TestBinOp2( "div2", 0xabbccdd, 16, 0xabbccd, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Div2(reg); }, true); TestBinOp2( "div2", 0xabbccdd, 0, 0, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Div2(reg); }, true); TestBinOp2( "mod2", 0xabbccdd, 16, 0xd, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Mod2(reg); }, true); TestBinOp2( "mod2", 0xabbccdd, 0, 0, [](BytecodeEmitter *emitter, uint8_t reg) { emitter->Mod2(reg); }, true); } template void TestBinOp(const std::string &mnemonic, T v1, T v2, T r, std::function emit, bool is_div = false) { std::ostringstream ss; ss << "Test " << mnemonic << " with sizeof(T) = " << sizeof(T); ss << ", v1 = " << v1 << ", v2 = " << v2; BytecodeEmitter emitter; emit(&emitter, 0, 1); emitter.ReturnWide(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); bool is_arithmetic_exception_expected = is_div && v2 == 0; if (is_arithmetic_exception_expected) { RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); } auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetVReg(0).SetPrimitive(v1); f->GetVReg(1).SetPrimitive(v2); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); if (is_arithmetic_exception_expected) { RuntimeInterface::SetArithmeticExceptionData({true}); thread->SetException(exception); } Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); if (is_arithmetic_exception_expected) { RuntimeInterface::SetArithmeticExceptionData({false}); auto *curr_thread = ManagedThread::GetCurrent(); ASSERT_FALSE(curr_thread->HasPendingException()); ASSERT_EQ(f->GetAcc().GetReference(), exception); } else { EXPECT_EQ(f->GetAcc().GetAs(), r) << ss.str(); } } TEST_F(InterpreterTest, TestBinOp) { constexpr size_t BITWIDTH = std::numeric_limits::digits; constexpr int64_t I16_MAX = std::numeric_limits::max(); TestBinOp("add", I16_MAX, 2, I16_MAX + 2, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Add(reg1, reg2); }); TestBinOp("sub", 1, 2, 1 - 2U, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Sub(reg1, reg2); }); TestBinOp("mul", I16_MAX, 3, I16_MAX * 3, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Mul(reg1, reg2); }); TestBinOp("and", 0xaabbccdd, 0xffff, 0xccdd, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->And(reg1, reg2); }); TestBinOp("or", 0xaabbccdd, 0xffff, 0xaabbffff, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Or(reg1, reg2); }); TestBinOp("xor", 0xaabbccdd, 0xffffffff, 0xaabbccdd ^ 0xffffffff, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Xor(reg1, reg2); }); TestBinOp("shl", 0xaabbccdd, 16, 0xccdd0000, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Shl(reg1, reg2); }); TestBinOp("shl", 0xaabbccdd, BITWIDTH + 16, 0xccdd0000, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Shl(reg1, reg2); }); TestBinOp("shr", 0xaabbccdd, 16, 0xaabb, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Shr(reg1, reg2); }); TestBinOp("shr", 0xaabbccdd, BITWIDTH + 16, 0xaabb, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Shr(reg1, reg2); }); TestBinOp("ashr", 0xaabbccdd, 16, 0xffffaabb, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Ashr(reg1, reg2); }); TestBinOp("ashr", 0xaabbccdd, BITWIDTH + 16, 0xffffaabb, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Ashr(reg1, reg2); }); TestBinOp( "div", 0xabbccdd, 16, 0xabbccd, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Div(reg1, reg2); }, true); TestBinOp( "div", 0xabbccdd, 0, 0, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Div(reg1, reg2); }, true); TestBinOp( "mod", 0xabbccdd, 16, 0xd, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Mod(reg1, reg2); }, true); TestBinOp( "mod", 0xabbccdd, 0, 0, [](BytecodeEmitter *emitter, uint8_t reg1, uint8_t reg2) { emitter->Mod(reg1, reg2); }, true); } void TestBinOpImm(const std::string &mnemonic, int32_t v1, int8_t v2, int32_t r, std::function emit, bool is_div = false) { std::ostringstream ss; ss << "Test " << mnemonic << " with v1 = " << v1 << ", v2 = " << static_cast(v2); BytecodeEmitter emitter; emit(&emitter, v2); emitter.ReturnWide(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); bool is_arithmetic_exception_expected = is_div && v2 == 0; if (is_arithmetic_exception_expected) { RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); } auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetAcc().SetPrimitive(v1); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); if (is_arithmetic_exception_expected) { RuntimeInterface::SetArithmeticExceptionData({true}); thread->SetException(exception); } Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); if (is_arithmetic_exception_expected) { RuntimeInterface::SetArithmeticExceptionData({false}); auto *curr_thread = ManagedThread::GetCurrent(); ASSERT_FALSE(curr_thread->HasPendingException()) << ss.str(); ASSERT_EQ(f->GetAcc().GetReference(), exception) << ss.str(); } else { EXPECT_EQ(f->GetAcc().Get(), r) << ss.str(); } } TEST_F(InterpreterTest, TestBinOpImm) { constexpr size_t BITWIDTH = std::numeric_limits::digits; constexpr int64_t I16_MAX = std::numeric_limits::max(); TestBinOpImm("addi", I16_MAX, 2, I16_MAX + 2, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Addi(imm); }); TestBinOpImm("subi", 1, 2, 1 - 2U, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Subi(imm); }); TestBinOpImm("muli", I16_MAX, 3, I16_MAX * 3, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Muli(imm); }); TestBinOpImm("andi", 0xaabbccdd, 0xf, 0xd, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Andi(imm); }); TestBinOpImm("ori", 0xaabbccdd, 0xf, 0xaabbccdf, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Ori(imm); }); TestBinOpImm("xori", 0xaabbccdd, 0xf, 0xaabbccdd ^ 0xf, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Xori(imm); }); TestBinOpImm("shli", 0xaabbccdd, 16, 0xccdd0000, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Shli(imm); }); TestBinOpImm("shli", 0xaabbccdd, BITWIDTH + 16, 0xccdd0000, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Shli(imm); }); TestBinOpImm("shri", 0xaabbccdd, 16, 0xaabb, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Shri(imm); }); TestBinOpImm("shri", 0xaabbccdd, BITWIDTH + 16, 0xaabb, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Shri(imm); }); TestBinOpImm("ashri", 0xaabbccdd, 16, 0xffffaabb, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Ashri(imm); }); TestBinOpImm("ashri", 0xaabbccdd, BITWIDTH + 16, 0xffffaabb, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Ashri(imm); }); TestBinOpImm( "divi", 0xabbccdd, 16, 0xabbccd, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Divi(imm); }, true); TestBinOpImm( "divi", 0xabbccdd, 0, 0, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Divi(imm); }, true); TestBinOpImm( "modi", 0xabbccdd, 16, 0xd, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Modi(imm); }, true); TestBinOpImm( "modi", 0xabbccdd, 0, 0, [](BytecodeEmitter *emitter, int8_t imm) { emitter->Modi(imm); }, true); } template void TestUnaryOp(const std::string &mnemonic, T v, R r, std::function emit) { std::ostringstream ss; ss << "Test " << mnemonic << " with v = " << v; BytecodeEmitter emitter; emit(&emitter); emitter.ReturnWide(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetAcc().SetPrimitive(v); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); EXPECT_EQ(f->GetAcc().GetAs(), r) << ss.str(); } TEST_F(InterpreterTest, TestUnaryOp) { constexpr int64_t I32_MIN = std::numeric_limits::min(); constexpr int64_t I64_MIN = std::numeric_limits::min(); TestUnaryOp("neg", I64_MIN + 1, -(I64_MIN + 1), [](BytecodeEmitter *emitter) { emitter->NegWide(); }); TestUnaryOp("neg", I32_MIN + 1, -(I32_MIN + 1), [](BytecodeEmitter *emitter) { emitter->Neg(); }); TestUnaryOp("fneg", 1.0, -1.0, [](BytecodeEmitter *emitter) { emitter->FnegWide(); }); TestUnaryOp("not", 0, 0xffffffffffffffff, [](BytecodeEmitter *emitter) { emitter->NotWide(); }); TestUnaryOp("not", 0, 0xffffffff, [](BytecodeEmitter *emitter) { emitter->Not(); }); } TEST_F(InterpreterTest, TestInci) { BytecodeEmitter emitter; constexpr int32_t R0_VALUE = 2; constexpr int32_t R1_VALUE = -3; emitter.Inci(0, R0_VALUE); emitter.Inci(1, R1_VALUE); emitter.ReturnWide(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetVReg(0).SetPrimitive(-R0_VALUE); f->GetVReg(1).SetPrimitive(-R1_VALUE); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); EXPECT_EQ(f->GetVReg(0).GetAs(), 0); EXPECT_EQ(f->GetVReg(1).GetAs(), 0); } TEST_F(InterpreterTest, TestCast) { constexpr int64_t I64_MAX = std::numeric_limits::max(); constexpr int32_t I32_MAX = std::numeric_limits::max(); constexpr int64_t I64_MIN = std::numeric_limits::min(); constexpr int32_t I32_MIN = std::numeric_limits::min(); constexpr double F64_MAX = std::numeric_limits::max(); constexpr double F64_PINF = std::numeric_limits::infinity(); constexpr double F64_NINF = -F64_PINF; double f64 = 64.0; TestUnaryOp("i32toi64", I32_MAX, static_cast(I32_MAX), [](BytecodeEmitter *emitter) { emitter->I32toi64(); }); TestUnaryOp("i32tof64", I32_MAX, static_cast(I32_MAX), [](BytecodeEmitter *emitter) { emitter->I32tof64(); }); TestUnaryOp("i64toi32", I64_MAX, static_cast(I64_MAX), [](BytecodeEmitter *emitter) { emitter->I64toi32(); }); TestUnaryOp("i64tof64", I64_MAX, static_cast(I64_MAX), [](BytecodeEmitter *emitter) { emitter->I64tof64(); }); TestUnaryOp("F64toi32", F64_MAX, I32_MAX, [](BytecodeEmitter *emitter) { emitter->F64toi32(); }); TestUnaryOp("F64toi32", F64_PINF, I32_MAX, [](BytecodeEmitter *emitter) { emitter->F64toi32(); }); TestUnaryOp("F64toi32", -F64_MAX, I32_MIN, [](BytecodeEmitter *emitter) { emitter->F64toi32(); }); TestUnaryOp("F64toi32", F64_NINF, I32_MIN, [](BytecodeEmitter *emitter) { emitter->F64toi32(); }); TestUnaryOp("F64toi32", nan(""), 0, [](BytecodeEmitter *emitter) { emitter->F64toi32(); }); TestUnaryOp("F64toi32", f64, static_cast(f64), [](BytecodeEmitter *emitter) { emitter->F64toi32(); }); TestUnaryOp("F64toi64", F64_MAX, I64_MAX, [](BytecodeEmitter *emitter) { emitter->F64toi64(); }); TestUnaryOp("F64toi64", F64_PINF, I64_MAX, [](BytecodeEmitter *emitter) { emitter->F64toi64(); }); TestUnaryOp("F64toi64", -F64_MAX, I64_MIN, [](BytecodeEmitter *emitter) { emitter->F64toi64(); }); TestUnaryOp("F64toi64", F64_NINF, I64_MIN, [](BytecodeEmitter *emitter) { emitter->F64toi64(); }); TestUnaryOp("F64toi64", nan(""), 0, [](BytecodeEmitter *emitter) { emitter->F64toi64(); }); TestUnaryOp("F64toi64", f64, static_cast(f64), [](BytecodeEmitter *emitter) { emitter->F64toi64(); }); } // clang-format off template struct ArrayComponentTypeHelper { using type = std::conditional_t>>>>>>>>>>>; }; // clang-format on template using ArrayComponentTypeHelperT = typename ArrayComponentTypeHelper::type; template struct ArrayStoredTypeHelperT { using type = typename ArrayComponentTypeHelper::type; }; template <> struct ArrayStoredTypeHelperT { using type = object_pointer_type; }; template typename ArrayStoredTypeHelperT::type CastIfRef(ArrayComponentTypeHelperT value) { if constexpr (type_id == panda_file::Type::TypeId::REFERENCE) { return static_cast(reinterpret_cast(value)); } else { return value; } } coretypes::Array *AllocArray(Class *cls, [[maybe_unused]] size_t elem_size, size_t length) { return coretypes::Array::Create(cls, length); } ObjectHeader *AllocObject(Class *cls) { return ObjectHeader::Create(cls); } template static T GetStoreValue([[maybe_unused]] Class *cls) { if constexpr (std::is_same_v) { return AllocObject(cls); } return std::numeric_limits::max(); } template static T GetLoadValue([[maybe_unused]] Class *cls) { if constexpr (std::is_same_v) { return AllocObject(cls); } return std::numeric_limits::min() + 1; } PandaString GetArrayClassName(panda_file::Type::TypeId component_type_id) { PandaString descriptor; if (component_type_id == panda_file::Type::TypeId::REFERENCE) { ClassHelper::GetArrayDescriptor(utf::CStringAsMutf8("panda.Object"), 1, &descriptor); return descriptor; } ClassHelper::GetPrimitiveArrayDescriptor(panda_file::Type(component_type_id), 1, &descriptor); return descriptor; } template static void TestArray() { std::ostringstream ss; ss << "Test with component type id " << static_cast(component_type_id); using component_type = ArrayComponentTypeHelperT; using stored_type = typename ArrayStoredTypeHelperT::type; BytecodeEmitter emitter; constexpr int64_t ARRAY_LENGTH = 10; constexpr size_t STORE_IDX = ARRAY_LENGTH - 1; constexpr size_t LOAD_IDX = 0; auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr) << ss.str(); auto ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY); PandaString array_class_name = GetArrayClassName(component_type_id); Class *array_class = class_linker->GetExtension(ctx)->GetClass(utf::CStringAsMutf8(array_class_name.c_str())); Class *elem_class = array_class->GetComponentType(); const component_type STORE_VALUE = GetStoreValue(elem_class); const component_type LOAD_VALUE = GetLoadValue(elem_class); emitter.Movi(0, ARRAY_LENGTH); emitter.Newarr(1, 0, RuntimeInterface::TYPE_ID.AsIndex()); if constexpr (component_type_id == panda_file::Type::TypeId::REFERENCE) { emitter.LdaObj(4); } else if constexpr (component_type_id == panda_file::Type::TypeId::F32) { emitter.Fldai(bit_cast(STORE_VALUE)); } else if constexpr (component_type_id == panda_file::Type::TypeId::F64) { emitter.FldaiWide(bit_cast(STORE_VALUE)); } else { emitter.LdaiWide(static_cast(STORE_VALUE)); } emitter.Movi(2U, STORE_IDX); if constexpr (component_type_id != panda_file::Type::TypeId::REFERENCE) { switch (component_type_id) { case panda_file::Type::TypeId::U1: case panda_file::Type::TypeId::U8: { emitter.Starr8(1, 2U); emitter.Ldai(LOAD_IDX); emitter.Ldarru8(1); break; } case panda_file::Type::TypeId::I8: { emitter.Starr8(1, 2U); emitter.Ldai(LOAD_IDX); emitter.Ldarr8(1); break; } case panda_file::Type::TypeId::U16: { emitter.Starr16(1, 2U); emitter.Ldai(LOAD_IDX); emitter.Ldarru16(1); break; } case panda_file::Type::TypeId::I16: { emitter.Starr16(1, 2U); emitter.Ldai(LOAD_IDX); emitter.Ldarr16(1); break; } case panda_file::Type::TypeId::U32: case panda_file::Type::TypeId::I32: { emitter.Starr(1, 2U); emitter.Ldai(LOAD_IDX); emitter.Ldarr(1); break; } case panda_file::Type::TypeId::U64: case panda_file::Type::TypeId::I64: { emitter.StarrWide(1, 2U); emitter.Ldai(LOAD_IDX); emitter.LdarrWide(1); break; } case panda_file::Type::TypeId::F32: { emitter.Fstarr32(1, 2U); emitter.Ldai(LOAD_IDX); emitter.Fldarr32(1); break; } case panda_file::Type::TypeId::F64: { emitter.FstarrWide(1, 2U); emitter.Ldai(LOAD_IDX); emitter.FldarrWide(1); break; } default: { UNREACHABLE(); break; } } } else { emitter.StarrObj(1, 2U); emitter.Ldai(LOAD_IDX); emitter.LdarrObj(1); } if constexpr (component_type_id != panda_file::Type::TypeId::REFERENCE) { emitter.StaWide(3U); } else { emitter.StaObj(3U); } emitter.Lenarr(1); emitter.Return(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); if constexpr (component_type_id == panda_file::Type::TypeId::REFERENCE) { f->GetVReg(4U).SetReference(STORE_VALUE); } coretypes::Array *array = AllocArray(array_class, sizeof(stored_type), ARRAY_LENGTH); array->Set(LOAD_IDX, LOAD_VALUE); RuntimeInterface::SetupResolvedClass(array_class); RuntimeInterface::SetupArrayClass(array_class); RuntimeInterface::SetupArrayLength(ARRAY_LENGTH); RuntimeInterface::SetupArrayObject(array); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); RuntimeInterface::SetupResolvedClass(nullptr); RuntimeInterface::SetupArrayClass(nullptr); RuntimeInterface::SetupArrayObject(nullptr); ASSERT_EQ(f->GetAcc().Get(), ARRAY_LENGTH) << ss.str(); auto *result = static_cast(f->GetVReg(1).GetReference()); EXPECT_EQ(result, array) << ss.str(); EXPECT_EQ(f->GetVReg(3U).GetAs(), LOAD_VALUE) << ss.str(); std::vector data(ARRAY_LENGTH); data[LOAD_IDX] = CastIfRef(LOAD_VALUE); data[STORE_IDX] = CastIfRef(STORE_VALUE); EXPECT_THAT(data, ::testing::ElementsAreArray(reinterpret_cast(array->GetData()), ARRAY_LENGTH)) << ss.str(); } TEST_F(InterpreterTest, TestArray) { TestArray(); TestArray(); TestArray(); TestArray(); TestArray(); TestArray(); TestArray(); TestArray(); TestArray(); TestArray(); TestArray(); TestArray(); } void TestNewArrayExceptions() { // Test with negative size { BytecodeEmitter emitter; emitter.Movi(0, -1); emitter.Newarr(0, 0, RuntimeInterface::TYPE_ID.AsIndex()); emitter.Movi(0, 0); emitter.Return(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); RuntimeInterface::SetNegativeArraySizeExceptionData({true, -1}); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); Execute(thread, bytecode.data(), f.get()); RuntimeInterface::SetNegativeArraySizeExceptionData({false, 0}); ASSERT_FALSE(thread->HasPendingException()); ASSERT_EQ(f->GetAcc().GetReference(), exception); } // Test with zero size { BytecodeEmitter emitter; emitter.Movi(0, 0); emitter.Newarr(0, 0, RuntimeInterface::TYPE_ID.AsIndex()); emitter.LdaObj(0); emitter.ReturnObj(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY); Class *array_class = class_linker->GetExtension(ctx)->GetClassRoot(ClassRoot::ARRAY_U1); coretypes::Array *array = AllocArray(array_class, 1, 0); RuntimeInterface::SetupResolvedClass(array_class); RuntimeInterface::SetupArrayClass(array_class); RuntimeInterface::SetupArrayLength(0); RuntimeInterface::SetupArrayObject(array); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); RuntimeInterface::SetupResolvedClass(nullptr); RuntimeInterface::SetupArrayClass(nullptr); RuntimeInterface::SetupArrayObject(nullptr); EXPECT_EQ(array, f->GetAcc().GetReference()); } } template void TestLoadArrayExceptions() { std::ostringstream ss; ss << "Test with component type id " << static_cast(component_type_id); using component_type = ArrayComponentTypeHelperT; constexpr int32_t ARRAY_LENGTH = 10; // Test NullPointerException { BytecodeEmitter emitter; if constexpr (component_type_id != panda_file::Type::TypeId::REFERENCE) { switch (sizeof(component_type)) { case sizeof(uint8_t): { emitter.Ldarr8(0); break; } case sizeof(uint16_t): { emitter.Ldarr16(0); break; } case sizeof(uint32_t): { emitter.Ldarr(0); break; } case sizeof(uint64_t): { emitter.LdarrWide(0); break; } default: { UNREACHABLE(); break; } } } else { emitter.LdarrObj(0); } emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetVReg(0).SetReference(nullptr); f->GetAcc().SetPrimitive(-1); RuntimeInterface::SetNullPointerExceptionData({true}); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); Execute(thread, bytecode.data(), f.get()); RuntimeInterface::SetNullPointerExceptionData({false}); ASSERT_FALSE(thread->HasPendingException()) << ss.str(); ASSERT_EQ(f->GetAcc().GetReference(), exception) << ss.str(); } // Test OOB exception { BytecodeEmitter emitter; if constexpr (component_type_id != panda_file::Type::TypeId::REFERENCE) { switch (sizeof(component_type)) { case sizeof(uint8_t): { emitter.Ldarr8(0); break; } case sizeof(uint16_t): { emitter.Ldarr16(0); break; } case sizeof(uint32_t): { emitter.Ldarr(0); break; } case sizeof(uint64_t): { emitter.LdarrWide(0); break; } default: { UNREACHABLE(); break; } } } else { emitter.LdarrObj(0); } emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr) << ss.str(); PandaString array_class_name = GetArrayClassName(component_type_id); auto ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY); Class *array_class = class_linker->GetExtension(ctx)->GetClass(utf::CStringAsMutf8(array_class_name.c_str())); coretypes::Array *array = AllocArray(array_class, sizeof(component_type), ARRAY_LENGTH); f->GetVReg(0).SetReference(array); f->GetAcc().SetPrimitive(-1); RuntimeInterface::SetArrayIndexOutOfBoundsExceptionData({true, -1, ARRAY_LENGTH}); RuntimeInterface::SetupResolvedClass(array_class); RuntimeInterface::SetupArrayClass(array_class); RuntimeInterface::SetupArrayLength(ARRAY_LENGTH); RuntimeInterface::SetupArrayObject(array); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); Execute(thread, bytecode.data(), f.get()); RuntimeInterface::SetupResolvedClass(nullptr); RuntimeInterface::SetupArrayClass(nullptr); RuntimeInterface::SetupArrayObject(nullptr); RuntimeInterface::SetArrayIndexOutOfBoundsExceptionData({false, 0, 0}); ASSERT_FALSE(thread->HasPendingException()) << ss.str(); ASSERT_EQ(f->GetAcc().GetReference(), exception) << ss.str(); } { BytecodeEmitter emitter; if constexpr (component_type_id != panda_file::Type::TypeId::REFERENCE) { switch (sizeof(component_type)) { case sizeof(uint8_t): { emitter.Ldarr8(0); break; } case sizeof(uint16_t): { emitter.Ldarr16(0); break; } case sizeof(uint32_t): { emitter.Ldarr(0); break; } case sizeof(uint64_t): { emitter.LdarrWide(0); break; } default: { UNREACHABLE(); break; } } } else { emitter.LdarrObj(0); } emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr) << ss.str(); PandaString array_class_name = GetArrayClassName(component_type_id); auto ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY); Class *array_class = class_linker->GetExtension(ctx)->GetClass(utf::CStringAsMutf8(array_class_name.c_str())); coretypes::Array *array = AllocArray(array_class, sizeof(component_type), ARRAY_LENGTH); f->GetVReg(0).SetReference(array); f->GetAcc().SetPrimitive(ARRAY_LENGTH); RuntimeInterface::SetArrayIndexOutOfBoundsExceptionData({true, ARRAY_LENGTH, ARRAY_LENGTH}); RuntimeInterface::SetupResolvedClass(array_class); RuntimeInterface::SetupArrayClass(array_class); RuntimeInterface::SetupArrayLength(ARRAY_LENGTH); RuntimeInterface::SetupArrayObject(array); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); Execute(thread, bytecode.data(), f.get()); RuntimeInterface::SetupResolvedClass(nullptr); RuntimeInterface::SetupArrayClass(nullptr); RuntimeInterface::SetupArrayObject(nullptr); RuntimeInterface::SetArrayIndexOutOfBoundsExceptionData({false, 0, 0}); ASSERT_FALSE(thread->HasPendingException()) << ss.str(); ASSERT_EQ(f->GetAcc().GetReference(), exception) << ss.str(); } } template void TestStoreArrayExceptions() { std::ostringstream ss; ss << "Test with component type id " << static_cast(component_type_id); using component_type = ArrayComponentTypeHelperT; constexpr int32_t ARRAY_LENGTH = 10; auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr) << ss.str(); PandaString array_class_name = GetArrayClassName(component_type_id); auto ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY); Class *array_class = class_linker->GetExtension(ctx)->GetClass(utf::CStringAsMutf8(array_class_name.c_str())); Class *elem_class = array_class->GetComponentType(); const component_type STORE_VALUE = GetStoreValue(elem_class); // Test NullPointerException { BytecodeEmitter emitter; if constexpr (component_type_id != panda_file::Type::TypeId::REFERENCE) { switch (sizeof(component_type)) { case sizeof(uint8_t): { emitter.Starr8(0, 1); break; } case sizeof(uint16_t): { emitter.Starr16(0, 1); break; } case sizeof(uint32_t): { emitter.Starr(0, 1); break; } case sizeof(uint64_t): { emitter.StarrWide(0, 1); break; } default: { UNREACHABLE(); break; } } } else { emitter.StarrObj(0, 1); } emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); if constexpr (component_type_id != panda_file::Type::TypeId::REFERENCE) { f->GetAcc().SetPrimitive(STORE_VALUE); } else { f->GetAcc().SetReference(STORE_VALUE); } f->GetVReg(0).SetReference(nullptr); f->GetVReg(1).SetPrimitive(-1); RuntimeInterface::SetNullPointerExceptionData({true}); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); Execute(thread, bytecode.data(), f.get()); RuntimeInterface::SetNullPointerExceptionData({false}); ASSERT_FALSE(thread->HasPendingException()) << ss.str(); ASSERT_EQ(f->GetAcc().GetReference(), exception) << ss.str(); } // Test OOB exception { BytecodeEmitter emitter; if constexpr (component_type_id != panda_file::Type::TypeId::REFERENCE) { switch (sizeof(component_type)) { case sizeof(uint8_t): { emitter.Starr8(0, 1); break; } case sizeof(uint16_t): { emitter.Starr16(0, 1); break; } case sizeof(uint32_t): { emitter.Starr(0, 1); break; } case sizeof(uint64_t): { emitter.StarrWide(0, 1); break; } default: { UNREACHABLE(); break; } } } else { emitter.StarrObj(0, 1); } emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); coretypes::Array *array = AllocArray(array_class, sizeof(component_type), ARRAY_LENGTH); if constexpr (component_type_id != panda_file::Type::TypeId::REFERENCE) { f->GetAcc().SetPrimitive(STORE_VALUE); } else { f->GetAcc().SetReference(STORE_VALUE); } f->GetVReg(0).SetReference(array); f->GetVReg(1).SetPrimitive(-1); RuntimeInterface::SetArrayIndexOutOfBoundsExceptionData({true, -1, ARRAY_LENGTH}); RuntimeInterface::SetupResolvedClass(array_class); RuntimeInterface::SetupArrayClass(array_class); RuntimeInterface::SetupArrayLength(ARRAY_LENGTH); RuntimeInterface::SetupArrayObject(array); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); Execute(thread, bytecode.data(), f.get()); RuntimeInterface::SetupResolvedClass(nullptr); RuntimeInterface::SetupArrayClass(nullptr); RuntimeInterface::SetupArrayObject(nullptr); RuntimeInterface::SetArrayIndexOutOfBoundsExceptionData({false, 0, 0}); ASSERT_FALSE(thread->HasPendingException()) << ss.str(); ASSERT_EQ(f->GetAcc().GetReference(), exception) << ss.str(); } { BytecodeEmitter emitter; if constexpr (component_type_id != panda_file::Type::TypeId::REFERENCE) { switch (sizeof(component_type)) { case sizeof(uint8_t): { emitter.Starr8(0, 1); break; } case sizeof(uint16_t): { emitter.Starr16(0, 1); break; } case sizeof(uint32_t): { emitter.Starr(0, 1); break; } case sizeof(uint64_t): { emitter.StarrWide(0, 1); break; } default: { UNREACHABLE(); break; } } } else { emitter.StarrObj(0, 1); } emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); coretypes::Array *array = AllocArray(array_class, sizeof(component_type), ARRAY_LENGTH); if constexpr (component_type_id != panda_file::Type::TypeId::REFERENCE) { f->GetAcc().SetPrimitive(STORE_VALUE); } else { f->GetAcc().SetReference(STORE_VALUE); } f->GetVReg(0).SetReference(array); f->GetVReg(1).SetPrimitive(ARRAY_LENGTH); RuntimeInterface::SetArrayIndexOutOfBoundsExceptionData({true, ARRAY_LENGTH, ARRAY_LENGTH}); RuntimeInterface::SetupResolvedClass(array_class); RuntimeInterface::SetupArrayClass(array_class); RuntimeInterface::SetupArrayLength(ARRAY_LENGTH); RuntimeInterface::SetupArrayObject(array); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); Execute(thread, bytecode.data(), f.get()); RuntimeInterface::SetupResolvedClass(nullptr); RuntimeInterface::SetupArrayClass(nullptr); RuntimeInterface::SetupArrayObject(nullptr); RuntimeInterface::SetArrayIndexOutOfBoundsExceptionData({false, 0, 0}); ASSERT_FALSE(thread->HasPendingException()) << ss.str(); ASSERT_EQ(f->GetAcc().GetReference(), exception) << ss.str(); } } void TestArrayLenException() { // Test NullPointerException BytecodeEmitter emitter; emitter.Lenarr(0); emitter.Return(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetVReg(0).SetReference(nullptr); RuntimeInterface::SetNullPointerExceptionData({true}); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); Execute(thread, bytecode.data(), f.get()); RuntimeInterface::SetNullPointerExceptionData({false}); ASSERT_FALSE(thread->HasPendingException()); ASSERT_EQ(f->GetAcc().GetReference(), exception); } ObjectHeader *AllocObject(BaseClass *cls) { return ObjectHeader::Create(cls); } TEST_F(InterpreterTest, TestNewobj) { BytecodeEmitter emitter; emitter.Newobj(0, RuntimeInterface::TYPE_ID.AsIndex()); emitter.LdaObj(0); emitter.ReturnObj(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; auto source = R"( .record R {} )"; auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *thread = ManagedThread::GetCurrent(); auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *object_class = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R"), &descriptor)); ASSERT_TRUE(class_linker->InitializeClass(thread, object_class)); ObjectHeader *obj = AllocObject(object_class); RuntimeInterface::SetupResolvedClass(object_class); RuntimeInterface::SetupObjectClass(object_class); RuntimeInterface::SetupObject(obj); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); RuntimeInterface::SetupResolvedClass(nullptr); RuntimeInterface::SetupObjectClass(nullptr); RuntimeInterface::SetupObject(nullptr); EXPECT_EQ(obj, f->GetAcc().GetReference()); } TEST_F(InterpreterTest, TestInitobj) { { BytecodeEmitter emitter; emitter.InitobjShort(0, 2, RuntimeInterface::METHOD_ID.AsIndex()); emitter.ReturnObj(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; auto source = R"( .record R {} .function void R.ctor(R a0, i32 a1, i32 a2) { return.void } )"; auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *thread = ManagedThread::GetCurrent(); auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *object_class = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R"), &descriptor)); ASSERT_TRUE(class_linker->InitializeClass(thread, object_class)); Method *ctor = object_class->GetMethods().data(); ObjectHeader *obj = AllocObject(object_class); f->GetVReg(0).Set(10); f->GetVReg(2U).Set(20); bool has_errors = false; RuntimeInterface::SetupInvokeMethodHandler( [&]([[maybe_unused]] ManagedThread *t, Method *m, Value *args) -> Value { if (m != ctor) { has_errors = true; return Value(nullptr); } Span sp(args, m->GetNumArgs()); if (sp[0].GetAs() != obj) { has_errors = true; return Value(nullptr); } if (sp[1].GetAs() != f->GetVReg(0).Get()) { has_errors = true; return Value(nullptr); } if (sp[2].GetAs() != f->GetVReg(2U).Get()) { has_errors = true; return Value(nullptr); } return Value(nullptr); }); RuntimeInterface::SetupResolvedMethod(ctor); RuntimeInterface::SetupResolvedClass(object_class); RuntimeInterface::SetupObjectClass(object_class); RuntimeInterface::SetupObject(obj); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); ASSERT_FALSE(has_errors); RuntimeInterface::SetupInvokeMethodHandler({}); RuntimeInterface::SetupResolvedMethod(nullptr); RuntimeInterface::SetupResolvedClass(nullptr); RuntimeInterface::SetupObjectClass(nullptr); RuntimeInterface::SetupObject(nullptr); EXPECT_EQ(obj, f->GetAcc().GetReference()); } { BytecodeEmitter emitter; emitter.Initobj(0, 2, 3, 5, RuntimeInterface::METHOD_ID.AsIndex()); emitter.ReturnObj(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; auto source = R"( .record R {} .function void R.ctor(R a0, i32 a1, i32 a2, i32 a3, i32 a4) { return.void } )"; auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *thread = ManagedThread::GetCurrent(); auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *object_class = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R"), &descriptor)); ASSERT_TRUE(class_linker->InitializeClass(thread, object_class)); Method *ctor = object_class->GetMethods().data(); ObjectHeader *obj = AllocObject(object_class); f->GetVReg(0).Set(10); f->GetVReg(2U).Set(20); f->GetVReg(3U).Set(30); f->GetVReg(5U).Set(40); bool has_errors = false; RuntimeInterface::SetupInvokeMethodHandler( [&]([[maybe_unused]] ManagedThread *t, Method *m, Value *args) -> Value { if (m != ctor) { has_errors = true; return Value(nullptr); } Span sp(args, m->GetNumArgs()); if (sp[0].GetAs() != obj) { has_errors = true; return Value(nullptr); } if (sp[1].GetAs() != f->GetVReg(0).Get()) { has_errors = true; return Value(nullptr); } if (sp[2].GetAs() != f->GetVReg(2U).Get()) { has_errors = true; return Value(nullptr); } if (sp[3].GetAs() != f->GetVReg(3U).Get()) { has_errors = true; return Value(nullptr); } if (sp[4].GetAs() != f->GetVReg(5U).Get()) { has_errors = true; return Value(nullptr); } return Value(nullptr); }); RuntimeInterface::SetupResolvedMethod(ctor); RuntimeInterface::SetupResolvedClass(object_class); RuntimeInterface::SetupObjectClass(object_class); RuntimeInterface::SetupObject(obj); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); ASSERT_FALSE(has_errors); RuntimeInterface::SetupInvokeMethodHandler({}); RuntimeInterface::SetupResolvedMethod(nullptr); RuntimeInterface::SetupResolvedClass(nullptr); RuntimeInterface::SetupObjectClass(nullptr); RuntimeInterface::SetupObject(nullptr); EXPECT_EQ(obj, f->GetAcc().GetReference()); } { BytecodeEmitter emitter; emitter.InitobjRange(2, RuntimeInterface::METHOD_ID.AsIndex()); emitter.ReturnObj(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; auto source = R"( .record R {} .function void R.ctor(R a0, i32 a1, i32 a2, i32 a3, i32 a4, i32 a5) { return.void } )"; auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *thread = ManagedThread::GetCurrent(); auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *object_class = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R"), &descriptor)); ASSERT_TRUE(class_linker->InitializeClass(thread, object_class)); Method *ctor = object_class->GetMethods().data(); ObjectHeader *obj = AllocObject(object_class); f->GetVReg(2U).Set(10U); f->GetVReg(3U).Set(20U); f->GetVReg(4U).Set(30U); f->GetVReg(5U).Set(40U); f->GetVReg(6U).Set(50U); bool has_errors = false; RuntimeInterface::SetupInvokeMethodHandler( [&]([[maybe_unused]] ManagedThread *t, Method *m, Value *args) -> Value { if (m != ctor) { has_errors = true; return Value(nullptr); } Span sp(args, m->GetNumArgs()); if (sp[0].GetAs() != obj) { has_errors = true; return Value(nullptr); } if (sp[1].GetAs() != f->GetVReg(2U).Get()) { has_errors = true; return Value(nullptr); } if (sp[2].GetAs() != f->GetVReg(3U).Get()) { has_errors = true; return Value(nullptr); } if (sp[3].GetAs() != f->GetVReg(4U).Get()) { has_errors = true; return Value(nullptr); } if (sp[4].GetAs() != f->GetVReg(5U).Get()) { has_errors = true; return Value(nullptr); } if (sp[5].GetAs() != f->GetVReg(6U).Get()) { has_errors = true; return Value(nullptr); } return Value(nullptr); }); RuntimeInterface::SetupResolvedMethod(ctor); RuntimeInterface::SetupResolvedClass(object_class); RuntimeInterface::SetupObjectClass(object_class); RuntimeInterface::SetupObject(obj); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); ASSERT_FALSE(has_errors); RuntimeInterface::SetupInvokeMethodHandler({}); RuntimeInterface::SetupResolvedMethod(nullptr); RuntimeInterface::SetupResolvedClass(nullptr); RuntimeInterface::SetupObjectClass(nullptr); RuntimeInterface::SetupObject(nullptr); EXPECT_EQ(obj, f->GetAcc().GetReference()); } } void TestLoadStoreField(bool is_static) { BytecodeEmitter emitter; if (is_static) { emitter.Ldstatic(RuntimeInterface::FIELD_ID.AsIndex()); emitter.StaWide(1); emitter.LdaWide(2U); emitter.Ststatic(RuntimeInterface::FIELD_ID.AsIndex()); emitter.Ldstatic(RuntimeInterface::FIELD_ID.AsIndex()); } else { emitter.Ldobj(0, RuntimeInterface::FIELD_ID.AsIndex()); emitter.StaWide(1); emitter.LdaWide(2U); emitter.Stobj(0, RuntimeInterface::FIELD_ID.AsIndex()); emitter.Ldobj(0, RuntimeInterface::FIELD_ID.AsIndex()); } emitter.ReturnWide(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; std::string source; if (is_static) { source = R"( .record R { u1 sf_u1 i8 sf_i8 u8 sf_u8 i16 sf_i16 u16 sf_u16 i32 sf_i32 u32 sf_u32 i64 sf_i64 u64 sf_u64 f32 sf_f32 f64 sf_f64 } )"; } else { source = R"( .record R { u1 if_u1 i8 if_i8 u8 if_u8 i16 if_i16 u16 if_u16 i32 if_i32 u32 if_u32 i64 if_i64 u64 if_u64 f32 if_f32 f64 if_f64 } )"; } auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *object_class = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R"), &descriptor)); ASSERT_TRUE(class_linker->InitializeClass(ManagedThread::GetCurrent(), object_class)); ObjectHeader *obj = nullptr; if (!is_static) { obj = AllocObject(object_class); f->GetVReg(0).SetReference(obj); } std::vector types { panda_file::Type::TypeId::U1, panda_file::Type::TypeId::I8, panda_file::Type::TypeId::U8, panda_file::Type::TypeId::I16, panda_file::Type::TypeId::U16, panda_file::Type::TypeId::I32, panda_file::Type::TypeId::U32, panda_file::Type::TypeId::I64, panda_file::Type::TypeId::U64, panda_file::Type::TypeId::F32, panda_file::Type::TypeId::F64}; Span fields = is_static ? object_class->GetStaticFields() : object_class->GetInstanceFields(); for (size_t i = 0; i < fields.size(); i++) { Field *field = &fields[i]; std::ostringstream ss; ss << "Test field " << reinterpret_cast(field->GetName().data); constexpr float FLOAT_VALUE = 1.0; constexpr double DOUBLE_VALUE = 2.0; int64_t value = 0; switch (field->GetType().GetId()) { case panda_file::Type::TypeId::U1: { value = std::numeric_limits::max(); f->GetVReg(2U).SetPrimitive(value); break; } case panda_file::Type::TypeId::I8: { value = std::numeric_limits::min(); f->GetVReg(2U).SetPrimitive(value); break; } case panda_file::Type::TypeId::U8: { value = std::numeric_limits::max(); f->GetVReg(2U).SetPrimitive(value); break; } case panda_file::Type::TypeId::I16: { value = std::numeric_limits::min(); f->GetVReg(2U).SetPrimitive(value); break; } case panda_file::Type::TypeId::U16: { value = std::numeric_limits::max(); f->GetVReg(2U).SetPrimitive(value); break; } case panda_file::Type::TypeId::I32: { value = std::numeric_limits::min(); f->GetVReg(2U).SetPrimitive(value); break; } case panda_file::Type::TypeId::U32: { value = std::numeric_limits::max(); f->GetVReg(2U).SetPrimitive(value); break; } case panda_file::Type::TypeId::I64: { value = std::numeric_limits::min(); f->GetVReg(2U).SetPrimitive(value); break; } case panda_file::Type::TypeId::U64: { value = std::numeric_limits::max(); f->GetVReg(2U).SetPrimitive(value); break; } case panda_file::Type::TypeId::F32: { f->GetVReg(2U).SetPrimitive(FLOAT_VALUE); break; } case panda_file::Type::TypeId::F64: { f->GetVReg(2U).SetPrimitive(DOUBLE_VALUE); break; } default: { UNREACHABLE(); break; } } RuntimeInterface::SetupResolvedField(field); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); RuntimeInterface::SetupResolvedField(nullptr); switch (field->GetType().GetId()) { case panda_file::Type::TypeId::F32: { EXPECT_EQ(f->GetAcc().GetFloat(), FLOAT_VALUE) << ss.str(); break; } case panda_file::Type::TypeId::F64: { EXPECT_EQ(f->GetAcc().GetDouble(), DOUBLE_VALUE) << ss.str(); break; } default: { EXPECT_EQ(f->GetAcc().GetLong(), value) << ss.str(); break; } } EXPECT_EQ(f->GetVReg(1).GetLong(), 0) << ss.str(); } } void TestLoadStoreObjectField(bool is_static) { BytecodeEmitter emitter; std::ostringstream ss; ss << "Test load/store "; if (is_static) { ss << "static "; } ss << "object field"; if (is_static) { emitter.LdstaticObj(RuntimeInterface::FIELD_ID.AsIndex()); emitter.StaObj(1); emitter.LdaObj(2U); emitter.StstaticObj(RuntimeInterface::FIELD_ID.AsIndex()); emitter.LdstaticObj(RuntimeInterface::FIELD_ID.AsIndex()); } else { emitter.LdobjObj(0, RuntimeInterface::FIELD_ID.AsIndex()); emitter.StaObj(1); emitter.LdaObj(2U); emitter.StobjObj(0, RuntimeInterface::FIELD_ID.AsIndex()); emitter.LdobjObj(0, RuntimeInterface::FIELD_ID.AsIndex()); } emitter.ReturnObj(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS) << ss.str(); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; std::string source; if (is_static) { source = R"( .record R { R sf_ref } )"; } else { source = R"( .record R { R sf_ref } )"; } auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr) << ss.str(); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *object_class = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R"), &descriptor)); ASSERT_TRUE(class_linker->InitializeClass(ManagedThread::GetCurrent(), object_class)) << ss.str(); ObjectHeader *obj = nullptr; if (!is_static) { obj = AllocObject(object_class); f->GetVReg(0).SetReference(obj); } Span fields = is_static ? object_class->GetStaticFields() : object_class->GetInstanceFields(); Field *field = &fields[0]; ObjectHeader *ref_value = ToPointer(0xaabbccdd); f->GetVReg(2U).SetReference(ref_value); RuntimeInterface::SetupResolvedField(field); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); RuntimeInterface::SetupResolvedField(nullptr); EXPECT_EQ(f->GetAcc().GetReference(), ref_value) << ss.str(); EXPECT_EQ(f->GetVReg(1).GetReference(), nullptr) << ss.str(); } TEST_F(InterpreterTest, TestLoadStoreField) { TestLoadStoreField(false); TestLoadStoreObjectField(false); } TEST_F(InterpreterTest, TestLoadStoreStaticField) { TestLoadStoreField(true); TestLoadStoreObjectField(true); } TEST_F(InterpreterTest, TestObjectExceptions) { // Test NullPointerException { BytecodeEmitter emitter; emitter.Stobj(0, RuntimeInterface::FIELD_ID.AsIndex()); emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; auto source = R"( .record R { i32 if } )"; auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *object_class = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R"), &descriptor)); Field *field = object_class->GetInstanceFields().data(); f->GetVReg(0).SetReference(nullptr); f->GetAcc().Set(0); RuntimeInterface::SetupResolvedField(field); RuntimeInterface::SetNullPointerExceptionData({true}); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); Execute(thread, bytecode.data(), f.get()); RuntimeInterface::SetupResolvedField(nullptr); RuntimeInterface::SetNullPointerExceptionData({false}); ASSERT_FALSE(thread->HasPendingException()); ASSERT_EQ(f->GetAcc().GetReference(), exception); } { BytecodeEmitter emitter; emitter.StobjObj(0, RuntimeInterface::FIELD_ID.AsIndex()); emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; auto source = R"( .record R { R if } )"; auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *object_class = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R"), &descriptor)); Field *field = object_class->GetInstanceFields().data(); f->GetVReg(0).SetReference(nullptr); f->GetAcc().SetReference(nullptr); RuntimeInterface::SetupResolvedField(field); RuntimeInterface::SetNullPointerExceptionData({true}); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); Execute(thread, bytecode.data(), f.get()); RuntimeInterface::SetupResolvedField(nullptr); RuntimeInterface::SetNullPointerExceptionData({false}); ASSERT_FALSE(thread->HasPendingException()); ASSERT_EQ(f->GetAcc().GetReference(), exception); } { BytecodeEmitter emitter; emitter.Ldobj(0, RuntimeInterface::FIELD_ID.AsIndex()); emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; auto source = R"( .record R { i32 if } )"; auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *object_class = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R"), &descriptor)); Field *field = object_class->GetInstanceFields().data(); f->GetVReg(0).SetReference(nullptr); RuntimeInterface::SetupResolvedField(field); RuntimeInterface::SetNullPointerExceptionData({true}); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); Execute(thread, bytecode.data(), f.get()); RuntimeInterface::SetupResolvedField(nullptr); RuntimeInterface::SetNullPointerExceptionData({false}); ASSERT_FALSE(thread->HasPendingException()); ASSERT_EQ(f->GetAcc().GetReference(), exception); } { BytecodeEmitter emitter; emitter.LdobjObj(0, RuntimeInterface::FIELD_ID.AsIndex()); emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; auto source = R"( .record R { R if } )"; auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *object_class = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R"), &descriptor)); Field *field = object_class->GetInstanceFields().data(); f->GetVReg(0).SetReference(nullptr); RuntimeInterface::SetupResolvedField(field); RuntimeInterface::SetNullPointerExceptionData({true}); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); Execute(thread, bytecode.data(), f.get()); RuntimeInterface::SetupResolvedField(nullptr); RuntimeInterface::SetNullPointerExceptionData({false}); ASSERT_FALSE(thread->HasPendingException()); ASSERT_EQ(f->GetAcc().GetReference(), exception); } } TEST_F(InterpreterTest, TestArrayExceptions) { TestNewArrayExceptions(); TestLoadArrayExceptions(); TestLoadArrayExceptions(); TestLoadArrayExceptions(); TestLoadArrayExceptions(); TestLoadArrayExceptions(); TestStoreArrayExceptions(); TestStoreArrayExceptions(); TestStoreArrayExceptions(); TestStoreArrayExceptions(); TestStoreArrayExceptions(); TestArrayLenException(); } TEST_F(InterpreterTest, TestReturns) { int64_t value = 0xaabbccdd11223344; ObjectHeader *obj = ToPointer(0xaabbccdd); { BytecodeEmitter emitter; emitter.Return(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetAcc().SetPrimitive(value); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); EXPECT_EQ(f->GetAcc().Get(), static_cast(value)); EXPECT_FALSE(f->GetAcc().HasObject()); } { BytecodeEmitter emitter; emitter.ReturnWide(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetAcc().SetPrimitive(value); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); EXPECT_EQ(f->GetAcc().GetLong(), value); EXPECT_FALSE(f->GetAcc().HasObject()); } { BytecodeEmitter emitter; emitter.ReturnObj(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetAcc().SetReference(obj); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); EXPECT_EQ(f->GetAcc().GetReference(), obj); EXPECT_TRUE(f->GetAcc().HasObject()); } } TEST_F(InterpreterTest, TestCheckCast) { { BytecodeEmitter emitter; emitter.Checkcast(RuntimeInterface::TYPE_ID.AsIndex()); emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; auto source = R"( .record R {} )"; auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *thread = ManagedThread::GetCurrent(); auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *object_class = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R"), &descriptor)); ASSERT_TRUE(class_linker->InitializeClass(thread, object_class)); f->GetAcc().SetReference(nullptr); RuntimeInterface::SetupResolvedClass(object_class); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); RuntimeInterface::SetupResolvedClass(nullptr); } { BytecodeEmitter emitter; emitter.Checkcast(RuntimeInterface::TYPE_ID.AsIndex()); emitter.ReturnVoid(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; auto source = R"( .record R {} )"; auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *thread = ManagedThread::GetCurrent(); auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); auto *object_class = ext->GetClass(ClassHelper::GetArrayDescriptor(utf::CStringAsMutf8("R"), 2, &descriptor)); ASSERT_TRUE(class_linker->InitializeClass(thread, object_class)); auto *obj = AllocArray(object_class, sizeof(uint8_t), 0); f->GetAcc().SetReference(obj); auto *dst_class = class_linker->GetExtension(ctx)->GetClassRoot(ClassRoot::OBJECT); ASSERT_TRUE(class_linker->InitializeClass(thread, dst_class)); RuntimeInterface::SetupResolvedClass(dst_class); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); RuntimeInterface::SetupResolvedClass(nullptr); } } TEST_F(InterpreterTest, TestIsInstance) { { BytecodeEmitter emitter; emitter.Isinstance(RuntimeInterface::TYPE_ID.AsIndex()); emitter.Return(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; auto source = R"( .record R {} )"; auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *thread = ManagedThread::GetCurrent(); auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *object_class = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R"), &descriptor)); ASSERT_TRUE(class_linker->InitializeClass(thread, object_class)); f->GetAcc().SetReference(nullptr); RuntimeInterface::SetupResolvedClass(object_class); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); RuntimeInterface::SetupResolvedClass(nullptr); ASSERT_EQ(f->GetAcc().Get(), 0); } { BytecodeEmitter emitter; emitter.Isinstance(RuntimeInterface::TYPE_ID.AsIndex()); emitter.Return(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; auto source = R"( .record R {} )"; auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *thread = ManagedThread::GetCurrent(); auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); auto *object_class = ext->GetClass(ClassHelper::GetArrayDescriptor(utf::CStringAsMutf8("R"), 2, &descriptor)); ASSERT_TRUE(class_linker->InitializeClass(thread, object_class)); auto *obj = AllocArray(object_class, sizeof(uint8_t), 0); f->GetAcc().SetReference(obj); auto *dst_class = class_linker->GetExtension(ctx)->GetClassRoot(ClassRoot::OBJECT); ASSERT_TRUE(class_linker->InitializeClass(thread, dst_class)); RuntimeInterface::SetupResolvedClass(dst_class); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); RuntimeInterface::SetupResolvedClass(nullptr); ASSERT_EQ(f->GetAcc().Get(), 1); } } TEST_F(InterpreterTest, TestThrow) { { BytecodeEmitter emitter; emitter.Throw(1); emitter.Movi(0, 16); emitter.Return(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); f->GetVReg(1).SetReference(exception); f->GetVReg(0).SetPrimitive(0); RuntimeInterface::SetCatchBlockPcOffset(panda_file::INVALID_OFFSET); Execute(thread, bytecode.data(), f.get()); ASSERT_TRUE(thread->HasPendingException()); ASSERT_EQ(thread->GetException(), exception); ASSERT_EQ(f->GetVReg(0).Get(), 0); thread->ClearException(); } { BytecodeEmitter emitter; emitter.Throw(1); emitter.Movi(0, 16); emitter.Return(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.Movi(0, 32); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); f->GetVReg(1).SetReference(exception); f->GetVReg(0).SetPrimitive(0); Execute(thread, bytecode.data(), f.get()); ASSERT_FALSE(thread->HasPendingException()); ASSERT_EQ(f->GetAcc().GetReference(), exception); ASSERT_EQ(f->GetVReg(0).Get(), 32); } // Test NullPointerException { BytecodeEmitter emitter; emitter.Throw(1); emitter.Movi(0, 16); emitter.Return(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.Movi(0, 32); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); f->GetVReg(1).SetReference(nullptr); f->GetVReg(0).SetPrimitive(0); RuntimeInterface::SetNullPointerExceptionData({true}); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); Execute(thread, bytecode.data(), f.get()); RuntimeInterface::SetNullPointerExceptionData({false}); ASSERT_FALSE(thread->HasPendingException()); ASSERT_EQ(f->GetAcc().GetReference(), exception); ASSERT_EQ(f->GetVReg(0).Get(), 32); } } static void MakeShorty(size_t num_args, std::vector *buf) { static constexpr uint8_t I64 = static_cast(panda_file::Type::TypeId::I64); static constexpr size_t ELEM_SIZE = 4; static constexpr size_t ELEM_COUNT = std::numeric_limits::digits / ELEM_SIZE; uint16_t val = 0; uint32_t count = 1; ++num_args; // consider the return value while (num_args > 0) { if (count == ELEM_COUNT) { buf->push_back(val); val = 0; count = 0; } val |= I64 << ELEM_SIZE * count; ++count; --num_args; } if (count == ELEM_COUNT) { buf->push_back(val); val = 0; } buf->push_back(val); } template static std::pair, std::unique_ptr> CreateResolvedMethod( Class *klass, size_t vreg_num, const std::vector args, std::vector *bytecode, std::vector *shorty_buf) { BytecodeEmitter emitter; Label label = emitter.CreateLabel(); size_t start_idx = 0; if constexpr (is_dynamic) { ++start_idx; // skip function object } for (size_t i = start_idx; i < args.size(); i++) { emitter.LdaiWide(args[i]); emitter.Jne(vreg_num + i, label); } emitter.LdaiWide(1); emitter.ReturnWide(); emitter.Bind(label); emitter.LdaiWide(0); emitter.ReturnWide(); [[maybe_unused]] auto res = emitter.Build(&*bytecode); ASSERT(res == BytecodeEmitter::ErrorCode::SUCCESS); MakeShorty(args.size(), shorty_buf); return CreateMethod(klass, 0, args.size(), vreg_num, shorty_buf->data(), *bytecode); } TEST_F(InterpreterTest, TestCalls) { size_t vreg_num = 10; { BytecodeEmitter emitter; emitter.CallShort(1, 3, RuntimeInterface::METHOD_ID.AsIndex()); emitter.ReturnWide(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); std::vector args = {1, 2}; f->GetVReg(1).SetPrimitive(args[0]); f->GetVReg(3U).SetPrimitive(args[1]); auto klass = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(klass.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); std::vector shorty_buf; std::vector method_bytecode; auto resolved_method_data = CreateResolvedMethod(klass.get(), vreg_num, args, &method_bytecode, &shorty_buf); auto resolved_method = std::move(resolved_method_data.first); RuntimeInterface::SetupResolvedMethod(resolved_method.get()); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); if (ManagedThread::GetCurrent()->GetLanguageContext().GetLanguage() != panda_file::SourceLang::ECMASCRIPT) { EXPECT_EQ(resolved_method->GetHotnessCounter(), 1U); } RuntimeInterface::SetupResolvedMethod(nullptr); EXPECT_EQ(f->GetAcc().GetLong(), 1); } { BytecodeEmitter emitter; emitter.Call(1, 3, 5, 7, RuntimeInterface::METHOD_ID.AsIndex()); emitter.ReturnWide(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); std::vector args = {1, 2, 3}; f->GetVReg(1).SetPrimitive(args[0]); f->GetVReg(3U).SetPrimitive(args[1]); f->GetVReg(5U).SetPrimitive(args[2]); auto klass = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(klass.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); std::vector shorty_buf; std::vector method_bytecode; auto resolved_method_data = CreateResolvedMethod(klass.get(), vreg_num, args, &method_bytecode, &shorty_buf); auto resolved_method = std::move(resolved_method_data.first); RuntimeInterface::SetupResolvedMethod(resolved_method.get()); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); if (ManagedThread::GetCurrent()->GetLanguageContext().GetLanguage() != panda_file::SourceLang::ECMASCRIPT) { EXPECT_EQ(resolved_method->GetHotnessCounter(), 1U); } RuntimeInterface::SetupResolvedMethod(nullptr); EXPECT_EQ(f->GetAcc().GetLong(), 1); } { BytecodeEmitter emitter; emitter.CallRange(3, RuntimeInterface::METHOD_ID.AsIndex()); emitter.ReturnWide(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); std::vector args = {1, 2, 3, 4, 5, 6, 7}; for (size_t i = 0; i < args.size(); i++) { f->GetVReg(3 + i).SetPrimitive(args[i]); } auto klass = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(klass.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); std::vector shorty_buf; std::vector method_bytecode; auto resolved_method_data = CreateResolvedMethod(klass.get(), vreg_num, args, &method_bytecode, &shorty_buf); auto resolved_method = std::move(resolved_method_data.first); RuntimeInterface::SetupResolvedMethod(resolved_method.get()); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); if (ManagedThread::GetCurrent()->GetLanguageContext().GetLanguage() != panda_file::SourceLang::ECMASCRIPT) { EXPECT_EQ(resolved_method->GetHotnessCounter(), 1U); } RuntimeInterface::SetupResolvedMethod(nullptr); EXPECT_EQ(f->GetAcc().GetLong(), 1); } } void TestVirtualCallExceptions() { // Test AbstractMethodError { BytecodeEmitter emitter; emitter.CallVirtRange(0, RuntimeInterface::METHOD_ID.AsIndex()); emitter.Return(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); RuntimeInterface::SetCatchBlockPcOffset(bytecode.size()); emitter.ReturnObj(); ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); pandasm::Parser p; auto source = R"( .record A {} .function i32 A.foo(A a0) )"; auto res = p.Parse(source); auto class_pf = pandasm::AsmEmitter::Emit(res.Value()); auto class_linker = CreateClassLinker(ManagedThread::GetCurrent()); ASSERT_NE(class_linker, nullptr); class_linker->AddPandaFile(std::move(class_pf)); PandaString descriptor; auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *object_class = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("A"), &descriptor)); Method *callee = object_class->GetMethods().data(); ObjectHeader *obj = AllocObject(object_class); f->GetVReg(0).SetReference(obj); RuntimeInterface::SetupResolvedMethod(callee); RuntimeInterface::SetAbstractMethodErrorData({true, callee}); auto *thread = ManagedThread::GetCurrent(); ObjectHeader *exception = CreateException(thread); thread->SetException(exception); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); RuntimeInterface::SetAbstractMethodErrorData({false, nullptr}); RuntimeInterface::SetupResolvedMethod(nullptr); ASSERT_FALSE(thread->HasPendingException()); ASSERT_EQ(f->GetAcc().GetReference(), exception); } } TEST_F(InterpreterTest, TestVirtualCallExceptions) { TestVirtualCallExceptions(); } int64_t EntryPoint([[maybe_unused]] Method *method, int64_t a0, int64_t a1) { return 100U + a0 + a1; } TEST_F(InterpreterTest, TestCallNative) { size_t vreg_num = 10; BytecodeEmitter emitter; emitter.CallShort(1, 3, RuntimeInterface::METHOD_ID.AsIndex()); emitter.ReturnWide(); std::vector bytecode; ASSERT_EQ(emitter.Build(&bytecode), BytecodeEmitter::ErrorCode::SUCCESS); auto f = CreateFrame(16U, nullptr, nullptr); InitializeFrame(f.get()); std::vector args1 = {1, 2}; f->GetVReg(1).SetPrimitive(args1[0]); f->GetVReg(3U).SetPrimitive(args1[1]); auto cls = CreateClass(panda_file::SourceLang::PANDA_ASSEMBLY); auto method_data = CreateMethod(cls.get(), f.get(), bytecode); auto method = std::move(method_data.first); f->SetMethod(method.get()); std::vector shorty_buf; std::vector method_bytecode; auto resolved_method_data = CreateResolvedMethod(cls.get(), vreg_num, args1, &method_bytecode, &shorty_buf); auto resolved_method = std::move(resolved_method_data.first); RuntimeInterface::SetCompilerHotnessThreshold(1); resolved_method->SetCompiledEntryPoint(reinterpret_cast(EntryPoint)); RuntimeInterface::SetupResolvedMethod(resolved_method.get()); Execute(ManagedThread::GetCurrent(), bytecode.data(), f.get()); RuntimeInterface::SetupResolvedMethod(nullptr); EXPECT_EQ(f->GetAcc().GetLong(), 103); } TEST_F(InterpreterTest, ResolveCtorClass) { pandasm::Parser p; PandaStringStream ss; ss << R"( .record R1 {} .function void R1.ctor(R1 a0) { return.void } )"; constexpr size_t METHOD_COUNT = panda_file::MAX_INDEX_16; for (size_t i = 0; i < METHOD_COUNT; i++) { ss << ".function void R1.f" << i << "() {" << std::endl; ss << " call R1.f" << i << std::endl; ss << " return.void" << std::endl; ss << "}" << std::endl; } ss << R"( .record R2 {} .function R1 R2.foo() { initobj R1.ctor return.obj } )"; auto source = ss.str(); auto res = p.Parse(source.c_str()); ASSERT_TRUE(res) << res.Error().message; auto pf = pandasm::AsmEmitter::Emit(res.Value()); ASSERT_NE(pf, nullptr) << pandasm::AsmEmitter::GetLastError(); ClassLinker *class_linker = Runtime::GetCurrent()->GetClassLinker(); class_linker->AddPandaFile(std::move(pf)); PandaString descriptor; auto *ext = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); Class *klass = ext->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R2"), &descriptor)); ASSERT_NE(klass, nullptr); Method *method = klass->GetDirectMethod(utf::CStringAsMutf8("foo")); ASSERT_NE(method, nullptr); std::vector args; Value v = method->Invoke(ManagedThread::GetCurrent(), args.data()); ASSERT_FALSE(ManagedThread::GetCurrent()->HasPendingException()); auto *ret = v.GetAs(); ASSERT_NE(ret, nullptr); ASSERT_EQ(ret->ClassAddr()->GetName(), "R1"); } TEST_F(InterpreterTest, ResolveField) { pandasm::Parser p; PandaStringStream ss; ss << R"( .record R1 { i32 f } .function void R1.cctor() { ldai 10 ststatic R1.f return.void } .function i32 R1.get() { ldstatic R1.f return } )"; constexpr size_t METHOD_COUNT = panda_file::MAX_INDEX_16; for (size_t i = 0; i < METHOD_COUNT; i++) { ss << ".function void R1.f" << i << "() {" << std::endl; ss << " call R1.f" << i << std::endl; ss << " return.void" << std::endl; ss << "}" << std::endl; } ss << R"( .record R2 { i32 f } .function void R2.cctor() { ldai 20 ststatic R2.f return.void } .function i32 R2.get() { ldstatic R2.f return } )"; for (size_t i = 0; i < METHOD_COUNT; i++) { ss << ".function void R2.f" << i << "() {" << std::endl; ss << " call R2.f" << i << std::endl; ss << " return.void" << std::endl; ss << "}" << std::endl; } auto source = ss.str(); auto res = p.Parse(source.c_str()); ASSERT_TRUE(res) << res.Error().message; auto pf = pandasm::AsmEmitter::Emit(res.Value()); ASSERT_NE(pf, nullptr) << pandasm::AsmEmitter::GetLastError(); ClassLinker *class_linker = Runtime::GetCurrent()->GetClassLinker(); class_linker->AddPandaFile(std::move(pf)); auto *extension = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY); PandaString descriptor; { Class *klass = extension->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R1"), &descriptor)); ASSERT_NE(klass, nullptr); Method *method = klass->GetDirectMethod(utf::CStringAsMutf8("get")); ASSERT_NE(method, nullptr); std::vector args; Value v = method->Invoke(ManagedThread::GetCurrent(), args.data()); ASSERT_FALSE(ManagedThread::GetCurrent()->HasPendingException()); auto ret = v.GetAs(); ASSERT_EQ(ret, 10); } { Class *klass = extension->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("R2"), &descriptor)); ASSERT_NE(klass, nullptr); Method *method = klass->GetDirectMethod(utf::CStringAsMutf8("get")); ASSERT_NE(method, nullptr); std::vector args; Value v = method->Invoke(ManagedThread::GetCurrent(), args.data()); ASSERT_FALSE(ManagedThread::GetCurrent()->HasPendingException()); auto ret = v.GetAs(); ASSERT_EQ(ret, 20); } } } // namespace test } // namespace panda::interpreter