From a3a54aadad99798a26506389b8181444ff695dd3 Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Sun, 4 Aug 2024 22:37:43 +0100 Subject: [PATCH 1/7] Implement trivial method support and add LiteralReturn - trivial methods are represented as VMInvokable - are assembled instead of normal methods when we see that it's trivial - methods and blocks can become trivial - add support for checking whether a bytecode is part of a group by using callbacks - changed how blocks are invoked to rely on the invokable's invoke routine this avoids creating a frame when we don't need it Signed-off-by: Stefan Marr --- SOM.xcodeproj/project.pbxproj | 8 ++ src/compiler/BytecodeGenerator.cpp | 2 +- src/compiler/BytecodeGenerator.h | 2 +- src/compiler/MethodGenerationContext.cpp | 120 ++++++++++++++++++----- src/compiler/MethodGenerationContext.h | 25 +++-- src/compiler/Parser.cpp | 2 +- src/interpreter/Interpreter.cpp | 4 +- src/vm/IsValidObject.cpp | 17 +++- src/vm/IsValidObject.h | 1 + src/vm/Universe.cpp | 2 +- src/vm/Universe.h | 2 +- src/vmobjects/ObjectFormats.h | 4 + src/vmobjects/VMBlock.cpp | 5 +- src/vmobjects/VMBlock.h | 6 +- src/vmobjects/VMEvaluationPrimitive.cpp | 22 ++++- src/vmobjects/VMEvaluationPrimitive.h | 8 +- src/vmobjects/VMInvokable.cpp | 8 ++ src/vmobjects/VMInvokable.h | 18 +++- src/vmobjects/VMMethod.cpp | 5 +- src/vmobjects/VMMethod.h | 18 ++-- src/vmobjects/VMPrimitive.cpp | 7 ++ src/vmobjects/VMPrimitive.h | 11 ++- src/vmobjects/VMSafePrimitive.cpp | 16 ++- src/vmobjects/VMSafePrimitive.h | 18 ++-- src/vmobjects/VMTrivialMethod.cpp | 50 ++++++++++ src/vmobjects/VMTrivialMethod.h | 81 +++++++++++++++ 26 files changed, 388 insertions(+), 74 deletions(-) create mode 100644 src/vmobjects/VMTrivialMethod.cpp create mode 100644 src/vmobjects/VMTrivialMethod.h diff --git a/SOM.xcodeproj/project.pbxproj b/SOM.xcodeproj/project.pbxproj index 06af9b7b..d848d034 100644 --- a/SOM.xcodeproj/project.pbxproj +++ b/SOM.xcodeproj/project.pbxproj @@ -90,6 +90,8 @@ 0A3A3CB31A5D5476004CB03B /* PrimitiveLoader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3F52032B0FA6624C00E75857 /* PrimitiveLoader.cpp */; }; 0A5A7E912C5D45A00011C783 /* VMSafePrimitive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A5A7E902C5D45A00011C783 /* VMSafePrimitive.cpp */; }; 0A5A7E922C5D45A00011C783 /* VMSafePrimitive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A5A7E902C5D45A00011C783 /* VMSafePrimitive.cpp */; }; + 0A5A7E962C60F5BB0011C783 /* VMTrivialMethod.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A5A7E952C60F5BB0011C783 /* VMTrivialMethod.cpp */; }; + 0A5A7E972C60F5BB0011C783 /* VMTrivialMethod.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A5A7E952C60F5BB0011C783 /* VMTrivialMethod.cpp */; }; 0A67EA7519ACD43A00830E3B /* CloneObjectsTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A67EA7019ACD43A00830E3B /* CloneObjectsTest.cpp */; }; 0A67EA7619ACD43A00830E3B /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A67EA7119ACD43A00830E3B /* main.cpp */; }; 0A67EA7819ACD43A00830E3B /* WalkObjectsTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A67EA7319ACD43A00830E3B /* WalkObjectsTest.cpp */; }; @@ -225,6 +227,8 @@ 0A5A7E8F2C5D30390011C783 /* VMSafePrimitive.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VMSafePrimitive.h; sourceTree = ""; }; 0A5A7E902C5D45A00011C783 /* VMSafePrimitive.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = VMSafePrimitive.cpp; sourceTree = ""; }; 0A5A7E932C5DA9A90011C783 /* Primitives.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Primitives.h; sourceTree = ""; }; + 0A5A7E942C602E8C0011C783 /* VMTrivialMethod.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VMTrivialMethod.h; sourceTree = ""; }; + 0A5A7E952C60F5BB0011C783 /* VMTrivialMethod.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = VMTrivialMethod.cpp; sourceTree = ""; }; 0A67EA6719ACD37200830E3B /* unittests */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = unittests; sourceTree = BUILT_PRODUCTS_DIR; }; 0A67EA7019ACD43A00830E3B /* CloneObjectsTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CloneObjectsTest.cpp; path = unitTests/CloneObjectsTest.cpp; sourceTree = ""; }; 0A67EA7119ACD43A00830E3B /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = main.cpp; path = unitTests/main.cpp; sourceTree = ""; }; @@ -671,6 +675,8 @@ 3F5203580FA6624C00E75857 /* VMSymbol.h */, 0A5A7E8F2C5D30390011C783 /* VMSafePrimitive.h */, 0A5A7E902C5D45A00011C783 /* VMSafePrimitive.cpp */, + 0A5A7E942C602E8C0011C783 /* VMTrivialMethod.h */, + 0A5A7E952C60F5BB0011C783 /* VMTrivialMethod.cpp */, ); path = vmobjects; sourceTree = ""; @@ -881,6 +887,7 @@ 0A1886DD1832BCC800A2CBCA /* ClassGenerationContext.cpp in Sources */, 0A3A3C941A5D546D004CB03B /* Class.cpp in Sources */, 0A1C98672C4D340300735850 /* Symbols.cpp in Sources */, + 0A5A7E962C60F5BB0011C783 /* VMTrivialMethod.cpp in Sources */, 0A18870B1832C0E400A2CBCA /* AbstractObject.cpp in Sources */, 0AB80AD82C394806006B6419 /* Globals.cpp in Sources */, 0A3A3C991A5D546D004CB03B /* Primitive.cpp in Sources */, @@ -997,6 +1004,7 @@ 0A1C986C2C4D363A00735850 /* LogAllocation.cpp in Sources */, 0A67EA9319ACD83200830E3B /* SourcecodeCompiler.cpp in Sources */, 0A1C98682C4D340300735850 /* Symbols.cpp in Sources */, + 0A5A7E972C60F5BB0011C783 /* VMTrivialMethod.cpp in Sources */, 0AB80AD42C392B78006B6419 /* Print.cpp in Sources */, 0A3A3CA41A5D546D004CB03B /* Double.cpp in Sources */, 0A1C98772C5526AD00735850 /* DebugCopyingCollector.cpp in Sources */, diff --git a/src/compiler/BytecodeGenerator.cpp b/src/compiler/BytecodeGenerator.cpp index 2dc6eb8d..fd6efd0a 100644 --- a/src/compiler/BytecodeGenerator.cpp +++ b/src/compiler/BytecodeGenerator.cpp @@ -119,7 +119,7 @@ void EmitPUSHFIELD(MethodGenerationContext& mgenc, VMSymbol* field) { } } -void EmitPUSHBLOCK(MethodGenerationContext& mgenc, VMMethod* block) { +void EmitPUSHBLOCK(MethodGenerationContext& mgenc, VMInvokable* block) { const int8_t idx = mgenc.AddLiteralIfAbsent(block); Emit2(mgenc, BC_PUSH_BLOCK, idx, 1); } diff --git a/src/compiler/BytecodeGenerator.h b/src/compiler/BytecodeGenerator.h index bfd9bba9..d529ac10 100644 --- a/src/compiler/BytecodeGenerator.h +++ b/src/compiler/BytecodeGenerator.h @@ -44,7 +44,7 @@ void EmitDUP(MethodGenerationContext& mgenc); void EmitPUSHLOCAL(MethodGenerationContext& mgenc, long idx, int ctx); void EmitPUSHARGUMENT(MethodGenerationContext& mgenc, long idx, int ctx); void EmitPUSHFIELD(MethodGenerationContext& mgenc, VMSymbol* field); -void EmitPUSHBLOCK(MethodGenerationContext& mgenc, VMMethod* block); +void EmitPUSHBLOCK(MethodGenerationContext& mgenc, VMInvokable* block); void EmitPUSHCONSTANT(MethodGenerationContext& mgenc, vm_oop_t cst); void EmitPUSHCONSTANT(MethodGenerationContext& mgenc, uint8_t literalIndex); void EmitPUSHCONSTANTString(MethodGenerationContext& mgenc, VMString* str); diff --git a/src/compiler/MethodGenerationContext.cpp b/src/compiler/MethodGenerationContext.cpp index 2f6c2387..e0409070 100644 --- a/src/compiler/MethodGenerationContext.cpp +++ b/src/compiler/MethodGenerationContext.cpp @@ -43,6 +43,7 @@ #include "../vmobjects/VMMethod.h" #include "../vmobjects/VMPrimitive.h" #include "../vmobjects/VMSymbol.h" +#include "../vmobjects/VMTrivialMethod.h" #include "BytecodeGenerator.h" #include "ClassGenerationContext.h" #include "LexicalScope.h" @@ -51,15 +52,20 @@ MethodGenerationContext::MethodGenerationContext(ClassGenerationContext& holder, MethodGenerationContext* outer) - : holderGenc(holder), outerGenc(outer), blockMethod(outer != nullptr), - signature(nullptr), primitive(false), finished(false), - currentStackDepth(0), maxStackDepth(0), - maxContextLevel(outer == nullptr ? 0 : outer->GetMaxContextLevel() + 1) { + : holderGenc(holder), outerGenc(outer), + maxContextLevel(outer == nullptr ? 0 : outer->GetMaxContextLevel() + 1), + blockMethod(outer != nullptr), signature(nullptr), primitive(false), + finished(false), currentStackDepth(0), maxStackDepth(0) { last4Bytecodes = {BC_INVALID, BC_INVALID, BC_INVALID, BC_INVALID}; isCurrentlyInliningABlock = false; } -VMMethod* MethodGenerationContext::Assemble() { +VMInvokable* MethodGenerationContext::Assemble() { + VMTrivialMethod* trivialMethod = assembleTrivialMethod(); + if (trivialMethod != nullptr) { + return trivialMethod; + } + // create a method instance with the given number of bytecodes and literals size_t numLiterals = literals.size(); size_t numLocals = locals.size(); @@ -83,6 +89,69 @@ VMMethod* MethodGenerationContext::Assemble() { return meth; } +VMTrivialMethod* MethodGenerationContext::assembleTrivialMethod() { + if (lastBytecodeIs(0, BC_RETURN_LOCAL)) { + uint8_t pushCandidate = lastBytecodeIsOneOf(1, &IsPushConstBytecode); + if (pushCandidate != BC_INVALID) { + return assembleLiteralReturn(pushCandidate); + } + + if (lastBytecodeIs(1, BC_PUSH_GLOBAL)) { + return assembleGlobalReturn(); + } + + pushCandidate = lastBytecodeIsOneOf(1, &IsPushFieldBytecode); + if (pushCandidate != BC_INVALID) { + return assembleFieldGetter(pushCandidate); + } + } + + // because we check for return_self here, we don't consider block methods + if (lastBytecodeIs(0, BC_PUSH_SELF)) { + assert(!IsBlockMethod()); + return assembleFieldSetter(); + } + + uint8_t returnCandidate = lastBytecodeIsOneOf(0, &IsReturnFieldBytecode); + if (returnCandidate != BC_INVALID) { + return assembleFieldGetterFromReturn(returnCandidate); + } + + return nullptr; +} + +VMTrivialMethod* MethodGenerationContext::assembleLiteralReturn( + uint8_t pushCandidate) { + if (bytecode.size() != (Bytecode::GetBytecodeLength(pushCandidate) + + Bytecode::GetBytecodeLength(BC_RETURN_LOCAL))) { + return nullptr; + } + + switch (pushCandidate) { + case BC_PUSH_0: { + return MakeLiteralReturn(signature, arguments, NEW_INT(0)); + } + case BC_PUSH_1: { + return MakeLiteralReturn(signature, arguments, NEW_INT(1)); + } + case BC_PUSH_NIL: { + return MakeLiteralReturn(signature, arguments, load_ptr(nilObject)); + } + case BC_PUSH_CONSTANT_0: + case BC_PUSH_CONSTANT_1: + case BC_PUSH_CONSTANT_2: + case BC_PUSH_CONSTANT: { + if (literals.size() == 1) { + return MakeLiteralReturn(signature, arguments, literals.at(0)); + } + } + } + + GetUniverse()->ErrorExit( + "Unexpected situation when trying to create trivial method that " + "returns a literal"); +} + VMPrimitive* MethodGenerationContext::AssemblePrimitive(bool classSide) { return VMPrimitive::GetEmptyPrimitive(signature, classSide); } @@ -288,32 +357,32 @@ bool MethodGenerationContext::hasTwoLiteralBlockArguments() { * and inlining, where this is used, happens right after the block was added. * This also means, we need to remove blocks in reverse order. */ -vm_oop_t MethodGenerationContext::getLastBlockMethodAndFreeLiteral( +VMInvokable* MethodGenerationContext::getLastBlockMethodAndFreeLiteral( uint8_t blockLiteralIdx) { assert(blockLiteralIdx == literals.size() - 1); - vm_oop_t block = literals.back(); + VMInvokable* block = (VMInvokable*)literals.back(); literals.pop_back(); return block; } -vm_oop_t MethodGenerationContext::extractBlockMethodAndRemoveBytecode() { +VMInvokable* MethodGenerationContext::extractBlockMethodAndRemoveBytecode() { uint8_t blockLitIdx = bytecode.at(bytecode.size() - 1); vm_oop_t toBeInlined = getLastBlockMethodAndFreeLiteral(blockLitIdx); removeLastBytecodes(1); - return toBeInlined; + return (VMInvokable*)toBeInlined; } -std::tuple +std::tuple MethodGenerationContext::extractBlockMethodsAndRemoveBytecodes() { uint8_t block1LitIdx = bytecode.at(bytecode.size() - 3); uint8_t block2LitIdx = bytecode.at(bytecode.size() - 1); // grab the blocks' methods for inlining - vm_oop_t toBeInlined2 = getLastBlockMethodAndFreeLiteral(block2LitIdx); - vm_oop_t toBeInlined1 = getLastBlockMethodAndFreeLiteral(block1LitIdx); + VMInvokable* toBeInlined2 = getLastBlockMethodAndFreeLiteral(block2LitIdx); + VMInvokable* toBeInlined1 = getLastBlockMethodAndFreeLiteral(block1LitIdx); removeLastBytecodes(2); @@ -329,8 +398,7 @@ bool MethodGenerationContext::InlineIfTrueOrIfFalse(bool isIfTrue) { return false; } - VMMethod* toBeInlined = - static_cast(extractBlockMethodAndRemoveBytecode()); + VMInvokable* toBeInlined = extractBlockMethodAndRemoveBytecode(); size_t jumpOffsetIdxToSkipBody = EmitJumpOnBoolWithDummyOffset(*this, isIfTrue, false); @@ -358,10 +426,10 @@ bool MethodGenerationContext::InlineIfTrueFalse(bool isIfTrue) { assert(Bytecode::GetBytecodeLength(BC_PUSH_BLOCK) == 2); - std::tuple methods = + std::tuple methods = extractBlockMethodsAndRemoveBytecodes(); - VMMethod* condMethod = static_cast(std::get<0>(methods)); - VMMethod* bodyMethod = static_cast(std::get<1>(methods)); + VMInvokable* condMethod = std::get<0>(methods); + VMInvokable* bodyMethod = std::get<1>(methods); size_t jumpOffsetIdxToSkipTrueBranch = EmitJumpOnBoolWithDummyOffset(*this, isIfTrue, true); @@ -395,10 +463,10 @@ bool MethodGenerationContext::InlineWhile(Parser& parser, bool isWhileTrue) { assert(Bytecode::GetBytecodeLength(BC_PUSH_BLOCK) == 2); - std::tuple methods = + std::tuple methods = extractBlockMethodsAndRemoveBytecodes(); - VMMethod* condMethod = static_cast(std::get<0>(methods)); - VMMethod* bodyMethod = static_cast(std::get<1>(methods)); + VMInvokable* condMethod = std::get<0>(methods); + VMInvokable* bodyMethod = std::get<1>(methods); size_t loopBeginIdx = OffsetOfNextInstruction(); @@ -428,8 +496,7 @@ bool MethodGenerationContext::InlineAndOr(bool isOr) { return false; } - VMMethod* toBeInlined = - static_cast(extractBlockMethodAndRemoveBytecode()); + VMInvokable* toBeInlined = extractBlockMethodAndRemoveBytecode(); size_t jumpOffsetIdxToSkipBranch = EmitJumpOnBoolWithDummyOffset(*this, !isOr, true); @@ -460,8 +527,7 @@ bool MethodGenerationContext::InlineToDo() { return false; } - VMMethod* toBeInlined = - static_cast(extractBlockMethodAndRemoveBytecode()); + VMInvokable* toBeInlined = extractBlockMethodAndRemoveBytecode(); toBeInlined->MergeScopeInto(*this); @@ -500,15 +566,15 @@ void MethodGenerationContext::CompleteLexicalScope() { void MethodGenerationContext::MergeIntoScope(LexicalScope& scopeToBeInlined) { if (scopeToBeInlined.GetNumberOfArguments() > 1) { - inlineAsLocals(scopeToBeInlined.arguments); + InlineAsLocals(scopeToBeInlined.arguments); } if (scopeToBeInlined.GetNumberOfLocals() > 0) { - inlineAsLocals(scopeToBeInlined.locals); + InlineAsLocals(scopeToBeInlined.locals); } } -void MethodGenerationContext::inlineAsLocals(vector& vars) { +void MethodGenerationContext::InlineAsLocals(vector& vars) { for (const Variable& var : vars) { Variable freshCopy = var.CopyForInlining(this->locals.size()); if (freshCopy.IsValid()) { diff --git a/src/compiler/MethodGenerationContext.h b/src/compiler/MethodGenerationContext.h index ea022a31..ce8d65c6 100644 --- a/src/compiler/MethodGenerationContext.h +++ b/src/compiler/MethodGenerationContext.h @@ -46,7 +46,7 @@ class MethodGenerationContext { MethodGenerationContext* outer = nullptr); ~MethodGenerationContext(); - VMMethod* Assemble(); + VMInvokable* Assemble(); VMPrimitive* AssemblePrimitive(bool classSide); int8_t FindLiteralIndex(vm_oop_t lit); @@ -107,6 +107,7 @@ class MethodGenerationContext { void CompleteLexicalScope(); void MergeIntoScope(LexicalScope& scopeToBeInlined); + void InlineAsLocals(vector& vars); uint8_t GetInlinedLocalIdx(const Variable* var) const; @@ -116,6 +117,17 @@ class MethodGenerationContext { bool OptimizeDupPopPopSequence(); private: + VMTrivialMethod* assembleTrivialMethod(); + VMTrivialMethod* assembleLiteralReturn(uint8_t pushCandidate); + VMTrivialMethod* assembleGlobalReturn() { return nullptr; } + VMTrivialMethod* assembleFieldGetter(uint8_t pushCandidate) { + return nullptr; + } + VMTrivialMethod* assembleFieldSetter() { return nullptr; } + VMTrivialMethod* assembleFieldGetterFromReturn(uint8_t pushCandidate) { + return nullptr; + } + bool optimizeIncFieldPush() { // TODO: implement return false; @@ -130,17 +142,18 @@ class MethodGenerationContext { bool lastBytecodeIs(size_t indexFromEnd, uint8_t bytecode); uint8_t lastBytecodeIsOneOf(size_t indexFromEnd, uint8_t (*predicate)(uint8_t)); - + size_t getOffsetOfLastBytecode(size_t indexFromEnd); - std::tuple extractBlockMethodsAndRemoveBytecodes(); - vm_oop_t extractBlockMethodAndRemoveBytecode(); + std::tuple + extractBlockMethodsAndRemoveBytecodes(); + VMInvokable* extractBlockMethodAndRemoveBytecode(); - vm_oop_t getLastBlockMethodAndFreeLiteral(uint8_t blockLiteralIdx); + VMInvokable* getLastBlockMethodAndFreeLiteral(uint8_t blockLiteralIdx); void completeJumpsAndEmitReturningNil(Parser& parser, size_t loopBeginIdx, size_t jumpOffsetIdxToSkipLoopBody); - void inlineAsLocals(vector& vars); + void checkJumpOffset(size_t jumpOffset, uint8_t bytecode); void resetLastBytecodeBuffer(); diff --git a/src/compiler/Parser.cpp b/src/compiler/Parser.cpp index 14cdb688..bd57dd7a 100644 --- a/src/compiler/Parser.cpp +++ b/src/compiler/Parser.cpp @@ -548,7 +548,7 @@ bool Parser::primary(MethodGenerationContext& mgenc) { nestedBlock(bgenc); - VMMethod* blockMethod = bgenc.Assemble(); + VMInvokable* blockMethod = bgenc.Assemble(); EmitPUSHBLOCK(mgenc, blockMethod); break; } diff --git a/src/interpreter/Interpreter.cpp b/src/interpreter/Interpreter.cpp index cbb59d57..49b9e7e8 100644 --- a/src/interpreter/Interpreter.cpp +++ b/src/interpreter/Interpreter.cpp @@ -243,8 +243,8 @@ void Interpreter::doPushFieldWithIndex(uint8_t fieldIndex) { } void Interpreter::doPushBlock(long bytecodeIndex) { - VMMethod* blockMethod = - static_cast(method->GetConstant(bytecodeIndex)); + vm_oop_t block = method->GetConstant(bytecodeIndex); + VMInvokable* blockMethod = static_cast(block); long numOfArgs = blockMethod->GetNumberOfArguments(); diff --git a/src/vm/IsValidObject.cpp b/src/vm/IsValidObject.cpp index fd33ac45..ea009951 100644 --- a/src/vm/IsValidObject.cpp +++ b/src/vm/IsValidObject.cpp @@ -1,7 +1,9 @@ #include "IsValidObject.h" #include +#include +#include "../compiler/Variable.h" #include "../memory/Heap.h" #include "../misc/defs.h" #include "../vmobjects/AbstractObject.h" @@ -18,6 +20,7 @@ #include "../vmobjects/VMSafePrimitive.h" #include "../vmobjects/VMString.h" #include "../vmobjects/VMSymbol.h" +#include "../vmobjects/VMTrivialMethod.h" #include "Globals.h" void* vt_array; @@ -33,6 +36,7 @@ void* vt_primitive; void* vt_safe_un_primitive; void* vt_safe_bin_primitive; void* vt_safe_ter_primitive; +void* vt_literal_return; void* vt_string; void* vt_symbol; @@ -65,7 +69,7 @@ bool IsValidObject(vm_oop_t obj) { vt == vt_integer || vt == vt_method || vt == vt_object || vt == vt_primitive || vt == vt_safe_un_primitive || vt == vt_safe_bin_primitive || vt == vt_safe_ter_primitive || - vt == vt_string || vt == vt_symbol; + vt == vt_string || vt == vt_symbol || vt == vt_literal_return; if (!b) { assert(b && "Expected vtable to be one of the known ones."); return false; @@ -104,6 +108,7 @@ void set_vt_to_null() { vt_safe_un_primitive = nullptr; vt_safe_bin_primitive = nullptr; vt_safe_ter_primitive = nullptr; + vt_literal_return = nullptr; vt_string = nullptr; vt_symbol = nullptr; } @@ -117,6 +122,11 @@ bool IsVMInteger(vm_oop_t obj) { return get_vtable(AS_OBJ(obj)) == vt_integer; } +bool IsVMMethod(vm_oop_t obj) { + assert(vt_method != nullptr); + return get_vtable(AS_OBJ(obj)) == vt_method; +} + bool IsVMSymbol(vm_oop_t obj) { assert(vt_symbol != nullptr); return get_vtable(AS_OBJ(obj)) == vt_symbol; @@ -167,6 +177,11 @@ void obtain_vtables_of_known_classes(VMSymbol* someValidSymbol) { VMSafeTernaryPrimitive(someValidSymbol, TernaryPrim()); vt_safe_ter_primitive = get_vtable(sbp3); + vector v; + VMLiteralReturn* lr = new (GetHeap(), 0) + VMLiteralReturn(someValidSymbol, v, someValidSymbol); + vt_literal_return = get_vtable(lr); + VMString* str = new (GetHeap(), PADDED_SIZE(1)) VMString(0, nullptr); vt_string = get_vtable(str); diff --git a/src/vm/IsValidObject.h b/src/vm/IsValidObject.h index d6a8c5de..0c53d9b1 100644 --- a/src/vm/IsValidObject.h +++ b/src/vm/IsValidObject.h @@ -6,6 +6,7 @@ bool IsValidObject(vm_oop_t obj); bool IsVMInteger(vm_oop_t obj); +bool IsVMMethod(vm_oop_t obj); bool IsVMSymbol(vm_oop_t obj); void set_vt_to_null(); diff --git a/src/vm/Universe.cpp b/src/vm/Universe.cpp index dac70ef3..a28d9c53 100644 --- a/src/vm/Universe.cpp +++ b/src/vm/Universe.cpp @@ -698,7 +698,7 @@ VMArray* Universe::NewArrayList(std::vector& list) const { return result; } -VMBlock* Universe::NewBlock(VMMethod* method, VMFrame* context, +VMBlock* Universe::NewBlock(VMInvokable* method, VMFrame* context, long arguments) { VMBlock* result = new (GetHeap(), 0) VMBlock(method, context); result->SetClass(GetBlockClassWithArgs(arguments)); diff --git a/src/vm/Universe.h b/src/vm/Universe.h index a507f910..363b7094 100644 --- a/src/vm/Universe.h +++ b/src/vm/Universe.h @@ -71,7 +71,7 @@ class Universe { VMArray* NewArrayFromStrings(const vector&) const; VMArray* NewArrayOfSymbolsFromStrings(const vector&) const; - VMBlock* NewBlock(VMMethod*, VMFrame*, long); + VMBlock* NewBlock(VMInvokable*, VMFrame*, long); VMClass* NewClass(VMClass*) const; VMFrame* NewFrame(VMFrame*, VMMethod*) const; VMMethod* NewMethod(VMSymbol*, size_t numberOfBytecodes, diff --git a/src/vmobjects/ObjectFormats.h b/src/vmobjects/ObjectFormats.h index ae5d72ad..2d9fd3e8 100644 --- a/src/vmobjects/ObjectFormats.h +++ b/src/vmobjects/ObjectFormats.h @@ -90,6 +90,8 @@ class VMSafePrimitive; class VMSafeUnaryPrimitive; class VMSafeBinaryPrimitive; class VMSafeTernaryPrimitive; +class VMTrivialMethod; +class VMLiteralReturn; class VMString; class VMSymbol; @@ -150,6 +152,8 @@ class GCSafePrimitive : public GCInvokable { public: typedef VMSafePrimiti class GCSafeUnaryPrimitive : public GCSafePrimitive { public: typedef VMSafeUnaryPrimitive Loaded; }; class GCSafeBinaryPrimitive : public GCSafePrimitive { public: typedef VMSafeBinaryPrimitive Loaded; }; class GCSafeTernaryPrimitive : public GCSafePrimitive { public: typedef VMSafeTernaryPrimitive Loaded; }; +class GCTrivialMethod : public GCInvokable { public: typedef VMTrivialMethod Loaded; }; +class GCLiteralReturn : public GCTrivialMethod { public: typedef VMLiteralReturn Loaded; }; class GCString : public GCAbstractObject { public: typedef VMString Loaded; }; class GCSymbol : public GCString { public: typedef VMSymbol Loaded; }; // clang-format on diff --git a/src/vmobjects/VMBlock.cpp b/src/vmobjects/VMBlock.cpp index 3be3feb0..079aa2f2 100644 --- a/src/vmobjects/VMBlock.cpp +++ b/src/vmobjects/VMBlock.cpp @@ -32,12 +32,11 @@ #include "../misc/defs.h" #include "ObjectFormats.h" #include "VMEvaluationPrimitive.h" -#include "VMMethod.h" #include "VMObject.h" const int VMBlock::VMBlockNumberOfFields = 2; -VMBlock::VMBlock(VMMethod* method, VMFrame* context) +VMBlock::VMBlock(VMInvokable* method, VMFrame* context) : VMObject(VMBlockNumberOfFields, sizeof(VMBlock)), blockMethod(store_with_separate_barrier(method)), context(store_with_separate_barrier(context)) { @@ -51,7 +50,7 @@ VMBlock* VMBlock::CloneForMovingGC() const { return clone; } -VMMethod* VMBlock::GetMethod() const { +VMInvokable* VMBlock::GetMethod() const { return load_ptr(blockMethod); } diff --git a/src/vmobjects/VMBlock.h b/src/vmobjects/VMBlock.h index 0c5bf96b..c76dfedf 100644 --- a/src/vmobjects/VMBlock.h +++ b/src/vmobjects/VMBlock.h @@ -33,9 +33,9 @@ class VMBlock : public VMObject { public: typedef GCBlock Stored; - VMBlock(VMMethod* method, VMFrame* context); + VMBlock(VMInvokable* method, VMFrame* context); - VMMethod* GetMethod() const; + VMInvokable* GetMethod() const; inline VMFrame* GetContext() const { return load_ptr(context); } @@ -48,7 +48,7 @@ class VMBlock : public VMObject { private: make_testable(public); - GCMethod* blockMethod; + GCInvokable* blockMethod; GCFrame* context; private: diff --git a/src/vmobjects/VMEvaluationPrimitive.cpp b/src/vmobjects/VMEvaluationPrimitive.cpp index 59c34ede..4b9b7675 100644 --- a/src/vmobjects/VMEvaluationPrimitive.cpp +++ b/src/vmobjects/VMEvaluationPrimitive.cpp @@ -30,6 +30,7 @@ #include #include +#include "../compiler/LexicalScope.h" #include "../memory/Heap.h" #include "../misc/defs.h" #include "../primitivesCore/Routine.h" @@ -82,7 +83,7 @@ VMSymbol* VMEvaluationPrimitive::computeSignatureString(long argc) { return SymbolFor(signatureString); } -void VMEvaluationPrimitive::Invoke(Interpreter* interp, VMFrame* frame) { +VMFrame* VMEvaluationPrimitive::Invoke(Interpreter* interp, VMFrame* frame) { // Get the block (the receiver) from the stack VMBlock* block = static_cast(frame->GetStackElement(numberOfArguments - 1)); @@ -90,10 +91,15 @@ void VMEvaluationPrimitive::Invoke(Interpreter* interp, VMFrame* frame) { // Get the context of the block... VMFrame* context = block->GetContext(); - // Push a new frame and set its context to be the one specified in the block - VMFrame* NewFrame = interp->PushNewFrame(block->GetMethod()); - NewFrame->CopyArgumentsFrom(frame); - NewFrame->SetContext(context); + VMInvokable* method = block->GetMethod(); + + VMFrame* newFrame = method->Invoke(interp, frame); + + // Push set its context to be the one specified in the block + if (newFrame != nullptr) { + newFrame->SetContext(context); + } + return nullptr; } std::string VMEvaluationPrimitive::AsDebugString() const { @@ -110,3 +116,9 @@ void VMEvaluationPrimitive::MarkObjectAsInvalid() { bool VMEvaluationPrimitive::IsMarkedInvalid() const { return numberOfArguments == INVALID_INT_MARKER; } + +void VMEvaluationPrimitive::InlineInto(MethodGenerationContext&, bool) { + GetUniverse()->ErrorExit( + "VMEvaluationPrimitive::InlineInto is not supported, and should not be " + "reached"); +} diff --git a/src/vmobjects/VMEvaluationPrimitive.h b/src/vmobjects/VMEvaluationPrimitive.h index 86a934f1..20a8b17e 100644 --- a/src/vmobjects/VMEvaluationPrimitive.h +++ b/src/vmobjects/VMEvaluationPrimitive.h @@ -47,7 +47,13 @@ class VMEvaluationPrimitive : public VMInvokable { void MarkObjectAsInvalid() override; bool IsMarkedInvalid() const override; - void Invoke(Interpreter* interp, VMFrame* frm) override; + VMFrame* Invoke(Interpreter* interp, VMFrame* frm) override; + void InlineInto(MethodGenerationContext& mgenc, + bool mergeScope = true) final; + + inline size_t GetNumberOfArguments() const final { + return numberOfArguments; + } private: static VMSymbol* computeSignatureString(long argc); diff --git a/src/vmobjects/VMInvokable.cpp b/src/vmobjects/VMInvokable.cpp index 08f594f0..bb2b79c9 100644 --- a/src/vmobjects/VMInvokable.cpp +++ b/src/vmobjects/VMInvokable.cpp @@ -26,6 +26,9 @@ #include "VMInvokable.h" +#include + +#include "../vm/Universe.h" #include "ObjectFormats.h" #include "VMClass.h" #include "VMSymbol.h" @@ -52,3 +55,8 @@ VMClass* VMInvokable::GetHolder() const { void VMInvokable::SetHolder(VMClass* hld) { store_ptr(holder, hld); } + +const Variable* VMInvokable::GetArgument(size_t, size_t) { + GetUniverse()->ErrorExit( + "VMInvokable::GetArgument not supported on anything VMMethod"); +} diff --git a/src/vmobjects/VMInvokable.h b/src/vmobjects/VMInvokable.h index 2bebff66..603e2d95 100644 --- a/src/vmobjects/VMInvokable.h +++ b/src/vmobjects/VMInvokable.h @@ -30,6 +30,10 @@ #include "VMObject.h" #include "VMSymbol.h" +class MethodGenerationContext; +class Variable; +class VMFrame; + class VMInvokable : public AbstractVMObject { public: typedef GCInvokable Stored; @@ -39,7 +43,18 @@ class VMInvokable : public AbstractVMObject { int64_t GetHash() const override { return hash; } - virtual void Invoke(Interpreter*, VMFrame*) = 0; + virtual VMFrame* Invoke(Interpreter*, VMFrame*) = 0; + virtual void InlineInto(MethodGenerationContext& mgenc, + bool mergeScope = true) = 0; + virtual void MergeScopeInto( + MethodGenerationContext&) { /* NOOP for everything but VMMethods */ } + virtual void AdaptAfterOuterInlined( + uint8_t removedCtxLevel, + MethodGenerationContext& + mgencWithInlined) { /* NOOP for everything but VMMethods */ } + virtual const Variable* GetArgument(size_t, size_t); + + virtual size_t GetNumberOfArguments() const = 0; virtual bool IsPrimitive() const; VMSymbol* GetSignature() const; @@ -53,6 +68,7 @@ class VMInvokable : public AbstractVMObject { holder = (GCClass*)INVALID_GC_POINTER; } +protected: make_testable(public); int64_t hash; diff --git a/src/vmobjects/VMMethod.cpp b/src/vmobjects/VMMethod.cpp index a1777d97..64f48bfa 100644 --- a/src/vmobjects/VMMethod.cpp +++ b/src/vmobjects/VMMethod.cpp @@ -125,9 +125,10 @@ void VMMethod::SetCachedFrame(VMFrame* frame) { } #endif -void VMMethod::Invoke(Interpreter* interp, VMFrame* frame) { +VMFrame* VMMethod::Invoke(Interpreter* interp, VMFrame* frame) { VMFrame* frm = interp->PushNewFrame(this); frm->CopyArgumentsFrom(frame); + return frm; } void VMMethod::SetHolder(VMClass* hld) { @@ -322,7 +323,7 @@ void VMMethod::inlineInto(MethodGenerationContext& mgenc) { } case BC_PUSH_BLOCK: { - VMMethod* blockMethod = (VMMethod*)GetConstant(i); + VMInvokable* blockMethod = (VMInvokable*)GetConstant(i); blockMethod->AdaptAfterOuterInlined(1, mgenc); EmitPUSHBLOCK(mgenc, blockMethod); break; diff --git a/src/vmobjects/VMMethod.h b/src/vmobjects/VMMethod.h index 1027124a..f7b3eccc 100644 --- a/src/vmobjects/VMMethod.h +++ b/src/vmobjects/VMMethod.h @@ -111,7 +111,9 @@ class VMMethod : public VMInvokable { return maximumNumberOfStackElements; } - inline size_t GetNumberOfArguments() const { return numberOfArguments; } + inline size_t GetNumberOfArguments() const final { + return numberOfArguments; + } size_t GetNumberOfBytecodes() const { return bcLength; } void SetHolder(VMClass* hld) override; @@ -147,7 +149,7 @@ class VMMethod : public VMInvokable { store_ptr(indexableFields[idx], item); } - void Invoke(Interpreter* interp, VMFrame* frame) override; + VMFrame* Invoke(Interpreter* interp, VMFrame* frame) override; void MarkObjectAsInvalid() override { VMInvokable::MarkObjectAsInvalid(); @@ -160,13 +162,15 @@ class VMMethod : public VMInvokable { StdString AsDebugString() const override; - void InlineInto(MethodGenerationContext& mgenc, bool mergeScope = true); + void InlineInto(MethodGenerationContext& mgenc, + bool mergeScope = true) final; - void AdaptAfterOuterInlined(uint8_t removedCtxLevel, - MethodGenerationContext& mgencWithInlined); + void AdaptAfterOuterInlined( + uint8_t removedCtxLevel, + MethodGenerationContext& mgencWithInlined) final; - void MergeScopeInto(MethodGenerationContext& mgenc); - const Variable* GetArgument(size_t index, size_t contextLevel) { + void MergeScopeInto(MethodGenerationContext& mgenc) override; + const Variable* GetArgument(size_t index, size_t contextLevel) override { return lexicalScope->GetArgument(index, contextLevel); } diff --git a/src/vmobjects/VMPrimitive.cpp b/src/vmobjects/VMPrimitive.cpp index 40c0be24..81b82728 100644 --- a/src/vmobjects/VMPrimitive.cpp +++ b/src/vmobjects/VMPrimitive.cpp @@ -28,11 +28,13 @@ #include +#include "../compiler/LexicalScope.h" #include "../memory/Heap.h" #include "../misc/defs.h" #include "../primitivesCore/Routine.h" #include "../vm/Globals.h" // NOLINT (misc-include-cleaner) #include "../vm/Print.h" +#include "../vm/Universe.h" #include "ObjectFormats.h" #include "VMClass.h" #include "VMFrame.h" @@ -62,6 +64,11 @@ void VMPrimitive::EmptyRoutine(Interpreter*, VMFrame*) { ErrorPrint("undefined primitive called: " + sig->GetStdString() + "\n"); } +void VMPrimitive::InlineInto(MethodGenerationContext&, bool) { + GetUniverse()->ErrorExit( + "VMPrimitive::InlineInto is not supported, and should not be reached"); +} + std::string VMPrimitive::AsDebugString() const { return "Primitive(" + GetClass()->GetName()->GetStdString() + ">>#" + GetSignature()->GetStdString() + ")"; diff --git a/src/vmobjects/VMPrimitive.h b/src/vmobjects/VMPrimitive.h index 6ea9c26c..05404a94 100644 --- a/src/vmobjects/VMPrimitive.h +++ b/src/vmobjects/VMPrimitive.h @@ -27,6 +27,7 @@ */ #include "PrimitiveRoutine.h" +#include "Signature.h" #include "VMInvokable.h" #include "VMObject.h" @@ -52,10 +53,14 @@ class VMPrimitive : public VMInvokable { void SetEmpty(bool value) { empty = value; }; VMPrimitive* CloneForMovingGC() const override; - void Invoke(Interpreter* interp, VMFrame* frm) override { + VMFrame* Invoke(Interpreter* interp, VMFrame* frm) override { routine->Invoke(interp, frm); + return nullptr; }; + void InlineInto(MethodGenerationContext& mgenc, + bool mergeScope = true) final; + bool IsPrimitive() const override { return true; }; void MarkObjectAsInvalid() override { @@ -68,6 +73,10 @@ class VMPrimitive : public VMInvokable { StdString AsDebugString() const override; + inline size_t GetNumberOfArguments() const final { + return Signature::GetNumberOfArguments(load_ptr(signature)); + } + private: void EmptyRoutine(Interpreter*, VMFrame*); diff --git a/src/vmobjects/VMSafePrimitive.cpp b/src/vmobjects/VMSafePrimitive.cpp index cf0d4441..32092819 100644 --- a/src/vmobjects/VMSafePrimitive.cpp +++ b/src/vmobjects/VMSafePrimitive.cpp @@ -2,9 +2,11 @@ #include +#include "../compiler/LexicalScope.h" #include "../memory/Heap.h" #include "../misc/defs.h" #include "../primitivesCore/Primitives.h" +#include "../vm/Universe.h" #include "AbstractObject.h" #include "ObjectFormats.h" #include "VMClass.h" @@ -17,10 +19,11 @@ VMSafePrimitive* VMSafePrimitive::GetSafeUnary(VMSymbol* sig, UnaryPrim prim) { return p; } -void VMSafeUnaryPrimitive::Invoke(Interpreter*, VMFrame* frame) { +VMFrame* VMSafeUnaryPrimitive::Invoke(Interpreter*, VMFrame* frame) { vm_oop_t receiverObj = frame->Pop(); frame->Push(prim.pointer(receiverObj)); + return nullptr; } VMSafePrimitive* VMSafePrimitive::GetSafeBinary(VMSymbol* sig, @@ -30,11 +33,12 @@ VMSafePrimitive* VMSafePrimitive::GetSafeBinary(VMSymbol* sig, return p; } -void VMSafeBinaryPrimitive::Invoke(Interpreter*, VMFrame* frame) { +VMFrame* VMSafeBinaryPrimitive::Invoke(Interpreter*, VMFrame* frame) { vm_oop_t rightObj = frame->Pop(); vm_oop_t leftObj = frame->Pop(); frame->Push(prim.pointer(leftObj, rightObj)); + return nullptr; } VMSafePrimitive* VMSafePrimitive::GetSafeTernary(VMSymbol* sig, @@ -44,12 +48,13 @@ VMSafePrimitive* VMSafePrimitive::GetSafeTernary(VMSymbol* sig, return p; } -void VMSafeTernaryPrimitive::Invoke(Interpreter*, VMFrame* frame) { +VMFrame* VMSafeTernaryPrimitive::Invoke(Interpreter*, VMFrame* frame) { vm_oop_t arg2 = frame->Pop(); vm_oop_t arg1 = frame->Pop(); vm_oop_t self = frame->Pop(); frame->Push(prim.pointer(self, arg1, arg2)); + return nullptr; } std::string VMSafePrimitive::AsDebugString() const { @@ -74,3 +79,8 @@ AbstractVMObject* VMSafeTernaryPrimitive::CloneForMovingGC() const { new (GetHeap(), 0 ALLOC_MATURE) VMSafeTernaryPrimitive(*this); return prim; } + +void VMSafePrimitive::InlineInto(MethodGenerationContext&, bool) { + GetUniverse()->ErrorExit( + "VMPrimitive::InlineInto is not supported, and should not be reached"); +} diff --git a/src/vmobjects/VMSafePrimitive.h b/src/vmobjects/VMSafePrimitive.h index f77d5bb1..cb0d8384 100644 --- a/src/vmobjects/VMSafePrimitive.h +++ b/src/vmobjects/VMSafePrimitive.h @@ -1,6 +1,7 @@ #pragma once #include "../primitivesCore/PrimitiveContainer.h" +#include "Signature.h" class VMSafePrimitive : public VMInvokable { public: @@ -10,17 +11,20 @@ class VMSafePrimitive : public VMInvokable { VMClass* GetClass() const final { return load_ptr(primitiveClass); } - inline size_t GetObjectSize() const override { - return sizeof(VMSafePrimitive); - } - bool IsPrimitive() const final { return true; }; + void InlineInto(MethodGenerationContext& mgenc, + bool mergeScope = true) final; + static VMSafePrimitive* GetSafeUnary(VMSymbol* sig, UnaryPrim prim); static VMSafePrimitive* GetSafeBinary(VMSymbol* sig, BinaryPrim prim); static VMSafePrimitive* GetSafeTernary(VMSymbol* sig, TernaryPrim prim); std::string AsDebugString() const final; + + inline size_t GetNumberOfArguments() const final { + return Signature::GetNumberOfArguments(load_ptr(signature)); + } }; class VMSafeUnaryPrimitive : public VMSafePrimitive { @@ -36,7 +40,7 @@ class VMSafeUnaryPrimitive : public VMSafePrimitive { return sizeof(VMSafeUnaryPrimitive); } - void Invoke(Interpreter*, VMFrame*) override; + VMFrame* Invoke(Interpreter*, VMFrame*) override; AbstractVMObject* CloneForMovingGC() const final; @@ -64,7 +68,7 @@ class VMSafeBinaryPrimitive : public VMSafePrimitive { return sizeof(VMSafeBinaryPrimitive); } - void Invoke(Interpreter*, VMFrame*) override; + VMFrame* Invoke(Interpreter*, VMFrame*) override; AbstractVMObject* CloneForMovingGC() const final; @@ -92,7 +96,7 @@ class VMSafeTernaryPrimitive : public VMSafePrimitive { return sizeof(VMSafeTernaryPrimitive); } - void Invoke(Interpreter*, VMFrame*) override; + VMFrame* Invoke(Interpreter*, VMFrame*) override; AbstractVMObject* CloneForMovingGC() const final; diff --git a/src/vmobjects/VMTrivialMethod.cpp b/src/vmobjects/VMTrivialMethod.cpp new file mode 100644 index 00000000..3919a437 --- /dev/null +++ b/src/vmobjects/VMTrivialMethod.cpp @@ -0,0 +1,50 @@ +#include "VMTrivialMethod.h" + +#include +#include + +#include "../compiler/BytecodeGenerator.h" +#include "../compiler/MethodGenerationContext.h" +#include "../compiler/Variable.h" +#include "../memory/Heap.h" +#include "../misc/defs.h" +#include "../vm/LogAllocation.h" +#include "AbstractObject.h" +#include "ObjectFormats.h" +#include "VMFrame.h" + +VMTrivialMethod* MakeLiteralReturn(VMSymbol* sig, vector& arguments, + vm_oop_t literal) { + VMLiteralReturn* result = + new (GetHeap(), 0) VMLiteralReturn(sig, arguments, literal); + LOG_ALLOCATION("VMLiteralReturn", result->GetObjectSize()); + return result; +} + +VMFrame* VMLiteralReturn::Invoke(Interpreter*, VMFrame* frame) { + for (int i = 0; i < numberOfArguments; i += 1) { + frame->Pop(); + } + frame->Push(load_ptr(literal)); + return nullptr; +} + +AbstractVMObject* VMLiteralReturn::CloneForMovingGC() const { + VMLiteralReturn* prim = + new (GetHeap(), 0 ALLOC_MATURE) VMLiteralReturn(*this); + return prim; +} + +std::string VMLiteralReturn::AsDebugString() const { + return "VMLiteralReturn(" + AS_OBJ(load_ptr(literal))->AsDebugString() + + ")"; +} + +void VMLiteralReturn::WalkObjects(walk_heap_fn walk) { + VMInvokable::WalkObjects(walk); + literal = walk(literal); +} + +void VMLiteralReturn::InlineInto(MethodGenerationContext& mgenc, bool) { + EmitPUSHCONSTANT(mgenc, load_ptr(literal)); +} diff --git a/src/vmobjects/VMTrivialMethod.h b/src/vmobjects/VMTrivialMethod.h new file mode 100644 index 00000000..b07e634d --- /dev/null +++ b/src/vmobjects/VMTrivialMethod.h @@ -0,0 +1,81 @@ +#pragma once + +#include "../compiler/MethodGenerationContext.h" +#include "../vm/Globals.h" +#include "Signature.h" +#include "VMInvokable.h" + +class VMTrivialMethod : public VMInvokable { +public: + typedef GCTrivialMethod Stored; + + VMTrivialMethod(VMSymbol* sig, vector& arguments) + : VMInvokable(sig), arguments(arguments) {} + + VMClass* GetClass() const final { return load_ptr(methodClass); } + + bool IsPrimitive() const final { return false; }; + + void MergeScopeInto(MethodGenerationContext& mgenc) final { + if (arguments.size() > 0) { + mgenc.InlineAsLocals(arguments); + } + } + + const Variable* GetArgument(size_t index, size_t contextLevel) final { + assert(contextLevel == 0); + if (contextLevel > 0) { + return nullptr; + } + return &arguments.at(index); + } + + inline size_t GetNumberOfArguments() const final { + return Signature::GetNumberOfArguments(load_ptr(signature)); + } + +private: + vector arguments; +}; + +VMTrivialMethod* MakeLiteralReturn(VMSymbol* sig, vector& arguments, + vm_oop_t literal); + +class VMLiteralReturn : public VMTrivialMethod { +public: + typedef GCLiteralReturn Stored; + + VMLiteralReturn(VMSymbol* sig, vector& arguments, + vm_oop_t literal) + : VMTrivialMethod(sig, arguments), + literal(store_with_separate_barrier(literal)), + numberOfArguments(Signature::GetNumberOfArguments(sig)) { + write_barrier(this, sig); + write_barrier(this, literal); + } + + inline size_t GetObjectSize() const override { + return sizeof(VMLiteralReturn); + } + + VMFrame* Invoke(Interpreter*, VMFrame*) override; + void InlineInto(MethodGenerationContext& mgenc, + bool mergeScope = true) final; + + AbstractVMObject* CloneForMovingGC() const final; + + void MarkObjectAsInvalid() final { + VMTrivialMethod::MarkObjectAsInvalid(); + literal = INVALID_GC_POINTER; + } + + void WalkObjects(walk_heap_fn) override; + + bool IsMarkedInvalid() const final { return literal == INVALID_GC_POINTER; } + + std::string AsDebugString() const final; + +private: + gc_oop_t literal; + int numberOfArguments; +}; From 0e13eb8f29b9d874bc05726bf6f62142ed9401d5 Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Mon, 5 Aug 2024 22:28:25 +0100 Subject: [PATCH 2/7] Port some more tests, but not trivial method related Signed-off-by: Stefan Marr --- src/unitTests/BytecodeGenerationTest.cpp | 155 +++++++++-------------- src/unitTests/BytecodeGenerationTest.h | 10 ++ 2 files changed, 70 insertions(+), 95 deletions(-) diff --git a/src/unitTests/BytecodeGenerationTest.cpp b/src/unitTests/BytecodeGenerationTest.cpp index 68a28ac4..08b97e06 100644 --- a/src/unitTests/BytecodeGenerationTest.cpp +++ b/src/unitTests/BytecodeGenerationTest.cpp @@ -682,6 +682,66 @@ void BytecodeGenerationTest::testInliningOfToDo() { BC_RETURN_SELF}); } +void BytecodeGenerationTest::testIfArg() { + ifArg("ifTrue:", BC_JUMP_ON_FALSE_TOP_NIL); + ifArg("ifFalse:", BC_JUMP_ON_TRUE_TOP_NIL); +} + +void BytecodeGenerationTest::ifArg(std::string selector, int8_t jumpBytecode) { + std::string source = R"""( test: arg = ( + #start. + self method IF_SELECTOR [ arg ]. + #end + ) )"""; + bool wasReplaced = ReplacePattern(source, "IF_SELECTOR", selector); + assert(wasReplaced); + + auto bytecodes = methodToBytecode(source.data()); + check(bytecodes, + {BC_PUSH_CONSTANT_0, BC_POP, BC_PUSH_SELF, BC(BC_SEND, 1), + BC(jumpBytecode, 4, 0), BC_PUSH_ARG_1, BC_POP, BC_PUSH_CONSTANT_2, + BC_POP, BC_PUSH_SELF, BC_RETURN_LOCAL}); + + tearDown(); +} + +void BytecodeGenerationTest::testKeywordIfTrueArg() { + auto bytecodes = methodToBytecode(R"""( test: arg = ( + #start. + (self key: 5) ifTrue: [ arg ]. + #end + ) )"""); + check(bytecodes, + {BC_PUSH_CONSTANT_0, BC_POP, BC_PUSH_SELF, BC_PUSH_CONSTANT_1, + BC(BC_SEND, 2), BC(BC_JUMP_ON_FALSE_TOP_NIL, 4, 0), BC_PUSH_ARG_1, + BC_POP, BC(BC_PUSH_CONSTANT, 3), BC_POP, BC_PUSH_SELF, + BC_RETURN_LOCAL}); +} + +void BytecodeGenerationTest::testIfReturnNonLocal() { + ifReturnNonLocal("ifTrue:", BC_JUMP_ON_FALSE_TOP_NIL); + ifReturnNonLocal("ifFalse:", BC_JUMP_ON_TRUE_TOP_NIL); +} + +void BytecodeGenerationTest::ifReturnNonLocal(std::string selector, + int8_t jumpBytecode) { + std::string source = R"""( test: arg = ( + #start. + self method IF_SELECTOR [ ^ arg ]. + #end + ) )"""; + bool wasReplaced = ReplacePattern(source, "IF_SELECTOR", selector); + assert(wasReplaced); + + auto bytecodes = methodToBytecode(source.data()); + check(bytecodes, + {BC_PUSH_CONSTANT_0, BC_POP, BC_PUSH_SELF, BC(BC_SEND, 1), + BC(jumpBytecode, 5, 0), BC_PUSH_ARG_1, BC_RETURN_LOCAL, BC_POP, + BC_PUSH_CONSTANT_2, BC_POP, BC_PUSH_SELF, BC_RETURN_LOCAL}); + + tearDown(); +} + /* @pytest.mark.parametrize( "operator,bytecode", @@ -698,68 +758,6 @@ void BytecodeGenerationTest::testInliningOfToDo() { check(bytecodes, [Bytecodes.push_1, bytecode, Bytecodes.return_self]) - - @pytest.mark.parametrize( - "if_selector,jump_bytecode", - [ - ("ifTrue:", Bytecodes.jump_on_false_top_nil), - ("ifFalse:", Bytecodes.jump_on_true_top_nil), - ], - ) - def test_if_arg(mgenc, if_selector, jump_bytecode): - bytecodes = method_to_bytecodes( - mgenc, - """ - test: arg = ( - #start. - self method IF_SELECTOR [ arg ]. - #end - )""".replace( - "IF_SELECTOR", if_selector - ), - ) - - assert len(bytecodes) == 17 - check( - bytecodes, - [ - Bytecodes.push_constant_0, - Bytecodes.pop, - Bytecodes.push_argument, - Bytecodes.send_1, - BC(jump_bytecode, 6, note="jump offset"), - BC(Bytecodes.push_argument, 1, 0), - Bytecodes.pop, - Bytecodes.push_constant, - Bytecodes.return_self, - ], - ) - - - def test_keyword_if_true_arg(mgenc): - bytecodes = method_to_bytecodes( - mgenc, - """ - test: arg = ( - #start. - (self key: 5) ifTrue: [ arg ]. - #end - )""", - ) - - assert len(bytecodes) == 18 - check( - bytecodes, - [ - (6, Bytecodes.send_2), - BC(Bytecodes.jump_on_false_top_nil, 6, note="jump offset"), - BC(Bytecodes.push_argument, 1, 0), - Bytecodes.pop, - Bytecodes.push_constant, - ], - ) - - def test_if_true_and_inc_field(cgenc, mgenc): add_field(cgenc, "field") bytecodes = method_to_bytecodes( @@ -810,39 +808,6 @@ void BytecodeGenerationTest::testInliningOfToDo() { ) - @pytest.mark.parametrize( - "if_selector,jump_bytecode", - [ - ("ifTrue:", Bytecodes.jump_on_false_top_nil), - ("ifFalse:", Bytecodes.jump_on_true_top_nil), - ], - ) - def test_if_return_non_local(mgenc, if_selector, jump_bytecode): - bytecodes = method_to_bytecodes( - mgenc, - """ - test: arg = ( - #start. - self method IF_SELECTOR [ ^ arg ]. - #end - )""".replace( - "IF_SELECTOR", if_selector - ), - ) - - assert len(bytecodes) == 18 - check( - bytecodes, - [ - (5, Bytecodes.send_1), - BC(jump_bytecode, 7, note="jump offset"), - BC(Bytecodes.push_argument, 1, 0), - Bytecodes.return_local, - Bytecodes.pop, - ], - ) - - def test_nested_ifs(cgenc, mgenc): add_field(cgenc, "field") bytecodes = method_to_bytecodes( diff --git a/src/unitTests/BytecodeGenerationTest.h b/src/unitTests/BytecodeGenerationTest.h index 8282b3c2..40e051a6 100644 --- a/src/unitTests/BytecodeGenerationTest.h +++ b/src/unitTests/BytecodeGenerationTest.h @@ -66,6 +66,9 @@ class BytecodeGenerationTest : public CPPUNIT_NS::TestCase { CPPUNIT_TEST(testInliningOfOr); CPPUNIT_TEST(testInliningOfAnd); CPPUNIT_TEST(testInliningOfToDo); + CPPUNIT_TEST(testIfArg); + CPPUNIT_TEST(testKeywordIfTrueArg); + CPPUNIT_TEST(testIfReturnNonLocal); CPPUNIT_TEST(testJumpQueuesOrdering); @@ -155,6 +158,13 @@ class BytecodeGenerationTest : public CPPUNIT_NS::TestCase { void testIfTrueIfFalseNlrArg1(); void testIfTrueIfFalseNlrArg2(); + void testIfArg(); + void ifArg(std::string selector, int8_t jumpBytecode); + void testKeywordIfTrueArg(); + + void testIfReturnNonLocal(); + void ifReturnNonLocal(std::string selector, int8_t jumpBytecode); + void testInliningOfOr(); void inliningOfOr(std::string selector); void testInliningOfAnd(); From c18e6297a2f0274145316e2e388a35051e54ff75 Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Mon, 5 Aug 2024 22:52:30 +0100 Subject: [PATCH 3/7] Extract shared superclass for tests with parsing Signed-off-by: Stefan Marr --- SOM.xcodeproj/project.pbxproj | 6 + src/unitTests/BytecodeGenerationTest.cpp | 148 +-------------------- src/unitTests/BytecodeGenerationTest.h | 52 +------- src/unitTests/TestWithParsing.cpp | 159 +++++++++++++++++++++++ src/unitTests/TestWithParsing.h | 58 +++++++++ 5 files changed, 226 insertions(+), 197 deletions(-) create mode 100644 src/unitTests/TestWithParsing.cpp create mode 100644 src/unitTests/TestWithParsing.h diff --git a/SOM.xcodeproj/project.pbxproj b/SOM.xcodeproj/project.pbxproj index d848d034..4a8af68d 100644 --- a/SOM.xcodeproj/project.pbxproj +++ b/SOM.xcodeproj/project.pbxproj @@ -92,6 +92,7 @@ 0A5A7E922C5D45A00011C783 /* VMSafePrimitive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A5A7E902C5D45A00011C783 /* VMSafePrimitive.cpp */; }; 0A5A7E962C60F5BB0011C783 /* VMTrivialMethod.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A5A7E952C60F5BB0011C783 /* VMTrivialMethod.cpp */; }; 0A5A7E972C60F5BB0011C783 /* VMTrivialMethod.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A5A7E952C60F5BB0011C783 /* VMTrivialMethod.cpp */; }; + 0A5A7E9D2C617EE00011C783 /* TestWithParsing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A5A7E9C2C617EE00011C783 /* TestWithParsing.cpp */; }; 0A67EA7519ACD43A00830E3B /* CloneObjectsTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A67EA7019ACD43A00830E3B /* CloneObjectsTest.cpp */; }; 0A67EA7619ACD43A00830E3B /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A67EA7119ACD43A00830E3B /* main.cpp */; }; 0A67EA7819ACD43A00830E3B /* WalkObjectsTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A67EA7319ACD43A00830E3B /* WalkObjectsTest.cpp */; }; @@ -229,6 +230,8 @@ 0A5A7E932C5DA9A90011C783 /* Primitives.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Primitives.h; sourceTree = ""; }; 0A5A7E942C602E8C0011C783 /* VMTrivialMethod.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VMTrivialMethod.h; sourceTree = ""; }; 0A5A7E952C60F5BB0011C783 /* VMTrivialMethod.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = VMTrivialMethod.cpp; sourceTree = ""; }; + 0A5A7E9B2C617EC70011C783 /* TestWithParsing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TestWithParsing.h; path = unitTests/TestWithParsing.h; sourceTree = ""; }; + 0A5A7E9C2C617EE00011C783 /* TestWithParsing.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = TestWithParsing.cpp; path = unitTests/TestWithParsing.cpp; sourceTree = ""; }; 0A67EA6719ACD37200830E3B /* unittests */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = unittests; sourceTree = BUILT_PRODUCTS_DIR; }; 0A67EA7019ACD43A00830E3B /* CloneObjectsTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CloneObjectsTest.cpp; path = unitTests/CloneObjectsTest.cpp; sourceTree = ""; }; 0A67EA7119ACD43A00830E3B /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = main.cpp; path = unitTests/main.cpp; sourceTree = ""; }; @@ -453,6 +456,8 @@ 0A67EA7419ACD43A00830E3B /* WriteBarrierTest.cpp */, 0A1C98562C3DD87300735850 /* unitTests/BytecodeGenerationTest.h */, 0A1C98572C3DD88500735850 /* unitTests/BytecodeGenerationTest.cpp */, + 0A5A7E9B2C617EC70011C783 /* TestWithParsing.h */, + 0A5A7E9C2C617EE00011C783 /* TestWithParsing.cpp */, ); name = unittests; sourceTree = ""; @@ -953,6 +958,7 @@ 0A3A3CA31A5D546D004CB03B /* Class.cpp in Sources */, 0A67EA9019ACD83200830E3B /* Lexer.cpp in Sources */, 0AB80AD92C394806006B6419 /* Globals.cpp in Sources */, + 0A5A7E9D2C617EE00011C783 /* TestWithParsing.cpp in Sources */, 0A3A3CA71A5D546D004CB03B /* Object.cpp in Sources */, 0A3A3CB21A5D5476004CB03B /* PrimitiveContainer.cpp in Sources */, 0A1C98582C3DD88500735850 /* unitTests/BytecodeGenerationTest.cpp in Sources */, diff --git a/src/unitTests/BytecodeGenerationTest.cpp b/src/unitTests/BytecodeGenerationTest.cpp index 08b97e06..1b875f6d 100644 --- a/src/unitTests/BytecodeGenerationTest.cpp +++ b/src/unitTests/BytecodeGenerationTest.cpp @@ -6,91 +6,13 @@ #include #include #include -#include #include #include -#include "../compiler/ClassGenerationContext.h" -#include "../compiler/Disassembler.h" -#include "../compiler/MethodGenerationContext.h" -#include "../compiler/Parser.h" #include "../interpreter/bytecodes.h" #include "../misc/StringUtil.h" -#include "../vm/Symbols.h" #include "../vmobjects/VMMethod.h" - -void BytecodeGenerationTest::dump(MethodGenerationContext* mgenc) { - Disassembler::DumpMethod(mgenc, ""); -} - -void BytecodeGenerationTest::ensureCGenC() { - if (_cgenc != nullptr) { - return; - } - - _cgenc = new ClassGenerationContext(); - _cgenc->SetName(SymbolFor("Test")); -} - -void BytecodeGenerationTest::ensureMGenC() { - if (_mgenc != nullptr) { - return; - } - ensureCGenC(); - - _mgenc = new MethodGenerationContext(*_cgenc); - std::string self = strSelf; - _mgenc->AddArgument(self, {0, 0}); -} - -void BytecodeGenerationTest::ensureBGenC() { - if (_bgenc != nullptr) { - return; - } - ensureCGenC(); - ensureMGenC(); - - _mgenc->SetSignature(SymbolFor("test")); - _bgenc = new MethodGenerationContext(*_cgenc, _mgenc); -} - -void BytecodeGenerationTest::addField(const char* fieldName) { - ensureCGenC(); - _cgenc->AddInstanceField(SymbolFor(fieldName)); -} - -std::vector BytecodeGenerationTest::methodToBytecode( - const char* source, bool dumpBytecodes) { - ensureMGenC(); - - istringstream ss(source); - - std::string fileName = "test"; - Parser parser(ss, fileName); - parser.method(*_mgenc); - - if (dumpBytecodes) { - dump(_mgenc); - } - return _mgenc->GetBytecodes(); -} - -std::vector BytecodeGenerationTest::blockToBytecode( - const char* source, bool dumpBytecodes) { - ensureBGenC(); - - istringstream ss(source); - - std::string fileName = "test"; - Parser parser(ss, fileName); - - parser.nestedBlock(*_bgenc); - - if (dumpBytecodes) { - dump(_bgenc); - } - return _bgenc->GetBytecodes(); -} +#include "TestWithParsing.h" void BytecodeGenerationTest::testEmptyMethodReturnsSelf() { auto bytecodes = methodToBytecode("test = ( )"); @@ -334,74 +256,6 @@ void BytecodeGenerationTest::testPopFieldOpt() { BC(BC_POP_FIELD, 2), BC_PUSH_1, BC(BC_POP_FIELD, 3), BC_RETURN_SELF}); } -void BytecodeGenerationTest::check(std::vector actual, - std::vector - expected) { - size_t i = 0; - size_t bci = 0; - for (; bci < actual.size() && i < expected.size();) { - uint8_t actualBc = actual.at(bci); - uint8_t bcLength = Bytecode::GetBytecodeLength(actualBc); - - BC expectedBc = expected.at(i); - - char msg[1000]; - snprintf(msg, 1000, "Bytecode %zu expected %s but got %s", i, - Bytecode::GetBytecodeName(expectedBc.bytecode), - Bytecode::GetBytecodeName(actualBc)); - if (expectedBc.bytecode != actualBc) { - dump(_mgenc); - } - CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, expectedBc.bytecode, actualBc); - - snprintf( - msg, 1000, - "Bytecode %zu (%s) was expected to have length %zu, but had %zu", i, - Bytecode::GetBytecodeName(actualBc), expectedBc.size, - (size_t)bcLength); - - if (expectedBc.size != bcLength) { - dump(_mgenc); - } - CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, expectedBc.size, (size_t)bcLength); - - if (bcLength > 1) { - snprintf(msg, 1000, - "Bytecode %zu (%s), arg1 expected %hhu but got %hhu", i, - Bytecode::GetBytecodeName(expectedBc.bytecode), - expectedBc.arg1, actual.at(bci + 1)); - if (expectedBc.arg1 != actual.at(bci + 1)) { - dump(_mgenc); - } - CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, expectedBc.arg1, - actual.at(bci + 1)); - - if (bcLength > 2) { - snprintf(msg, 1000, - "Bytecode %zu (%s), arg2 expected %hhu but got %hhu", - i, Bytecode::GetBytecodeName(expectedBc.bytecode), - expectedBc.arg2, actual.at(bci + 2)); - if (expectedBc.arg2 != actual.at(bci + 2)) { - dump(_mgenc); - } - CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, expectedBc.arg2, - actual.at(bci + 2)); - } - } - - i += 1; - bci += bcLength; - } - if (expected.size() != i || actual.size() != bci) { - dump(_mgenc); - } - - CPPUNIT_ASSERT_EQUAL_MESSAGE("All expected bytecodes covered", - expected.size(), i); - CPPUNIT_ASSERT_EQUAL_MESSAGE("All actual bytecodes covered", actual.size(), - bci); -} - void BytecodeGenerationTest::testWhileInlining(const char* selector, uint8_t jumpBytecode) { std::string source = R"""( test: arg = ( diff --git a/src/unitTests/BytecodeGenerationTest.h b/src/unitTests/BytecodeGenerationTest.h index 40e051a6..f5b70a36 100644 --- a/src/unitTests/BytecodeGenerationTest.h +++ b/src/unitTests/BytecodeGenerationTest.h @@ -5,25 +5,9 @@ #include "../compiler/ClassGenerationContext.h" #include "../compiler/MethodGenerationContext.h" #include "../interpreter/bytecodes.h" +#include "TestWithParsing.h" -class BC { -public: - BC(uint8_t bytecode) : bytecode(bytecode), arg1(0), arg2(0), size(1) {} - - BC(uint8_t bytecode, uint8_t arg1) - : bytecode(bytecode), arg1(arg1), arg2(0), size(2) {} - - BC(uint8_t bytecode, uint8_t arg1, uint8_t arg2) - : bytecode(bytecode), arg1(arg1), arg2(arg2), size(3) {} - - uint8_t bytecode; - uint8_t arg1; - uint8_t arg2; - - size_t size; -}; - -class BytecodeGenerationTest : public CPPUNIT_NS::TestCase { +class BytecodeGenerationTest : public TestWithParsing { CPPUNIT_TEST_SUITE(BytecodeGenerationTest); CPPUNIT_TEST(testEmptyMethodReturnsSelf); CPPUNIT_TEST(testPushConstant); @@ -74,35 +58,7 @@ class BytecodeGenerationTest : public CPPUNIT_NS::TestCase { CPPUNIT_TEST_SUITE_END(); -public: - inline void setUp() {} - - inline void tearDown() { - delete _cgenc; - _cgenc = nullptr; - - delete _mgenc; - _mgenc = nullptr; - - delete _bgenc; - _bgenc = nullptr; - } - private: - ClassGenerationContext* _cgenc; - MethodGenerationContext* _mgenc; - MethodGenerationContext* _bgenc; - - void ensureCGenC(); - void ensureMGenC(); - void ensureBGenC(); - void addField(const char* fieldName); - - std::vector methodToBytecode(const char* source, - bool dumpBytecodes = false); - std::vector blockToBytecode(const char* source, - bool dumpBytecodes = false); - void testEmptyMethodReturnsSelf(); void testPushConstant(); @@ -173,8 +129,4 @@ class BytecodeGenerationTest : public CPPUNIT_NS::TestCase { void testInliningOfToDo(); void testJumpQueuesOrdering(); - - void dump(MethodGenerationContext* mgenc); - - void check(std::vector actual, std::vector expected); }; diff --git a/src/unitTests/TestWithParsing.cpp b/src/unitTests/TestWithParsing.cpp new file mode 100644 index 00000000..e49d3be3 --- /dev/null +++ b/src/unitTests/TestWithParsing.cpp @@ -0,0 +1,159 @@ +#include "TestWithParsing.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../compiler/ClassGenerationContext.h" +#include "../compiler/Disassembler.h" +#include "../compiler/MethodGenerationContext.h" +#include "../compiler/Parser.h" +#include "../interpreter/bytecodes.h" +#include "../vm/Symbols.h" +#include "../vmobjects/VMMethod.h" + +void TestWithParsing::dump(MethodGenerationContext* mgenc) { + Disassembler::DumpMethod(mgenc, ""); +} + +void TestWithParsing::ensureCGenC() { + if (_cgenc != nullptr) { + return; + } + + _cgenc = new ClassGenerationContext(); + _cgenc->SetName(SymbolFor("Test")); +} + +void TestWithParsing::ensureMGenC() { + if (_mgenc != nullptr) { + return; + } + ensureCGenC(); + + _mgenc = new MethodGenerationContext(*_cgenc); + std::string self = strSelf; + _mgenc->AddArgument(self, {0, 0}); +} + +void TestWithParsing::ensureBGenC() { + if (_bgenc != nullptr) { + return; + } + ensureCGenC(); + ensureMGenC(); + + _mgenc->SetSignature(SymbolFor("test")); + _bgenc = new MethodGenerationContext(*_cgenc, _mgenc); +} + +void TestWithParsing::addField(const char* fieldName) { + ensureCGenC(); + _cgenc->AddInstanceField(SymbolFor(fieldName)); +} + +std::vector TestWithParsing::methodToBytecode(const char* source, + bool dumpBytecodes) { + ensureMGenC(); + + istringstream ss(source); + + std::string fileName = "test"; + Parser parser(ss, fileName); + parser.method(*_mgenc); + + if (dumpBytecodes) { + dump(_mgenc); + } + return _mgenc->GetBytecodes(); +} + +std::vector TestWithParsing::blockToBytecode(const char* source, + bool dumpBytecodes) { + ensureBGenC(); + + istringstream ss(source); + + std::string fileName = "test"; + Parser parser(ss, fileName); + + parser.nestedBlock(*_bgenc); + + if (dumpBytecodes) { + dump(_bgenc); + } + return _bgenc->GetBytecodes(); +} + +void TestWithParsing::check(std::vector actual, + std::vector + expected) { + size_t i = 0; + size_t bci = 0; + for (; bci < actual.size() && i < expected.size();) { + uint8_t actualBc = actual.at(bci); + uint8_t bcLength = Bytecode::GetBytecodeLength(actualBc); + + BC expectedBc = expected.at(i); + + char msg[1000]; + snprintf(msg, 1000, "Bytecode %zu expected %s but got %s", i, + Bytecode::GetBytecodeName(expectedBc.bytecode), + Bytecode::GetBytecodeName(actualBc)); + if (expectedBc.bytecode != actualBc) { + dump(_mgenc); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, expectedBc.bytecode, actualBc); + + snprintf( + msg, 1000, + "Bytecode %zu (%s) was expected to have length %zu, but had %zu", i, + Bytecode::GetBytecodeName(actualBc), expectedBc.size, + (size_t)bcLength); + + if (expectedBc.size != bcLength) { + dump(_mgenc); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, expectedBc.size, (size_t)bcLength); + + if (bcLength > 1) { + snprintf(msg, 1000, + "Bytecode %zu (%s), arg1 expected %hhu but got %hhu", i, + Bytecode::GetBytecodeName(expectedBc.bytecode), + expectedBc.arg1, actual.at(bci + 1)); + if (expectedBc.arg1 != actual.at(bci + 1)) { + dump(_mgenc); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, expectedBc.arg1, + actual.at(bci + 1)); + + if (bcLength > 2) { + snprintf(msg, 1000, + "Bytecode %zu (%s), arg2 expected %hhu but got %hhu", + i, Bytecode::GetBytecodeName(expectedBc.bytecode), + expectedBc.arg2, actual.at(bci + 2)); + if (expectedBc.arg2 != actual.at(bci + 2)) { + dump(_mgenc); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, expectedBc.arg2, + actual.at(bci + 2)); + } + } + + i += 1; + bci += bcLength; + } + if (expected.size() != i || actual.size() != bci) { + dump(_mgenc); + } + + CPPUNIT_ASSERT_EQUAL_MESSAGE("All expected bytecodes covered", + expected.size(), i); + CPPUNIT_ASSERT_EQUAL_MESSAGE("All actual bytecodes covered", actual.size(), + bci); +} diff --git a/src/unitTests/TestWithParsing.h b/src/unitTests/TestWithParsing.h new file mode 100644 index 00000000..98aa378b --- /dev/null +++ b/src/unitTests/TestWithParsing.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include "../compiler/ClassGenerationContext.h" +#include "../compiler/MethodGenerationContext.h" + +class BC { +public: + BC(uint8_t bytecode) : bytecode(bytecode), arg1(0), arg2(0), size(1) {} + + BC(uint8_t bytecode, uint8_t arg1) + : bytecode(bytecode), arg1(arg1), arg2(0), size(2) {} + + BC(uint8_t bytecode, uint8_t arg1, uint8_t arg2) + : bytecode(bytecode), arg1(arg1), arg2(arg2), size(3) {} + + uint8_t bytecode; + uint8_t arg1; + uint8_t arg2; + + size_t size; +}; + +class TestWithParsing : public CPPUNIT_NS::TestCase { +public: + inline void setUp() {} + + inline void tearDown() { + delete _cgenc; + _cgenc = nullptr; + + delete _mgenc; + _mgenc = nullptr; + + delete _bgenc; + _bgenc = nullptr; + } + +protected: + ClassGenerationContext* _cgenc; + MethodGenerationContext* _mgenc; + MethodGenerationContext* _bgenc; + + void ensureCGenC(); + void ensureMGenC(); + void ensureBGenC(); + void addField(const char* fieldName); + + std::vector methodToBytecode(const char* source, + bool dumpBytecodes = false); + std::vector blockToBytecode(const char* source, + bool dumpBytecodes = false); + + void dump(MethodGenerationContext* mgenc); + + void check(std::vector actual, std::vector expected); +}; From 5659e7e2f3d2e179f6914c5edae80bc85cada895 Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Mon, 5 Aug 2024 23:18:58 +0100 Subject: [PATCH 4/7] Add trivial method tests for literal return Signed-off-by: Stefan Marr --- SOM.xcodeproj/project.pbxproj | 6 + src/misc/debug.cpp | 4 +- src/misc/debug.h | 2 +- src/unitTests/TrivialMethodTest.cpp | 224 ++++++++++++++++++++++++++++ src/unitTests/TrivialMethodTest.h | 27 ++++ src/unitTests/main.cpp | 2 + src/vm/IsValidObject.cpp | 5 + src/vm/IsValidObject.h | 1 + 8 files changed, 268 insertions(+), 3 deletions(-) create mode 100644 src/unitTests/TrivialMethodTest.cpp create mode 100644 src/unitTests/TrivialMethodTest.h diff --git a/SOM.xcodeproj/project.pbxproj b/SOM.xcodeproj/project.pbxproj index 4a8af68d..498dbaf8 100644 --- a/SOM.xcodeproj/project.pbxproj +++ b/SOM.xcodeproj/project.pbxproj @@ -92,6 +92,7 @@ 0A5A7E922C5D45A00011C783 /* VMSafePrimitive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A5A7E902C5D45A00011C783 /* VMSafePrimitive.cpp */; }; 0A5A7E962C60F5BB0011C783 /* VMTrivialMethod.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A5A7E952C60F5BB0011C783 /* VMTrivialMethod.cpp */; }; 0A5A7E972C60F5BB0011C783 /* VMTrivialMethod.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A5A7E952C60F5BB0011C783 /* VMTrivialMethod.cpp */; }; + 0A5A7E9A2C617E400011C783 /* TrivialMethodTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A5A7E992C617E400011C783 /* TrivialMethodTest.cpp */; }; 0A5A7E9D2C617EE00011C783 /* TestWithParsing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A5A7E9C2C617EE00011C783 /* TestWithParsing.cpp */; }; 0A67EA7519ACD43A00830E3B /* CloneObjectsTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A67EA7019ACD43A00830E3B /* CloneObjectsTest.cpp */; }; 0A67EA7619ACD43A00830E3B /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A67EA7119ACD43A00830E3B /* main.cpp */; }; @@ -230,6 +231,8 @@ 0A5A7E932C5DA9A90011C783 /* Primitives.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Primitives.h; sourceTree = ""; }; 0A5A7E942C602E8C0011C783 /* VMTrivialMethod.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VMTrivialMethod.h; sourceTree = ""; }; 0A5A7E952C60F5BB0011C783 /* VMTrivialMethod.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = VMTrivialMethod.cpp; sourceTree = ""; }; + 0A5A7E982C617E2B0011C783 /* TrivialMethodTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TrivialMethodTest.h; path = unitTests/TrivialMethodTest.h; sourceTree = ""; }; + 0A5A7E992C617E400011C783 /* TrivialMethodTest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = TrivialMethodTest.cpp; path = unitTests/TrivialMethodTest.cpp; sourceTree = ""; }; 0A5A7E9B2C617EC70011C783 /* TestWithParsing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TestWithParsing.h; path = unitTests/TestWithParsing.h; sourceTree = ""; }; 0A5A7E9C2C617EE00011C783 /* TestWithParsing.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = TestWithParsing.cpp; path = unitTests/TestWithParsing.cpp; sourceTree = ""; }; 0A67EA6719ACD37200830E3B /* unittests */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = unittests; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -456,6 +459,8 @@ 0A67EA7419ACD43A00830E3B /* WriteBarrierTest.cpp */, 0A1C98562C3DD87300735850 /* unitTests/BytecodeGenerationTest.h */, 0A1C98572C3DD88500735850 /* unitTests/BytecodeGenerationTest.cpp */, + 0A5A7E982C617E2B0011C783 /* TrivialMethodTest.h */, + 0A5A7E992C617E400011C783 /* TrivialMethodTest.cpp */, 0A5A7E9B2C617EC70011C783 /* TestWithParsing.h */, 0A5A7E9C2C617EE00011C783 /* TestWithParsing.cpp */, ); @@ -963,6 +968,7 @@ 0A3A3CB21A5D5476004CB03B /* PrimitiveContainer.cpp in Sources */, 0A1C98582C3DD88500735850 /* unitTests/BytecodeGenerationTest.cpp in Sources */, 0A1C986F2C4F1D3900735850 /* debug.cpp in Sources */, + 0A5A7E9A2C617E400011C783 /* TrivialMethodTest.cpp in Sources */, 0A67EA8419ACD74800830E3B /* VMFrame.cpp in Sources */, 0A67EA7D19ACD74800830E3B /* Signature.cpp in Sources */, 0A67EA7E19ACD74800830E3B /* VMArray.cpp in Sources */, diff --git a/src/misc/debug.cpp b/src/misc/debug.cpp index d87cef0d..ec2a45d6 100644 --- a/src/misc/debug.cpp +++ b/src/misc/debug.cpp @@ -15,6 +15,6 @@ std::string DebugGetClassName(gc_oop_t obj) { return CLASS_OF(obj)->GetName()->GetStdString(); } -void DebugDumpMethod(VMMethod* method) { - Disassembler::DumpMethod(method, "", false); +void DebugDumpMethod(VMInvokable* method) { + Disassembler::DumpMethod((VMMethod*)method, "", false); } diff --git a/src/misc/debug.h b/src/misc/debug.h index affe3d8c..2dafb1bd 100644 --- a/src/misc/debug.h +++ b/src/misc/debug.h @@ -98,4 +98,4 @@ static inline void DebugTrace(const char* fmt, ...) { std::string DebugGetClassName(vm_oop_t); std::string DebugGetClassName(gc_oop_t); -void DebugDumpMethod(VMMethod* method); +void DebugDumpMethod(VMInvokable* method); diff --git a/src/unitTests/TrivialMethodTest.cpp b/src/unitTests/TrivialMethodTest.cpp new file mode 100644 index 00000000..4e5173f1 --- /dev/null +++ b/src/unitTests/TrivialMethodTest.cpp @@ -0,0 +1,224 @@ +#include "TrivialMethodTest.h" + +#include +#include + +#include "../compiler/MethodGenerationContext.h" +#include "../vm/IsValidObject.h" +#include "../vmobjects/VMInvokable.h" + +void TrivialMethodTest::literalReturn(std::string source) { + std::string s = "test = ( ^ " + source + " )"; + methodToBytecode(s.data()); + VMInvokable* m = _mgenc->Assemble(); + + std::string expected = "Expected to be trivial: " + s; + bool result = IsLiteralReturn(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); + + tearDown(); +} + +void TrivialMethodTest::testLiteralReturn() { + literalReturn("0"); + literalReturn("1"); + literalReturn("-10"); + literalReturn("3333"); + literalReturn("'str'"); + literalReturn("#sym"); + literalReturn("1.1"); + literalReturn("-2342.234"); + literalReturn("true"); + literalReturn("false"); + literalReturn("nil"); +} + +void TrivialMethodTest::blockLiteralReturn(std::string source) { + std::string s = "[ " + source + " ]"; + blockToBytecode(s.data()); + VMInvokable* m = _bgenc->Assemble(); + + std::string expected = "Expected to be trivial: " + s; + bool result = IsLiteralReturn(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); + + tearDown(); +} + +void TrivialMethodTest::testBlockLiteralReturn() { + blockLiteralReturn("0"); + blockLiteralReturn("1"); + blockLiteralReturn("-10"); + blockLiteralReturn("3333"); + blockLiteralReturn("'str'"); + blockLiteralReturn("#sym"); + blockLiteralReturn("1.1"); + blockLiteralReturn("-2342.234"); + blockLiteralReturn("true"); + blockLiteralReturn("false"); + blockLiteralReturn("nil"); +} + +void TrivialMethodTest::literalNoReturn(std::string source) { + std::string s = "test = ( " + source + " )"; + methodToBytecode(s.data()); + VMInvokable* m = _mgenc->Assemble(); + + std::string expected = "Expected to be non-trivial: " + s; + bool result = IsLiteralReturn(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), !result); + + tearDown(); +} + +void TrivialMethodTest::testLiteralNoReturn() { + literalNoReturn("0"); + literalNoReturn("1"); + literalNoReturn("-10"); + literalNoReturn("3333"); + literalNoReturn("'str'"); + literalNoReturn("#sym"); + literalNoReturn("1.1"); + literalNoReturn("-2342.234"); + literalNoReturn("true"); + literalNoReturn("false"); + literalNoReturn("nil"); +} + +void TrivialMethodTest::nonTrivialLiteralReturn(std::string source) { + std::string s = "test = ( 1. ^ " + source + " )"; + methodToBytecode(s.data()); + VMInvokable* m = _mgenc->Assemble(); + + std::string expected = "Expected to be non-trivial: " + s; + bool result = !IsLiteralReturn(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); + + tearDown(); +} + +void TrivialMethodTest::testNonTrivialLiteralReturn() { + nonTrivialLiteralReturn("0"); + nonTrivialLiteralReturn("1"); + nonTrivialLiteralReturn("-10"); + nonTrivialLiteralReturn("3333"); + nonTrivialLiteralReturn("'str'"); + nonTrivialLiteralReturn("#sym"); + nonTrivialLiteralReturn("1.1"); + nonTrivialLiteralReturn("-2342.234"); + nonTrivialLiteralReturn("true"); + nonTrivialLiteralReturn("false"); + nonTrivialLiteralReturn("nil"); +} + +/* + + @pytest.mark.parametrize("source", ["Nil", "system", "MyClassFooBar"]) + def test_global_return(mgenc, source): + body_or_none = parse_method(mgenc, "test = ( ^ " + source + " )") + m = mgenc.assemble(body_or_none) + assert isinstance(m, GlobalRead) + + + def test_non_trivial_global_return(mgenc): + body_or_none = parse_method(mgenc, "test = ( #foo. ^ system )") + m = mgenc.assemble(body_or_none) + assert isinstance(m, AstMethod) or isinstance(m, BcMethod) + + def test_unknown_global_in_block(bgenc): + """ + In PySOM we can actually support this, in TruffleSOM we can't + because of the difference in frame format. + """ + body_or_none = parse_block(bgenc, "[ UnknownGlobalSSSS ]") + m = bgenc.assemble(body_or_none) + assert isinstance(m, GlobalRead) + + def test_field_getter_0(cgenc, mgenc): + add_field(cgenc, "field") + body_or_none = parse_method(mgenc, "test = ( ^ field )") + m = mgenc.assemble(body_or_none) + assert isinstance(m, FieldRead) + + + def test_field_getter_n(cgenc, mgenc): + add_field(cgenc, "a") + add_field(cgenc, "b") + add_field(cgenc, "c") + add_field(cgenc, "d") + add_field(cgenc, "e") + add_field(cgenc, "field") + body_or_none = parse_method(mgenc, "test = ( ^ field )") + m = mgenc.assemble(body_or_none) + assert isinstance(m, FieldRead) + + + def test_non_trivial_getter_0(cgenc, mgenc): + add_field(cgenc, "field") + body = parse_method(mgenc, "test = ( 0. ^ field )") + m = mgenc.assemble(body) + assert isinstance(m, AstMethod) or isinstance(m, BcMethod) + + + def test_non_trivial_getter_n(cgenc, mgenc): + add_field(cgenc, "a") + add_field(cgenc, "b") + add_field(cgenc, "c") + add_field(cgenc, "d") + add_field(cgenc, "e") + add_field(cgenc, "field") + body = parse_method(mgenc, "test = ( 0. ^ field )") + m = mgenc.assemble(body) + assert isinstance(m, AstMethod) or isinstance(m, BcMethod) + + + @pytest.mark.parametrize( + "source", ["field := val", "field := val.", "field := val. ^ self"] + ) + def test_field_setter_0(cgenc, mgenc, source): + add_field(cgenc, "field") + body_or_none = parse_method(mgenc, "test: val = ( " + source + " )") + m = mgenc.assemble(body_or_none) + assert isinstance(m, FieldWrite) + + + @pytest.mark.parametrize( + "source", ["field := value", "field := value.", "field := value. ^ self"] + ) + def test_field_setter_n(cgenc, mgenc, source): + add_field(cgenc, "a") + add_field(cgenc, "b") + add_field(cgenc, "c") + add_field(cgenc, "d") + add_field(cgenc, "e") + add_field(cgenc, "field") + body_or_none = parse_method(mgenc, "test: value = ( " + source + " )") + m = mgenc.assemble(body_or_none) + assert isinstance(m, FieldWrite) + + + def test_non_trivial_field_setter_0(cgenc, mgenc): + add_field(cgenc, "field") + body_or_none = parse_method(mgenc, "test: val = ( 0. field := value )") + m = mgenc.assemble(body_or_none) + assert isinstance(m, AstMethod) or isinstance(m, BcMethod) + + + def test_non_trivial_field_setter_n(cgenc, mgenc): + add_field(cgenc, "a") + add_field(cgenc, "b") + add_field(cgenc, "c") + add_field(cgenc, "d") + add_field(cgenc, "e") + add_field(cgenc, "field") + body_or_none = parse_method(mgenc, "test: val = ( 0. field := value )") + m = mgenc.assemble(body_or_none) + assert isinstance(m, AstMethod) or isinstance(m, BcMethod) + + def test_block_return(mgenc): + body_or_none = parse_method(mgenc, "test = ( ^ [] )") + m = mgenc.assemble(body_or_none) + assert isinstance(m, AstMethod) or isinstance(m, BcMethod) + + + */ diff --git a/src/unitTests/TrivialMethodTest.h b/src/unitTests/TrivialMethodTest.h new file mode 100644 index 00000000..6eebf049 --- /dev/null +++ b/src/unitTests/TrivialMethodTest.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "TestWithParsing.h" + +class TrivialMethodTest : public TestWithParsing { + CPPUNIT_TEST_SUITE(TrivialMethodTest); + CPPUNIT_TEST(testLiteralReturn); + CPPUNIT_TEST(testLiteralNoReturn); + CPPUNIT_TEST(testBlockLiteralReturn); + CPPUNIT_TEST(testNonTrivialLiteralReturn); + CPPUNIT_TEST_SUITE_END(); + +private: + void testLiteralReturn(); + void literalReturn(std::string source); + + void testBlockLiteralReturn(); + void blockLiteralReturn(std::string source); + + void testLiteralNoReturn(); + void literalNoReturn(std::string source); + + void testNonTrivialLiteralReturn(); + void nonTrivialLiteralReturn(std::string source); +}; diff --git a/src/unitTests/main.cpp b/src/unitTests/main.cpp index cdeb15c0..77f282eb 100644 --- a/src/unitTests/main.cpp +++ b/src/unitTests/main.cpp @@ -20,6 +20,7 @@ #include "BasicInterpreterTests.h" #include "BytecodeGenerationTest.h" #include "CloneObjectsTest.h" +#include "TrivialMethodTest.h" #include "WalkObjectsTest.h" #if GC_TYPE == GENERATIONAL @@ -32,6 +33,7 @@ CPPUNIT_TEST_SUITE_REGISTRATION(CloneObjectsTest); CPPUNIT_TEST_SUITE_REGISTRATION(WriteBarrierTest); #endif CPPUNIT_TEST_SUITE_REGISTRATION(BytecodeGenerationTest); +CPPUNIT_TEST_SUITE_REGISTRATION(TrivialMethodTest); CPPUNIT_TEST_SUITE_REGISTRATION(BasicInterpreterTests); int main(int ac, char** av) { diff --git a/src/vm/IsValidObject.cpp b/src/vm/IsValidObject.cpp index ea009951..041a6993 100644 --- a/src/vm/IsValidObject.cpp +++ b/src/vm/IsValidObject.cpp @@ -132,6 +132,11 @@ bool IsVMSymbol(vm_oop_t obj) { return get_vtable(AS_OBJ(obj)) == vt_symbol; } +bool IsLiteralReturn(vm_oop_t obj) { + assert(vt_literal_return != nullptr); + return get_vtable(AS_OBJ(obj)) == vt_literal_return; +} + void obtain_vtables_of_known_classes(VMSymbol* someValidSymbol) { // These objects are allocated on the heap. So, they will get GC'ed soon // enough. diff --git a/src/vm/IsValidObject.h b/src/vm/IsValidObject.h index 0c53d9b1..5edc8fe6 100644 --- a/src/vm/IsValidObject.h +++ b/src/vm/IsValidObject.h @@ -8,6 +8,7 @@ bool IsValidObject(vm_oop_t obj); bool IsVMInteger(vm_oop_t obj); bool IsVMMethod(vm_oop_t obj); bool IsVMSymbol(vm_oop_t obj); +bool IsLiteralReturn(vm_oop_t obj); void set_vt_to_null(); From 5c8febd734c10d993447ce8b4ff007daf052042e Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Tue, 6 Aug 2024 00:30:25 +0100 Subject: [PATCH 5/7] Add support for trivial methods that read globals Signed-off-by: Stefan Marr --- src/compiler/MethodGenerationContext.cpp | 16 ++++++++ src/compiler/MethodGenerationContext.h | 2 +- src/interpreter/Interpreter.cpp | 29 ++++++++------ src/interpreter/Interpreter.h | 2 + src/unitTests/TrivialMethodTest.cpp | 51 +++++++++++++++--------- src/unitTests/TrivialMethodTest.h | 9 +++++ src/vm/IsValidObject.cpp | 14 ++++++- src/vm/IsValidObject.h | 1 + src/vmobjects/ObjectFormats.h | 2 + src/vmobjects/VMTrivialMethod.cpp | 44 ++++++++++++++++++++ src/vmobjects/VMTrivialMethod.h | 45 +++++++++++++++++++++ 11 files changed, 182 insertions(+), 33 deletions(-) diff --git a/src/compiler/MethodGenerationContext.cpp b/src/compiler/MethodGenerationContext.cpp index e0409070..5da658db 100644 --- a/src/compiler/MethodGenerationContext.cpp +++ b/src/compiler/MethodGenerationContext.cpp @@ -152,6 +152,22 @@ VMTrivialMethod* MethodGenerationContext::assembleLiteralReturn( "returns a literal"); } +VMTrivialMethod* MethodGenerationContext::assembleGlobalReturn() { + if (bytecode.size() != (Bytecode::GetBytecodeLength(BC_PUSH_GLOBAL) + + Bytecode::GetBytecodeLength(BC_RETURN_LOCAL))) { + return nullptr; + } + + if (literals.size() != 1) { + GetUniverse()->ErrorExit( + "Unexpected situation when trying to create trivial method that " + "reads a global. New Bytecode?"); + } + + VMSymbol* globalName = (VMSymbol*)literals.at(0); + return MakeGlobalReturn(signature, arguments, globalName); +} + VMPrimitive* MethodGenerationContext::AssemblePrimitive(bool classSide) { return VMPrimitive::GetEmptyPrimitive(signature, classSide); } diff --git a/src/compiler/MethodGenerationContext.h b/src/compiler/MethodGenerationContext.h index ce8d65c6..4c9c15f9 100644 --- a/src/compiler/MethodGenerationContext.h +++ b/src/compiler/MethodGenerationContext.h @@ -119,7 +119,7 @@ class MethodGenerationContext { private: VMTrivialMethod* assembleTrivialMethod(); VMTrivialMethod* assembleLiteralReturn(uint8_t pushCandidate); - VMTrivialMethod* assembleGlobalReturn() { return nullptr; } + VMTrivialMethod* assembleGlobalReturn(); VMTrivialMethod* assembleFieldGetter(uint8_t pushCandidate) { return nullptr; } diff --git a/src/interpreter/Interpreter.cpp b/src/interpreter/Interpreter.cpp index 49b9e7e8..ef3b5abd 100644 --- a/src/interpreter/Interpreter.cpp +++ b/src/interpreter/Interpreter.cpp @@ -260,22 +260,25 @@ void Interpreter::doPushGlobal(long bytecodeIndex) { if (global != nullptr) { GetFrame()->Push(global); } else { - vm_oop_t arguments[] = {globalName}; - vm_oop_t self = GetSelf(); + SendUnknownGlobal(globalName); + } +} - // check if there is enough space on the stack for this unplanned Send - // unknowGlobal: needs 2 slots, one for "this" and one for the argument - long additionalStackSlots = 2 - GetFrame()->RemainingStackSize(); - if (additionalStackSlots > 0) { - GetFrame()->SetBytecodeIndex(bytecodeIndexGlobal); - // copy current frame into a bigger one and replace the current - // frame - SetFrame( - VMFrame::EmergencyFrameFrom(GetFrame(), additionalStackSlots)); - } +void Interpreter::SendUnknownGlobal(VMSymbol* globalName) { + vm_oop_t arguments[] = {globalName}; + vm_oop_t self = GetSelf(); - AS_OBJ(self)->Send(this, unknownGlobal, arguments, 1); + // check if there is enough space on the stack for this unplanned Send + // unknowGlobal: needs 2 slots, one for "this" and one for the argument + long additionalStackSlots = 2 - GetFrame()->RemainingStackSize(); + if (additionalStackSlots > 0) { + GetFrame()->SetBytecodeIndex(bytecodeIndexGlobal); + // copy current frame into a bigger one and replace the current + // frame + SetFrame(VMFrame::EmergencyFrameFrom(GetFrame(), additionalStackSlots)); } + + AS_OBJ(self)->Send(this, unknownGlobal, arguments, 1); } void Interpreter::doPop() { diff --git a/src/interpreter/Interpreter.h b/src/interpreter/Interpreter.h index 80264018..5825846c 100644 --- a/src/interpreter/Interpreter.h +++ b/src/interpreter/Interpreter.h @@ -57,6 +57,8 @@ class Interpreter { uint8_t* GetBytecodes() const; void WalkGlobals(walk_heap_fn); + void SendUnknownGlobal(VMSymbol* globalName); + private: vm_oop_t GetSelf() const; diff --git a/src/unitTests/TrivialMethodTest.cpp b/src/unitTests/TrivialMethodTest.cpp index 4e5173f1..d9b4743e 100644 --- a/src/unitTests/TrivialMethodTest.cpp +++ b/src/unitTests/TrivialMethodTest.cpp @@ -111,29 +111,44 @@ void TrivialMethodTest::testNonTrivialLiteralReturn() { nonTrivialLiteralReturn("nil"); } -/* +void TrivialMethodTest::globalReturn(std::string source) { + std::string s = "test = ( ^ " + source + " )"; + methodToBytecode(s.data()); + VMInvokable* m = _mgenc->Assemble(); - @pytest.mark.parametrize("source", ["Nil", "system", "MyClassFooBar"]) - def test_global_return(mgenc, source): - body_or_none = parse_method(mgenc, "test = ( ^ " + source + " )") - m = mgenc.assemble(body_or_none) - assert isinstance(m, GlobalRead) + std::string expected = "Expected to be trivial: " + s; + bool result = IsGlobalReturn(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); + tearDown(); +} - def test_non_trivial_global_return(mgenc): - body_or_none = parse_method(mgenc, "test = ( #foo. ^ system )") - m = mgenc.assemble(body_or_none) - assert isinstance(m, AstMethod) or isinstance(m, BcMethod) +void TrivialMethodTest::testGlobalReturn() { + globalReturn("Nil"); + globalReturn("system"); + globalReturn("MyClassFooBar"); +} + +void TrivialMethodTest::testNonTrivialGlobalReturn() { + methodToBytecode("test = ( #foo. ^ system )"); + VMInvokable* m = _mgenc->Assemble(); - def test_unknown_global_in_block(bgenc): - """ - In PySOM we can actually support this, in TruffleSOM we can't - because of the difference in frame format. - """ - body_or_none = parse_block(bgenc, "[ UnknownGlobalSSSS ]") - m = bgenc.assemble(body_or_none) - assert isinstance(m, GlobalRead) + std::string expected = + "Expected to be non-trivial: test = ( #foo. ^ system )"; + bool result = !IsGlobalReturn(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); +} +void TrivialMethodTest::testUnknownGlobalInBlock() { + blockToBytecode("[ UnknownGlobalSSSS ]"); + VMInvokable* m = _bgenc->Assemble(); + + std::string expected = "Expected to be trivial: [ UnknownGlobalSSSS ]"; + bool result = IsGlobalReturn(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); +} + +/* def test_field_getter_0(cgenc, mgenc): add_field(cgenc, "field") body_or_none = parse_method(mgenc, "test = ( ^ field )") diff --git a/src/unitTests/TrivialMethodTest.h b/src/unitTests/TrivialMethodTest.h index 6eebf049..db2643fd 100644 --- a/src/unitTests/TrivialMethodTest.h +++ b/src/unitTests/TrivialMethodTest.h @@ -10,6 +10,9 @@ class TrivialMethodTest : public TestWithParsing { CPPUNIT_TEST(testLiteralNoReturn); CPPUNIT_TEST(testBlockLiteralReturn); CPPUNIT_TEST(testNonTrivialLiteralReturn); + CPPUNIT_TEST(testGlobalReturn); + CPPUNIT_TEST(testNonTrivialGlobalReturn); + CPPUNIT_TEST(testUnknownGlobalInBlock); CPPUNIT_TEST_SUITE_END(); private: @@ -24,4 +27,10 @@ class TrivialMethodTest : public TestWithParsing { void testNonTrivialLiteralReturn(); void nonTrivialLiteralReturn(std::string source); + + void testGlobalReturn(); + void globalReturn(std::string source); + + void testNonTrivialGlobalReturn(); + void testUnknownGlobalInBlock(); }; diff --git a/src/vm/IsValidObject.cpp b/src/vm/IsValidObject.cpp index 041a6993..3ee5740c 100644 --- a/src/vm/IsValidObject.cpp +++ b/src/vm/IsValidObject.cpp @@ -37,6 +37,7 @@ void* vt_safe_un_primitive; void* vt_safe_bin_primitive; void* vt_safe_ter_primitive; void* vt_literal_return; +void* vt_global_return; void* vt_string; void* vt_symbol; @@ -69,7 +70,8 @@ bool IsValidObject(vm_oop_t obj) { vt == vt_integer || vt == vt_method || vt == vt_object || vt == vt_primitive || vt == vt_safe_un_primitive || vt == vt_safe_bin_primitive || vt == vt_safe_ter_primitive || - vt == vt_string || vt == vt_symbol || vt == vt_literal_return; + vt == vt_string || vt == vt_symbol || vt == vt_literal_return || + vt == vt_global_return; if (!b) { assert(b && "Expected vtable to be one of the known ones."); return false; @@ -109,6 +111,7 @@ void set_vt_to_null() { vt_safe_bin_primitive = nullptr; vt_safe_ter_primitive = nullptr; vt_literal_return = nullptr; + vt_global_return = nullptr; vt_string = nullptr; vt_symbol = nullptr; } @@ -137,6 +140,11 @@ bool IsLiteralReturn(vm_oop_t obj) { return get_vtable(AS_OBJ(obj)) == vt_literal_return; } +bool IsGlobalReturn(vm_oop_t obj) { + assert(vt_global_return != nullptr); + return get_vtable(AS_OBJ(obj)) == vt_global_return; +} + void obtain_vtables_of_known_classes(VMSymbol* someValidSymbol) { // These objects are allocated on the heap. So, they will get GC'ed soon // enough. @@ -187,6 +195,10 @@ void obtain_vtables_of_known_classes(VMSymbol* someValidSymbol) { VMLiteralReturn(someValidSymbol, v, someValidSymbol); vt_literal_return = get_vtable(lr); + VMGlobalReturn* gr = new (GetHeap(), 0) + VMGlobalReturn(someValidSymbol, v, someValidSymbol); + vt_global_return = get_vtable(gr); + VMString* str = new (GetHeap(), PADDED_SIZE(1)) VMString(0, nullptr); vt_string = get_vtable(str); diff --git a/src/vm/IsValidObject.h b/src/vm/IsValidObject.h index 5edc8fe6..e81495d4 100644 --- a/src/vm/IsValidObject.h +++ b/src/vm/IsValidObject.h @@ -9,6 +9,7 @@ bool IsVMInteger(vm_oop_t obj); bool IsVMMethod(vm_oop_t obj); bool IsVMSymbol(vm_oop_t obj); bool IsLiteralReturn(vm_oop_t obj); +bool IsGlobalReturn(vm_oop_t obj); void set_vt_to_null(); diff --git a/src/vmobjects/ObjectFormats.h b/src/vmobjects/ObjectFormats.h index 2d9fd3e8..0bf8ac37 100644 --- a/src/vmobjects/ObjectFormats.h +++ b/src/vmobjects/ObjectFormats.h @@ -92,6 +92,7 @@ class VMSafeBinaryPrimitive; class VMSafeTernaryPrimitive; class VMTrivialMethod; class VMLiteralReturn; +class VMGlobalReturn; class VMString; class VMSymbol; @@ -154,6 +155,7 @@ class GCSafeBinaryPrimitive : public GCSafePrimitive { public: typedef VMSafeBi class GCSafeTernaryPrimitive : public GCSafePrimitive { public: typedef VMSafeTernaryPrimitive Loaded; }; class GCTrivialMethod : public GCInvokable { public: typedef VMTrivialMethod Loaded; }; class GCLiteralReturn : public GCTrivialMethod { public: typedef VMLiteralReturn Loaded; }; +class GCGlobalReturn : public GCTrivialMethod { public: typedef VMGlobalReturn Loaded; }; class GCString : public GCAbstractObject { public: typedef VMString Loaded; }; class GCSymbol : public GCString { public: typedef VMSymbol Loaded; }; // clang-format on diff --git a/src/vmobjects/VMTrivialMethod.cpp b/src/vmobjects/VMTrivialMethod.cpp index 3919a437..f5da14f0 100644 --- a/src/vmobjects/VMTrivialMethod.cpp +++ b/src/vmobjects/VMTrivialMethod.cpp @@ -9,6 +9,7 @@ #include "../memory/Heap.h" #include "../misc/defs.h" #include "../vm/LogAllocation.h" +#include "../vm/Universe.h" #include "AbstractObject.h" #include "ObjectFormats.h" #include "VMFrame.h" @@ -21,6 +22,14 @@ VMTrivialMethod* MakeLiteralReturn(VMSymbol* sig, vector& arguments, return result; } +VMTrivialMethod* MakeGlobalReturn(VMSymbol* sig, vector& arguments, + VMSymbol* globalName) { + VMGlobalReturn* result = + new (GetHeap(), 0) VMGlobalReturn(sig, arguments, globalName); + LOG_ALLOCATION("VMGlobalReturn", result->GetObjectSize()); + return result; +} + VMFrame* VMLiteralReturn::Invoke(Interpreter*, VMFrame* frame) { for (int i = 0; i < numberOfArguments; i += 1) { frame->Pop(); @@ -48,3 +57,38 @@ void VMLiteralReturn::WalkObjects(walk_heap_fn walk) { void VMLiteralReturn::InlineInto(MethodGenerationContext& mgenc, bool) { EmitPUSHCONSTANT(mgenc, load_ptr(literal)); } + +VMFrame* VMGlobalReturn::Invoke(Interpreter* interpreter, VMFrame* frame) { + for (int i = 0; i < numberOfArguments; i += 1) { + frame->Pop(); + } + + vm_oop_t value = GetUniverse()->GetGlobal(load_ptr(globalName)); + if (value != nullptr) { + frame->Push(value); + } else { + interpreter->SendUnknownGlobal(load_ptr(globalName)); + } + + return nullptr; +} + +void VMGlobalReturn::InlineInto(MethodGenerationContext& mgenc, bool) { + EmitPUSHGLOBAL(mgenc, load_ptr(globalName)); +} + +void VMGlobalReturn::WalkObjects(walk_heap_fn walk) { + VMInvokable::WalkObjects(walk); + globalName = (GCSymbol*)walk(globalName); +} + +std::string VMGlobalReturn::AsDebugString() const { + return "VMGlobalReturn(" + AS_OBJ(load_ptr(globalName))->AsDebugString() + + ")"; +} + +AbstractVMObject* VMGlobalReturn::CloneForMovingGC() const { + VMGlobalReturn* prim = + new (GetHeap(), 0 ALLOC_MATURE) VMGlobalReturn(*this); + return prim; +} diff --git a/src/vmobjects/VMTrivialMethod.h b/src/vmobjects/VMTrivialMethod.h index b07e634d..57c506eb 100644 --- a/src/vmobjects/VMTrivialMethod.h +++ b/src/vmobjects/VMTrivialMethod.h @@ -2,8 +2,10 @@ #include "../compiler/MethodGenerationContext.h" #include "../vm/Globals.h" +#include "ObjectFormats.h" #include "Signature.h" #include "VMInvokable.h" +#include "VMSymbol.h" class VMTrivialMethod : public VMInvokable { public: @@ -40,6 +42,8 @@ class VMTrivialMethod : public VMInvokable { VMTrivialMethod* MakeLiteralReturn(VMSymbol* sig, vector& arguments, vm_oop_t literal); +VMTrivialMethod* MakeGlobalReturn(VMSymbol* sig, vector& arguments, + VMSymbol* globalName); class VMLiteralReturn : public VMTrivialMethod { public: @@ -79,3 +83,44 @@ class VMLiteralReturn : public VMTrivialMethod { gc_oop_t literal; int numberOfArguments; }; + +class VMGlobalReturn : public VMTrivialMethod { +public: + typedef GCGlobalReturn Stored; + + VMGlobalReturn(VMSymbol* sig, vector& arguments, + VMSymbol* globalName) + : VMTrivialMethod(sig, arguments), + globalName(store_with_separate_barrier(globalName)), + numberOfArguments(Signature::GetNumberOfArguments(sig)) { + write_barrier(this, sig); + write_barrier(this, globalName); + } + + inline size_t GetObjectSize() const override { + return sizeof(VMLiteralReturn); + } + + VMFrame* Invoke(Interpreter*, VMFrame*) override; + void InlineInto(MethodGenerationContext& mgenc, + bool mergeScope = true) final; + + AbstractVMObject* CloneForMovingGC() const final; + + void MarkObjectAsInvalid() final { + VMTrivialMethod::MarkObjectAsInvalid(); + globalName = (GCSymbol*)INVALID_GC_POINTER; + } + + void WalkObjects(walk_heap_fn) override; + + bool IsMarkedInvalid() const final { + return globalName == (GCSymbol*)INVALID_GC_POINTER; + } + + std::string AsDebugString() const final; + +private: + GCSymbol* globalName; + int numberOfArguments; +}; From 59e8d09a18b7e7ccef8c4acc53e24d4d6c46debd Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Tue, 6 Aug 2024 10:25:21 +0100 Subject: [PATCH 6/7] Add support for trivial getter methods Signed-off-by: Stefan Marr --- src/compiler/BytecodeGenerator.cpp | 3 +- src/compiler/BytecodeGenerator.h | 3 +- src/compiler/MethodGenerationContext.cpp | 24 ++++++++ src/compiler/MethodGenerationContext.h | 4 +- src/unitTests/TrivialMethodTest.cpp | 78 ++++++++++++++---------- src/unitTests/TrivialMethodTest.h | 10 +++ src/vm/IsValidObject.cpp | 13 +++- src/vm/IsValidObject.h | 1 + src/vmobjects/ObjectFormats.h | 10 +-- src/vmobjects/VMMethod.cpp | 4 +- src/vmobjects/VMObject.h | 1 + src/vmobjects/VMTrivialMethod.cpp | 48 +++++++++++++++ src/vmobjects/VMTrivialMethod.h | 37 +++++++++++ 13 files changed, 190 insertions(+), 46 deletions(-) diff --git a/src/compiler/BytecodeGenerator.cpp b/src/compiler/BytecodeGenerator.cpp index fd6efd0a..452abdc2 100644 --- a/src/compiler/BytecodeGenerator.cpp +++ b/src/compiler/BytecodeGenerator.cpp @@ -322,8 +322,7 @@ size_t Emit3WithDummy(MethodGenerationContext& mgenc, uint8_t bytecode, return index; } -void EmitPushFieldWithIndex(MethodGenerationContext& mgenc, uint8_t fieldIdx, - uint8_t ctxLevel) { +void EmitPushFieldWithIndex(MethodGenerationContext& mgenc, uint8_t fieldIdx) { // if (ctxLevel == 0) { if (fieldIdx == 0) { Emit1(mgenc, BC_PUSH_FIELD_0, 1); diff --git a/src/compiler/BytecodeGenerator.h b/src/compiler/BytecodeGenerator.h index d529ac10..5b6b2a81 100644 --- a/src/compiler/BytecodeGenerator.h +++ b/src/compiler/BytecodeGenerator.h @@ -71,7 +71,6 @@ void EmitJumpBackwardWithOffset(MethodGenerationContext& mgenc, size_t Emit3WithDummy(MethodGenerationContext& mgenc, uint8_t bytecode, size_t stackEffect); -void EmitPushFieldWithIndex(MethodGenerationContext& mgenc, uint8_t fieldIdx, - uint8_t ctxLevel); +void EmitPushFieldWithIndex(MethodGenerationContext& mgenc, uint8_t fieldIdx); void EmitPopFieldWithIndex(MethodGenerationContext& mgenc, uint8_t fieldIdx, uint8_t ctxLevel); diff --git a/src/compiler/MethodGenerationContext.cpp b/src/compiler/MethodGenerationContext.cpp index 5da658db..4be726a3 100644 --- a/src/compiler/MethodGenerationContext.cpp +++ b/src/compiler/MethodGenerationContext.cpp @@ -168,6 +168,30 @@ VMTrivialMethod* MethodGenerationContext::assembleGlobalReturn() { return MakeGlobalReturn(signature, arguments, globalName); } +VMTrivialMethod* MethodGenerationContext::assembleFieldGetter( + uint8_t pushCandidate) { + if (bytecode.size() != (Bytecode::GetBytecodeLength(pushCandidate) + + Bytecode::GetBytecodeLength(BC_RETURN_LOCAL))) { + return nullptr; + } + + size_t fieldIndex; + if (pushCandidate == BC_PUSH_FIELD_0) { + fieldIndex = 0; + } else if (pushCandidate == BC_PUSH_FIELD_1) { + fieldIndex = 1; + } else { + assert(pushCandidate == BC_PUSH_FIELD); + // -2: -1 to skip over a 1-byte BC_RETURN_LOCAL, + // and of course -1 for length vs offset + fieldIndex = bytecode.at(bytecode.size() - 2); + assert(fieldIndex > 1 && + "BC_PUSH_FIELD with index 0 or 1 is not optimal"); + } + + return MakeGetter(signature, arguments, fieldIndex); +} + VMPrimitive* MethodGenerationContext::AssemblePrimitive(bool classSide) { return VMPrimitive::GetEmptyPrimitive(signature, classSide); } diff --git a/src/compiler/MethodGenerationContext.h b/src/compiler/MethodGenerationContext.h index 4c9c15f9..4a1e2b87 100644 --- a/src/compiler/MethodGenerationContext.h +++ b/src/compiler/MethodGenerationContext.h @@ -120,9 +120,7 @@ class MethodGenerationContext { VMTrivialMethod* assembleTrivialMethod(); VMTrivialMethod* assembleLiteralReturn(uint8_t pushCandidate); VMTrivialMethod* assembleGlobalReturn(); - VMTrivialMethod* assembleFieldGetter(uint8_t pushCandidate) { - return nullptr; - } + VMTrivialMethod* assembleFieldGetter(uint8_t pushCandidate); VMTrivialMethod* assembleFieldSetter() { return nullptr; } VMTrivialMethod* assembleFieldGetterFromReturn(uint8_t pushCandidate) { return nullptr; diff --git a/src/unitTests/TrivialMethodTest.cpp b/src/unitTests/TrivialMethodTest.cpp index d9b4743e..bd0bae0e 100644 --- a/src/unitTests/TrivialMethodTest.cpp +++ b/src/unitTests/TrivialMethodTest.cpp @@ -148,45 +148,61 @@ void TrivialMethodTest::testUnknownGlobalInBlock() { CPPUNIT_ASSERT_MESSAGE(expected.data(), result); } -/* - def test_field_getter_0(cgenc, mgenc): - add_field(cgenc, "field") - body_or_none = parse_method(mgenc, "test = ( ^ field )") - m = mgenc.assemble(body_or_none) - assert isinstance(m, FieldRead) +void TrivialMethodTest::testFieldGetter0() { + addField("field"); + methodToBytecode("test = ( ^ field )"); + VMInvokable* m = _mgenc->Assemble(); - def test_field_getter_n(cgenc, mgenc): - add_field(cgenc, "a") - add_field(cgenc, "b") - add_field(cgenc, "c") - add_field(cgenc, "d") - add_field(cgenc, "e") - add_field(cgenc, "field") - body_or_none = parse_method(mgenc, "test = ( ^ field )") - m = mgenc.assemble(body_or_none) - assert isinstance(m, FieldRead) + std::string expected = "Expected to be trivial: test = ( ^ field )"; + bool result = IsGetter(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); +} +void TrivialMethodTest::testFieldGetterN() { + addField("a"); + addField("b"); + addField("c"); + addField("d"); + addField("e"); + addField("field"); + methodToBytecode("test = ( ^ field )"); - def test_non_trivial_getter_0(cgenc, mgenc): - add_field(cgenc, "field") - body = parse_method(mgenc, "test = ( 0. ^ field )") - m = mgenc.assemble(body) - assert isinstance(m, AstMethod) or isinstance(m, BcMethod) + VMInvokable* m = _mgenc->Assemble(); + + std::string expected = "Expected to be trivial: test = ( ^ field )"; + bool result = IsGetter(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); +} +void TrivialMethodTest::testNonTrivialFieldGetter0() { + addField("field"); + methodToBytecode("test = ( 0. ^ field )"); - def test_non_trivial_getter_n(cgenc, mgenc): - add_field(cgenc, "a") - add_field(cgenc, "b") - add_field(cgenc, "c") - add_field(cgenc, "d") - add_field(cgenc, "e") - add_field(cgenc, "field") - body = parse_method(mgenc, "test = ( 0. ^ field )") - m = mgenc.assemble(body) - assert isinstance(m, AstMethod) or isinstance(m, BcMethod) + VMInvokable* m = _mgenc->Assemble(); + std::string expected = "Expected to be non-trivial: test = ( 0. ^ field )"; + bool result = !IsGetter(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); +} + +void TrivialMethodTest::testNonTrivialFieldGetterN() { + addField("a"); + addField("b"); + addField("c"); + addField("d"); + addField("e"); + addField("field"); + methodToBytecode("test = ( 0. ^ field )"); + + VMInvokable* m = _mgenc->Assemble(); + std::string expected = "Expected to be non-trivial: test = ( 0. ^ field )"; + bool result = !IsGetter(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); +} + +/* @pytest.mark.parametrize( "source", ["field := val", "field := val.", "field := val. ^ self"] ) diff --git a/src/unitTests/TrivialMethodTest.h b/src/unitTests/TrivialMethodTest.h index db2643fd..7c1c847b 100644 --- a/src/unitTests/TrivialMethodTest.h +++ b/src/unitTests/TrivialMethodTest.h @@ -13,6 +13,10 @@ class TrivialMethodTest : public TestWithParsing { CPPUNIT_TEST(testGlobalReturn); CPPUNIT_TEST(testNonTrivialGlobalReturn); CPPUNIT_TEST(testUnknownGlobalInBlock); + CPPUNIT_TEST(testFieldGetter0); + CPPUNIT_TEST(testFieldGetterN); + CPPUNIT_TEST(testNonTrivialFieldGetter0); + CPPUNIT_TEST(testNonTrivialFieldGetterN); CPPUNIT_TEST_SUITE_END(); private: @@ -33,4 +37,10 @@ class TrivialMethodTest : public TestWithParsing { void testNonTrivialGlobalReturn(); void testUnknownGlobalInBlock(); + + void testFieldGetter0(); + void testFieldGetterN(); + + void testNonTrivialFieldGetter0(); + void testNonTrivialFieldGetterN(); }; diff --git a/src/vm/IsValidObject.cpp b/src/vm/IsValidObject.cpp index 3ee5740c..cfd73711 100644 --- a/src/vm/IsValidObject.cpp +++ b/src/vm/IsValidObject.cpp @@ -38,6 +38,7 @@ void* vt_safe_bin_primitive; void* vt_safe_ter_primitive; void* vt_literal_return; void* vt_global_return; +void* vt_getter; void* vt_string; void* vt_symbol; @@ -71,7 +72,7 @@ bool IsValidObject(vm_oop_t obj) { vt == vt_primitive || vt == vt_safe_un_primitive || vt == vt_safe_bin_primitive || vt == vt_safe_ter_primitive || vt == vt_string || vt == vt_symbol || vt == vt_literal_return || - vt == vt_global_return; + vt == vt_global_return || vt == vt_getter; if (!b) { assert(b && "Expected vtable to be one of the known ones."); return false; @@ -112,6 +113,7 @@ void set_vt_to_null() { vt_safe_ter_primitive = nullptr; vt_literal_return = nullptr; vt_global_return = nullptr; + vt_getter = nullptr; vt_string = nullptr; vt_symbol = nullptr; } @@ -145,6 +147,11 @@ bool IsGlobalReturn(vm_oop_t obj) { return get_vtable(AS_OBJ(obj)) == vt_global_return; } +bool IsGetter(vm_oop_t obj) { + assert(vt_getter != nullptr); + return get_vtable(AS_OBJ(obj)) == vt_getter; +} + void obtain_vtables_of_known_classes(VMSymbol* someValidSymbol) { // These objects are allocated on the heap. So, they will get GC'ed soon // enough. @@ -199,6 +206,10 @@ void obtain_vtables_of_known_classes(VMSymbol* someValidSymbol) { VMGlobalReturn(someValidSymbol, v, someValidSymbol); vt_global_return = get_vtable(gr); + VMGetter* get = + new (GetHeap(), 0) VMGetter(someValidSymbol, v, 0); + vt_getter = get_vtable(get); + VMString* str = new (GetHeap(), PADDED_SIZE(1)) VMString(0, nullptr); vt_string = get_vtable(str); diff --git a/src/vm/IsValidObject.h b/src/vm/IsValidObject.h index e81495d4..710d55d6 100644 --- a/src/vm/IsValidObject.h +++ b/src/vm/IsValidObject.h @@ -10,6 +10,7 @@ bool IsVMMethod(vm_oop_t obj); bool IsVMSymbol(vm_oop_t obj); bool IsLiteralReturn(vm_oop_t obj); bool IsGlobalReturn(vm_oop_t obj); +bool IsGetter(vm_oop_t obj); void set_vt_to_null(); diff --git a/src/vmobjects/ObjectFormats.h b/src/vmobjects/ObjectFormats.h index 0bf8ac37..684c4fcf 100644 --- a/src/vmobjects/ObjectFormats.h +++ b/src/vmobjects/ObjectFormats.h @@ -93,6 +93,7 @@ class VMSafeTernaryPrimitive; class VMTrivialMethod; class VMLiteralReturn; class VMGlobalReturn; +class VMGetter; class VMString; class VMSymbol; @@ -150,12 +151,13 @@ class GCMethod : public GCInvokable { public: typedef VMMethod class GCPrimitive : public GCInvokable { public: typedef VMPrimitive Loaded; }; class GCEvaluationPrimitive : public GCInvokable { public: typedef VMEvaluationPrimitive Loaded; }; class GCSafePrimitive : public GCInvokable { public: typedef VMSafePrimitive Loaded; }; -class GCSafeUnaryPrimitive : public GCSafePrimitive { public: typedef VMSafeUnaryPrimitive Loaded; }; +class GCSafeUnaryPrimitive : public GCSafePrimitive { public: typedef VMSafeUnaryPrimitive Loaded; }; class GCSafeBinaryPrimitive : public GCSafePrimitive { public: typedef VMSafeBinaryPrimitive Loaded; }; -class GCSafeTernaryPrimitive : public GCSafePrimitive { public: typedef VMSafeTernaryPrimitive Loaded; }; +class GCSafeTernaryPrimitive : public GCSafePrimitive { public: typedef VMSafeTernaryPrimitive Loaded; }; class GCTrivialMethod : public GCInvokable { public: typedef VMTrivialMethod Loaded; }; -class GCLiteralReturn : public GCTrivialMethod { public: typedef VMLiteralReturn Loaded; }; -class GCGlobalReturn : public GCTrivialMethod { public: typedef VMGlobalReturn Loaded; }; +class GCLiteralReturn : public GCTrivialMethod { public: typedef VMLiteralReturn Loaded; }; +class GCGlobalReturn : public GCTrivialMethod { public: typedef VMGlobalReturn Loaded; }; +class GCGetter : public GCTrivialMethod { public: typedef VMGetter Loaded; }; class GCString : public GCAbstractObject { public: typedef VMString Loaded; }; class GCSymbol : public GCString { public: typedef VMSymbol Loaded; }; // clang-format on diff --git a/src/vmobjects/VMMethod.cpp b/src/vmobjects/VMMethod.cpp index 64f48bfa..0fc4ec3f 100644 --- a/src/vmobjects/VMMethod.cpp +++ b/src/vmobjects/VMMethod.cpp @@ -233,9 +233,7 @@ void VMMethod::inlineInto(MethodGenerationContext& mgenc) { if (bytecode == BC_PUSH_FIELD || bytecode == BC_PUSH_FIELD_0 || bytecode == BC_PUSH_FIELD_1) { - EmitPushFieldWithIndex( - mgenc, idx, - 0 /* dummy, self is looked up dynamically at the moment. */); + EmitPushFieldWithIndex(mgenc, idx); } else { EmitPopFieldWithIndex( mgenc, idx, diff --git a/src/vmobjects/VMObject.h b/src/vmobjects/VMObject.h index 34279146..bfdcdae1 100644 --- a/src/vmobjects/VMObject.h +++ b/src/vmobjects/VMObject.h @@ -74,6 +74,7 @@ class VMObject : public AbstractVMObject { inline long GetNumberOfFields() const override; inline vm_oop_t GetField(size_t index) const { + assert(numberOfFields > index); vm_oop_t result = load_ptr(FIELDS[index]); assert(IsValidObject(result)); return result; diff --git a/src/vmobjects/VMTrivialMethod.cpp b/src/vmobjects/VMTrivialMethod.cpp index f5da14f0..07ab80ff 100644 --- a/src/vmobjects/VMTrivialMethod.cpp +++ b/src/vmobjects/VMTrivialMethod.cpp @@ -1,5 +1,7 @@ #include "VMTrivialMethod.h" +#include +#include #include #include @@ -30,6 +32,14 @@ VMTrivialMethod* MakeGlobalReturn(VMSymbol* sig, vector& arguments, return result; } +VMTrivialMethod* MakeGetter(VMSymbol* sig, vector& arguments, + size_t fieldIndex) { + VMGetter* result = + new (GetHeap(), 0) VMGetter(sig, arguments, fieldIndex); + LOG_ALLOCATION("VMGetter", result->GetObjectSize()); + return result; +} + VMFrame* VMLiteralReturn::Invoke(Interpreter*, VMFrame* frame) { for (int i = 0; i < numberOfArguments; i += 1) { frame->Pop(); @@ -92,3 +102,41 @@ AbstractVMObject* VMGlobalReturn::CloneForMovingGC() const { new (GetHeap(), 0 ALLOC_MATURE) VMGlobalReturn(*this); return prim; } + +VMFrame* VMGetter::Invoke(Interpreter*, VMFrame* frame) { + vm_oop_t self = nullptr; + for (int i = 0; i < numberOfArguments; i += 1) { + self = frame->Pop(); + } + + assert(self != nullptr); + + vm_oop_t result; + if (unlikely(IS_TAGGED(self))) { + result = nullptr; + Universe()->ErrorExit("Integers do not have fields!"); + } else { + result = ((VMObject*)self)->GetField(fieldIndex); + } + + frame->Push(result); + + return nullptr; +} + +void VMGetter::InlineInto(MethodGenerationContext& mgenc, bool) { + EmitPushFieldWithIndex(mgenc, fieldIndex); +} + +AbstractVMObject* VMGetter::CloneForMovingGC() const { + VMGetter* prim = new (GetHeap(), 0 ALLOC_MATURE) VMGetter(*this); + return prim; +} + +void VMGetter::WalkObjects(walk_heap_fn walk) { + VMInvokable::WalkObjects(walk); +} + +std::string VMGetter::AsDebugString() const { + return "VMGetter(fieldIndex: " + to_string(fieldIndex) + ")"; +} diff --git a/src/vmobjects/VMTrivialMethod.h b/src/vmobjects/VMTrivialMethod.h index 57c506eb..f90b8a01 100644 --- a/src/vmobjects/VMTrivialMethod.h +++ b/src/vmobjects/VMTrivialMethod.h @@ -44,6 +44,8 @@ VMTrivialMethod* MakeLiteralReturn(VMSymbol* sig, vector& arguments, vm_oop_t literal); VMTrivialMethod* MakeGlobalReturn(VMSymbol* sig, vector& arguments, VMSymbol* globalName); +VMTrivialMethod* MakeGetter(VMSymbol* sig, vector& arguments, + size_t fieldIndex); class VMLiteralReturn : public VMTrivialMethod { public: @@ -124,3 +126,38 @@ class VMGlobalReturn : public VMTrivialMethod { GCSymbol* globalName; int numberOfArguments; }; + +class VMGetter : public VMTrivialMethod { +public: + typedef GCGetter Stored; + + VMGetter(VMSymbol* sig, vector& arguments, size_t fieldIndex) + : VMTrivialMethod(sig, arguments), fieldIndex(fieldIndex), + numberOfArguments(Signature::GetNumberOfArguments(sig)) { + write_barrier(this, sig); + } + + inline size_t GetObjectSize() const override { + return sizeof(VMLiteralReturn); + } + + VMFrame* Invoke(Interpreter*, VMFrame*) override; + void InlineInto(MethodGenerationContext& mgenc, + bool mergeScope = true) final; + + AbstractVMObject* CloneForMovingGC() const final; + + void MarkObjectAsInvalid() final { VMTrivialMethod::MarkObjectAsInvalid(); } + + bool IsMarkedInvalid() const final { + return signature == (GCSymbol*)INVALID_GC_POINTER; + } + + void WalkObjects(walk_heap_fn) override; + + std::string AsDebugString() const final; + +private: + size_t fieldIndex; + int numberOfArguments; +}; From 505d4ecc4da247e478aafd5f361110202afa7d71 Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Tue, 6 Aug 2024 12:04:01 +0100 Subject: [PATCH 7/7] Implement handling of setters Signed-off-by: Stefan Marr --- src/compiler/MethodGenerationContext.cpp | 85 +++++++++++-- src/compiler/MethodGenerationContext.h | 8 +- src/interpreter/Interpreter.cpp | 15 ++- src/unitTests/BytecodeGenerationTest.cpp | 7 +- src/unitTests/TrivialMethodTest.cpp | 146 +++++++++++++++-------- src/unitTests/TrivialMethodTest.h | 14 +++ src/vm/IsValidObject.cpp | 13 +- src/vm/IsValidObject.h | 1 + src/vmobjects/ObjectFormats.h | 2 + src/vmobjects/VMTrivialMethod.cpp | 52 ++++++++ src/vmobjects/VMTrivialMethod.h | 44 ++++++- 11 files changed, 307 insertions(+), 80 deletions(-) diff --git a/src/compiler/MethodGenerationContext.cpp b/src/compiler/MethodGenerationContext.cpp index 4be726a3..0602190e 100644 --- a/src/compiler/MethodGenerationContext.cpp +++ b/src/compiler/MethodGenerationContext.cpp @@ -90,13 +90,13 @@ VMInvokable* MethodGenerationContext::Assemble() { } VMTrivialMethod* MethodGenerationContext::assembleTrivialMethod() { - if (lastBytecodeIs(0, BC_RETURN_LOCAL)) { + if (LastBytecodeIs(0, BC_RETURN_LOCAL)) { uint8_t pushCandidate = lastBytecodeIsOneOf(1, &IsPushConstBytecode); if (pushCandidate != BC_INVALID) { return assembleLiteralReturn(pushCandidate); } - if (lastBytecodeIs(1, BC_PUSH_GLOBAL)) { + if (LastBytecodeIs(1, BC_PUSH_GLOBAL)) { return assembleGlobalReturn(); } @@ -106,8 +106,8 @@ VMTrivialMethod* MethodGenerationContext::assembleTrivialMethod() { } } - // because we check for return_self here, we don't consider block methods - if (lastBytecodeIs(0, BC_PUSH_SELF)) { + // because we check for returning self here, we don't consider block methods + if (LastBytecodeIs(0, BC_RETURN_SELF)) { assert(!IsBlockMethod()); return assembleFieldSetter(); } @@ -192,6 +192,67 @@ VMTrivialMethod* MethodGenerationContext::assembleFieldGetter( return MakeGetter(signature, arguments, fieldIndex); } +VMTrivialMethod* MethodGenerationContext::assembleFieldSetter() { + uint8_t popCandidate = lastBytecodeIsOneOf(1, IsPopFieldBytecode); + if (popCandidate == BC_INVALID) { + return nullptr; + } + + uint8_t pushCandidate = lastBytecodeIsOneOf(2, IsPushArgBytecode); + if (pushCandidate == BC_INVALID) { + return nullptr; + } + + size_t lenReturnSelf = Bytecode::GetBytecodeLength(BC_RETURN_SELF); + size_t lenInclPop = + lenReturnSelf + Bytecode::GetBytecodeLength(popCandidate); + if (bytecode.size() != + (lenInclPop + Bytecode::GetBytecodeLength(pushCandidate))) { + return nullptr; + } + + size_t argIndex = 0; + + switch (pushCandidate) { + case BC_PUSH_SELF: + argIndex = 0; + break; + case BC_PUSH_ARG_1: + argIndex = 1; + break; + case BC_PUSH_ARG_2: + argIndex = 2; + break; + case BC_PUSH_ARGUMENT: { + argIndex = bytecode.at(bytecode.size() - (lenInclPop + 2)); + break; + } + default: { + GetUniverse()->ErrorExit("Unexpected bytecode"); + } + } + + size_t fieldIndex = 0; + + switch (popCandidate) { + case BC_POP_FIELD_0: + fieldIndex = 0; + break; + case BC_POP_FIELD_1: + fieldIndex = 1; + break; + case BC_POP_FIELD: { + fieldIndex = bytecode.at(bytecode.size() - (lenReturnSelf + 1)); + break; + } + default: { + GetUniverse()->ErrorExit("Unexpected bytecode"); + } + } + + return MakeSetter(signature, arguments, fieldIndex, argIndex); +} + VMPrimitive* MethodGenerationContext::AssemblePrimitive(bool classSide) { return VMPrimitive::GetEmptyPrimitive(signature, classSide); } @@ -354,7 +415,7 @@ uint8_t MethodGenerationContext::lastBytecodeAt(size_t indexFromEnd) { return last4Bytecodes[3 - indexFromEnd]; } -bool MethodGenerationContext::lastBytecodeIs(size_t indexFromEnd, +bool MethodGenerationContext::LastBytecodeIs(size_t indexFromEnd, uint8_t bytecode) { assert(indexFromEnd >= 0 && indexFromEnd < NUM_LAST_BYTECODES); uint8_t actual = last4Bytecodes[3 - indexFromEnd]; @@ -381,15 +442,15 @@ void MethodGenerationContext::removeLastBytecodes(size_t numBytecodes) { } bool MethodGenerationContext::hasOneLiteralBlockArgument() { - return lastBytecodeIs(0, BC_PUSH_BLOCK); + return LastBytecodeIs(0, BC_PUSH_BLOCK); } bool MethodGenerationContext::hasTwoLiteralBlockArguments() { - if (!lastBytecodeIs(0, BC_PUSH_BLOCK)) { + if (!LastBytecodeIs(0, BC_PUSH_BLOCK)) { return false; } - return lastBytecodeIs(1, BC_PUSH_BLOCK); + return LastBytecodeIs(1, BC_PUSH_BLOCK); } /** @@ -748,13 +809,13 @@ void MethodGenerationContext::removeLastBytecodeAt(size_t indexFromEnd) { } void MethodGenerationContext::RemoveLastPopForBlockLocalReturn() { - if (lastBytecodeIs(0, BC_POP)) { + if (LastBytecodeIs(0, BC_POP)) { bytecode.pop_back(); return; } if (lastBytecodeIsOneOf(0, IsPopSmthBytecode) && - !lastBytecodeIs(1, BC_DUP)) { + !LastBytecodeIs(1, BC_DUP)) { // we just removed the DUP and didn't emit the POP using // optimizeDupPopPopSequence() so, to make blocks work, we need to // reintroduce the DUP @@ -793,7 +854,7 @@ bool MethodGenerationContext::OptimizeDupPopPopSequence() { return false; } - if (lastBytecodeIs(0, BC_INC_FIELD_PUSH)) { + if (LastBytecodeIs(0, BC_INC_FIELD_PUSH)) { return optimizeIncFieldPush(); } @@ -802,7 +863,7 @@ bool MethodGenerationContext::OptimizeDupPopPopSequence() { return false; } - if (!lastBytecodeIs(1, BC_DUP)) { + if (!LastBytecodeIs(1, BC_DUP)) { return false; } diff --git a/src/compiler/MethodGenerationContext.h b/src/compiler/MethodGenerationContext.h index 4a1e2b87..ba4ea416 100644 --- a/src/compiler/MethodGenerationContext.h +++ b/src/compiler/MethodGenerationContext.h @@ -116,12 +116,14 @@ class MethodGenerationContext { bool OptimizeDupPopPopSequence(); + bool LastBytecodeIs(size_t indexFromEnd, uint8_t bytecode); + private: VMTrivialMethod* assembleTrivialMethod(); VMTrivialMethod* assembleLiteralReturn(uint8_t pushCandidate); VMTrivialMethod* assembleGlobalReturn(); VMTrivialMethod* assembleFieldGetter(uint8_t pushCandidate); - VMTrivialMethod* assembleFieldSetter() { return nullptr; } + VMTrivialMethod* assembleFieldSetter(); VMTrivialMethod* assembleFieldGetterFromReturn(uint8_t pushCandidate) { return nullptr; } @@ -137,10 +139,10 @@ class MethodGenerationContext { bool hasOneLiteralBlockArgument(); bool hasTwoLiteralBlockArguments(); uint8_t lastBytecodeAt(size_t indexFromEnd); - bool lastBytecodeIs(size_t indexFromEnd, uint8_t bytecode); + uint8_t lastBytecodeIsOneOf(size_t indexFromEnd, uint8_t (*predicate)(uint8_t)); - + size_t getOffsetOfLastBytecode(size_t indexFromEnd); std::tuple diff --git a/src/interpreter/Interpreter.cpp b/src/interpreter/Interpreter.cpp index ef3b5abd..6c32cf9f 100644 --- a/src/interpreter/Interpreter.cpp +++ b/src/interpreter/Interpreter.cpp @@ -208,14 +208,17 @@ void Interpreter::doPushLocalWithIndex(uint8_t localIndex) { } void Interpreter::doPushArgument(long bytecodeIndex) { - uint8_t bc1 = method->GetBytecode(bytecodeIndex + 1); - uint8_t bc2 = method->GetBytecode(bytecodeIndex + 2); + uint8_t argIndex = method->GetBytecode(bytecodeIndex + 1); + uint8_t contextLevel = method->GetBytecode(bytecodeIndex + 2); - assert(!(bc1 == 0 && bc2 == 0 && "should have been BC_PUSH_SELF")); - assert(!(bc1 == 1 && bc2 == 0 && "should have been BC_PUSH_ARG_1")); - assert(!(bc1 == 2 && bc2 == 0 && "should have been BC_PUSH_ARG_2")); + assert(!(argIndex == 0 && contextLevel == 0 && + "should have been BC_PUSH_SELF")); + assert(!(argIndex == 1 && contextLevel == 0 && + "should have been BC_PUSH_ARG_1")); + assert(!(argIndex == 2 && contextLevel == 0 && + "should have been BC_PUSH_ARG_2")); - vm_oop_t argument = GetFrame()->GetArgument(bc1, bc2); + vm_oop_t argument = GetFrame()->GetArgument(argIndex, contextLevel); GetFrame()->Push(argument); } diff --git a/src/unitTests/BytecodeGenerationTest.cpp b/src/unitTests/BytecodeGenerationTest.cpp index 1b875f6d..3b38301f 100644 --- a/src/unitTests/BytecodeGenerationTest.cpp +++ b/src/unitTests/BytecodeGenerationTest.cpp @@ -554,7 +554,7 @@ void BytecodeGenerationTest::ifArg(std::string selector, int8_t jumpBytecode) { check(bytecodes, {BC_PUSH_CONSTANT_0, BC_POP, BC_PUSH_SELF, BC(BC_SEND, 1), BC(jumpBytecode, 4, 0), BC_PUSH_ARG_1, BC_POP, BC_PUSH_CONSTANT_2, - BC_POP, BC_PUSH_SELF, BC_RETURN_LOCAL}); + BC_RETURN_SELF}); tearDown(); } @@ -568,8 +568,7 @@ void BytecodeGenerationTest::testKeywordIfTrueArg() { check(bytecodes, {BC_PUSH_CONSTANT_0, BC_POP, BC_PUSH_SELF, BC_PUSH_CONSTANT_1, BC(BC_SEND, 2), BC(BC_JUMP_ON_FALSE_TOP_NIL, 4, 0), BC_PUSH_ARG_1, - BC_POP, BC(BC_PUSH_CONSTANT, 3), BC_POP, BC_PUSH_SELF, - BC_RETURN_LOCAL}); + BC_POP, BC(BC_PUSH_CONSTANT, 3), BC_RETURN_SELF}); } void BytecodeGenerationTest::testIfReturnNonLocal() { @@ -591,7 +590,7 @@ void BytecodeGenerationTest::ifReturnNonLocal(std::string selector, check(bytecodes, {BC_PUSH_CONSTANT_0, BC_POP, BC_PUSH_SELF, BC(BC_SEND, 1), BC(jumpBytecode, 5, 0), BC_PUSH_ARG_1, BC_RETURN_LOCAL, BC_POP, - BC_PUSH_CONSTANT_2, BC_POP, BC_PUSH_SELF, BC_RETURN_LOCAL}); + BC_PUSH_CONSTANT_2, BC_RETURN_SELF}); tearDown(); } diff --git a/src/unitTests/TrivialMethodTest.cpp b/src/unitTests/TrivialMethodTest.cpp index bd0bae0e..e762c87e 100644 --- a/src/unitTests/TrivialMethodTest.cpp +++ b/src/unitTests/TrivialMethodTest.cpp @@ -1,11 +1,13 @@ #include "TrivialMethodTest.h" #include +#include #include #include "../compiler/MethodGenerationContext.h" #include "../vm/IsValidObject.h" #include "../vmobjects/VMInvokable.h" +#include "../vmobjects/VMTrivialMethod.h" void TrivialMethodTest::literalReturn(std::string source) { std::string s = "test = ( ^ " + source + " )"; @@ -202,54 +204,96 @@ void TrivialMethodTest::testNonTrivialFieldGetterN() { CPPUNIT_ASSERT_MESSAGE(expected.data(), result); } -/* - @pytest.mark.parametrize( - "source", ["field := val", "field := val.", "field := val. ^ self"] - ) - def test_field_setter_0(cgenc, mgenc, source): - add_field(cgenc, "field") - body_or_none = parse_method(mgenc, "test: val = ( " + source + " )") - m = mgenc.assemble(body_or_none) - assert isinstance(m, FieldWrite) - - - @pytest.mark.parametrize( - "source", ["field := value", "field := value.", "field := value. ^ self"] - ) - def test_field_setter_n(cgenc, mgenc, source): - add_field(cgenc, "a") - add_field(cgenc, "b") - add_field(cgenc, "c") - add_field(cgenc, "d") - add_field(cgenc, "e") - add_field(cgenc, "field") - body_or_none = parse_method(mgenc, "test: value = ( " + source + " )") - m = mgenc.assemble(body_or_none) - assert isinstance(m, FieldWrite) - - - def test_non_trivial_field_setter_0(cgenc, mgenc): - add_field(cgenc, "field") - body_or_none = parse_method(mgenc, "test: val = ( 0. field := value )") - m = mgenc.assemble(body_or_none) - assert isinstance(m, AstMethod) or isinstance(m, BcMethod) - - - def test_non_trivial_field_setter_n(cgenc, mgenc): - add_field(cgenc, "a") - add_field(cgenc, "b") - add_field(cgenc, "c") - add_field(cgenc, "d") - add_field(cgenc, "e") - add_field(cgenc, "field") - body_or_none = parse_method(mgenc, "test: val = ( 0. field := value )") - m = mgenc.assemble(body_or_none) - assert isinstance(m, AstMethod) or isinstance(m, BcMethod) - - def test_block_return(mgenc): - body_or_none = parse_method(mgenc, "test = ( ^ [] )") - m = mgenc.assemble(body_or_none) - assert isinstance(m, AstMethod) or isinstance(m, BcMethod) - - - */ +void TrivialMethodTest::testFieldSetter0() { + fieldSetter0("field := val"); + fieldSetter0("field := val."); + fieldSetter0("field := val. ^ self"); +} + +void TrivialMethodTest::fieldSetter0(std::string source) { + addField("field"); + std::string s = "test: val = ( " + source + " )"; + methodToBytecode(s.data()); + + VMInvokable* m = _mgenc->Assemble(); + + std::string expected = "Expected to be trivial: " + s; + bool result = IsSetter(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); + + VMSetter* setter = (VMSetter*)m; + CPPUNIT_ASSERT_EQUAL(setter->fieldIndex, (size_t)0); + CPPUNIT_ASSERT_EQUAL(setter->argIndex, (size_t)1); + CPPUNIT_ASSERT_EQUAL(setter->numberOfArguments, 2); + + tearDown(); +} + +void TrivialMethodTest::testFieldSetterN() { + fieldSetterN("field := arg2"); + fieldSetterN("field := arg2."); + fieldSetterN("field := arg2. ^ self"); +} + +void TrivialMethodTest::fieldSetterN(std::string source) { + addField("a"); + addField("b"); + addField("c"); + addField("d"); + addField("e"); + addField("field"); + std::string s = "a: arg1 b: arg2 c: arg3 = ( " + source + " )"; + methodToBytecode(s.data()); + + VMInvokable* m = _mgenc->Assemble(); + + std::string expected = "Expected to be trivial: " + s; + bool result = IsSetter(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); + + VMSetter* setter = (VMSetter*)m; + CPPUNIT_ASSERT_EQUAL(setter->fieldIndex, (size_t)5); + CPPUNIT_ASSERT_EQUAL(setter->argIndex, (size_t)2); + CPPUNIT_ASSERT_EQUAL(setter->numberOfArguments, 4); + + tearDown(); +} + +void TrivialMethodTest::testNonTrivialFieldSetter0() { + addField("field"); + std::string s = "test: val = ( 0. field := val )"; + methodToBytecode(s.data()); + + VMInvokable* m = _mgenc->Assemble(); + + std::string expected = "Expected to be non-trivial: " + s; + bool result = !IsSetter(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); +} + +void TrivialMethodTest::testNonTrivialFieldSetterN() { + addField("a"); + addField("b"); + addField("c"); + addField("d"); + addField("e"); + addField("field"); + std::string s = "test: val = ( 0. field := val )"; + methodToBytecode(s.data()); + + VMInvokable* m = _mgenc->Assemble(); + + std::string expected = "Expected to be non-trivial: " + s; + bool result = !IsSetter(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); +} + +void TrivialMethodTest::testBlockReturn() { + methodToBytecode("test = ( ^ [] )"); + + VMInvokable* m = _mgenc->Assemble(); + + std::string expected = "Expected to be non-trivial: test = ( ^ [] )"; + bool result = IsVMMethod(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); +} diff --git a/src/unitTests/TrivialMethodTest.h b/src/unitTests/TrivialMethodTest.h index 7c1c847b..13bbac1e 100644 --- a/src/unitTests/TrivialMethodTest.h +++ b/src/unitTests/TrivialMethodTest.h @@ -17,6 +17,11 @@ class TrivialMethodTest : public TestWithParsing { CPPUNIT_TEST(testFieldGetterN); CPPUNIT_TEST(testNonTrivialFieldGetter0); CPPUNIT_TEST(testNonTrivialFieldGetterN); + CPPUNIT_TEST(testFieldSetter0); + CPPUNIT_TEST(testFieldSetterN); + CPPUNIT_TEST(testNonTrivialFieldSetter0); + CPPUNIT_TEST(testNonTrivialFieldSetterN); + CPPUNIT_TEST(testBlockReturn); CPPUNIT_TEST_SUITE_END(); private: @@ -43,4 +48,13 @@ class TrivialMethodTest : public TestWithParsing { void testNonTrivialFieldGetter0(); void testNonTrivialFieldGetterN(); + + void testFieldSetter0(); + void fieldSetter0(std::string source); + void testFieldSetterN(); + void fieldSetterN(std::string source); + void testNonTrivialFieldSetter0(); + void testNonTrivialFieldSetterN(); + + void testBlockReturn(); }; diff --git a/src/vm/IsValidObject.cpp b/src/vm/IsValidObject.cpp index cfd73711..92934211 100644 --- a/src/vm/IsValidObject.cpp +++ b/src/vm/IsValidObject.cpp @@ -39,6 +39,7 @@ void* vt_safe_ter_primitive; void* vt_literal_return; void* vt_global_return; void* vt_getter; +void* vt_setter; void* vt_string; void* vt_symbol; @@ -72,7 +73,7 @@ bool IsValidObject(vm_oop_t obj) { vt == vt_primitive || vt == vt_safe_un_primitive || vt == vt_safe_bin_primitive || vt == vt_safe_ter_primitive || vt == vt_string || vt == vt_symbol || vt == vt_literal_return || - vt == vt_global_return || vt == vt_getter; + vt == vt_global_return || vt == vt_getter || vt == vt_setter; if (!b) { assert(b && "Expected vtable to be one of the known ones."); return false; @@ -114,6 +115,7 @@ void set_vt_to_null() { vt_literal_return = nullptr; vt_global_return = nullptr; vt_getter = nullptr; + vt_setter = nullptr; vt_string = nullptr; vt_symbol = nullptr; } @@ -152,6 +154,11 @@ bool IsGetter(vm_oop_t obj) { return get_vtable(AS_OBJ(obj)) == vt_getter; } +bool IsSetter(vm_oop_t obj) { + assert(vt_setter != nullptr); + return get_vtable(AS_OBJ(obj)) == vt_setter; +} + void obtain_vtables_of_known_classes(VMSymbol* someValidSymbol) { // These objects are allocated on the heap. So, they will get GC'ed soon // enough. @@ -210,6 +217,10 @@ void obtain_vtables_of_known_classes(VMSymbol* someValidSymbol) { new (GetHeap(), 0) VMGetter(someValidSymbol, v, 0); vt_getter = get_vtable(get); + VMSetter* set = + new (GetHeap(), 0) VMSetter(someValidSymbol, v, 0, 0); + vt_setter = get_vtable(set); + VMString* str = new (GetHeap(), PADDED_SIZE(1)) VMString(0, nullptr); vt_string = get_vtable(str); diff --git a/src/vm/IsValidObject.h b/src/vm/IsValidObject.h index 710d55d6..31da1cc7 100644 --- a/src/vm/IsValidObject.h +++ b/src/vm/IsValidObject.h @@ -11,6 +11,7 @@ bool IsVMSymbol(vm_oop_t obj); bool IsLiteralReturn(vm_oop_t obj); bool IsGlobalReturn(vm_oop_t obj); bool IsGetter(vm_oop_t obj); +bool IsSetter(vm_oop_t obj); void set_vt_to_null(); diff --git a/src/vmobjects/ObjectFormats.h b/src/vmobjects/ObjectFormats.h index 684c4fcf..6e7ca576 100644 --- a/src/vmobjects/ObjectFormats.h +++ b/src/vmobjects/ObjectFormats.h @@ -94,6 +94,7 @@ class VMTrivialMethod; class VMLiteralReturn; class VMGlobalReturn; class VMGetter; +class VMSetter; class VMString; class VMSymbol; @@ -158,6 +159,7 @@ class GCTrivialMethod : public GCInvokable { public: typedef VMTrivialMeth class GCLiteralReturn : public GCTrivialMethod { public: typedef VMLiteralReturn Loaded; }; class GCGlobalReturn : public GCTrivialMethod { public: typedef VMGlobalReturn Loaded; }; class GCGetter : public GCTrivialMethod { public: typedef VMGetter Loaded; }; +class GCSetter : public GCTrivialMethod { public: typedef VMSetter Loaded; }; class GCString : public GCAbstractObject { public: typedef VMString Loaded; }; class GCSymbol : public GCString { public: typedef VMSymbol Loaded; }; // clang-format on diff --git a/src/vmobjects/VMTrivialMethod.cpp b/src/vmobjects/VMTrivialMethod.cpp index 07ab80ff..6aee2bef 100644 --- a/src/vmobjects/VMTrivialMethod.cpp +++ b/src/vmobjects/VMTrivialMethod.cpp @@ -40,6 +40,14 @@ VMTrivialMethod* MakeGetter(VMSymbol* sig, vector& arguments, return result; } +VMTrivialMethod* MakeSetter(VMSymbol* sig, vector& arguments, + size_t fieldIndex, size_t argIndex) { + VMSetter* result = new (GetHeap(), 0) + VMSetter(sig, arguments, fieldIndex, argIndex); + LOG_ALLOCATION("VMSetter", result->GetObjectSize()); + return result; +} + VMFrame* VMLiteralReturn::Invoke(Interpreter*, VMFrame* frame) { for (int i = 0; i < numberOfArguments; i += 1) { frame->Pop(); @@ -140,3 +148,47 @@ void VMGetter::WalkObjects(walk_heap_fn walk) { std::string VMGetter::AsDebugString() const { return "VMGetter(fieldIndex: " + to_string(fieldIndex) + ")"; } + +VMFrame* VMSetter::Invoke(Interpreter*, VMFrame* frame) { + vm_oop_t value = nullptr; + vm_oop_t self = nullptr; + + for (size_t i = numberOfArguments - 1; i > 0; i -= 1) { + if (i == argIndex) { + value = frame->Pop(); + } else { + frame->Pop(); + } + } + + self = frame->Top(); + assert(self != nullptr); + assert(value != nullptr); + + if (unlikely(IS_TAGGED(self))) { + Universe()->ErrorExit("Integers do not have fields!"); + } else { + ((VMObject*)self)->SetField(fieldIndex, value); + } + + return nullptr; +} + +void VMSetter::InlineInto(MethodGenerationContext& mgenc, bool) { + GetUniverse()->ErrorExit( + "We don't currently support blocks for trivial setters"); +} + +AbstractVMObject* VMSetter::CloneForMovingGC() const { + VMSetter* prim = new (GetHeap(), 0 ALLOC_MATURE) VMSetter(*this); + return prim; +} + +void VMSetter::WalkObjects(walk_heap_fn walk) { + VMInvokable::WalkObjects(walk); +} + +std::string VMSetter::AsDebugString() const { + return "VMSetter(fieldIndex: " + to_string(fieldIndex) + + ", argIndex: " + to_string(argIndex) + ")"; +} diff --git a/src/vmobjects/VMTrivialMethod.h b/src/vmobjects/VMTrivialMethod.h index f90b8a01..f65e6569 100644 --- a/src/vmobjects/VMTrivialMethod.h +++ b/src/vmobjects/VMTrivialMethod.h @@ -46,6 +46,8 @@ VMTrivialMethod* MakeGlobalReturn(VMSymbol* sig, vector& arguments, VMSymbol* globalName); VMTrivialMethod* MakeGetter(VMSymbol* sig, vector& arguments, size_t fieldIndex); +VMTrivialMethod* MakeSetter(VMSymbol* sig, vector& arguments, + size_t fieldIndex, size_t argIndex); class VMLiteralReturn : public VMTrivialMethod { public: @@ -100,7 +102,7 @@ class VMGlobalReturn : public VMTrivialMethod { } inline size_t GetObjectSize() const override { - return sizeof(VMLiteralReturn); + return sizeof(VMGlobalReturn); } VMFrame* Invoke(Interpreter*, VMFrame*) override; @@ -137,10 +139,43 @@ class VMGetter : public VMTrivialMethod { write_barrier(this, sig); } - inline size_t GetObjectSize() const override { - return sizeof(VMLiteralReturn); + inline size_t GetObjectSize() const override { return sizeof(VMGetter); } + + VMFrame* Invoke(Interpreter*, VMFrame*) override; + void InlineInto(MethodGenerationContext& mgenc, + bool mergeScope = true) final; + + AbstractVMObject* CloneForMovingGC() const final; + + void MarkObjectAsInvalid() final { VMTrivialMethod::MarkObjectAsInvalid(); } + + bool IsMarkedInvalid() const final { + return signature == (GCSymbol*)INVALID_GC_POINTER; + } + + void WalkObjects(walk_heap_fn) override; + + std::string AsDebugString() const final; + +private: + size_t fieldIndex; + int numberOfArguments; +}; + +class VMSetter : public VMTrivialMethod { +public: + typedef GCSetter Stored; + + VMSetter(VMSymbol* sig, vector& arguments, size_t fieldIndex, + size_t argIndex) + : VMTrivialMethod(sig, arguments), fieldIndex(fieldIndex), + argIndex(argIndex), + numberOfArguments(Signature::GetNumberOfArguments(sig)) { + write_barrier(this, sig); } + inline size_t GetObjectSize() const override { return sizeof(VMSetter); } + VMFrame* Invoke(Interpreter*, VMFrame*) override; void InlineInto(MethodGenerationContext& mgenc, bool mergeScope = true) final; @@ -158,6 +193,9 @@ class VMGetter : public VMTrivialMethod { std::string AsDebugString() const final; private: + make_testable(public); + size_t fieldIndex; + size_t argIndex; int numberOfArguments; };