diff --git a/SOM.xcodeproj/project.pbxproj b/SOM.xcodeproj/project.pbxproj index 06af9b7b..498dbaf8 100644 --- a/SOM.xcodeproj/project.pbxproj +++ b/SOM.xcodeproj/project.pbxproj @@ -90,6 +90,10 @@ 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 */; }; + 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 */; }; 0A67EA7819ACD43A00830E3B /* WalkObjectsTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0A67EA7319ACD43A00830E3B /* WalkObjectsTest.cpp */; }; @@ -225,6 +229,12 @@ 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 = ""; }; + 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; }; 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 = ""; }; @@ -449,6 +459,10 @@ 0A67EA7419ACD43A00830E3B /* WriteBarrierTest.cpp */, 0A1C98562C3DD87300735850 /* unitTests/BytecodeGenerationTest.h */, 0A1C98572C3DD88500735850 /* unitTests/BytecodeGenerationTest.cpp */, + 0A5A7E982C617E2B0011C783 /* TrivialMethodTest.h */, + 0A5A7E992C617E400011C783 /* TrivialMethodTest.cpp */, + 0A5A7E9B2C617EC70011C783 /* TestWithParsing.h */, + 0A5A7E9C2C617EE00011C783 /* TestWithParsing.cpp */, ); name = unittests; sourceTree = ""; @@ -671,6 +685,8 @@ 3F5203580FA6624C00E75857 /* VMSymbol.h */, 0A5A7E8F2C5D30390011C783 /* VMSafePrimitive.h */, 0A5A7E902C5D45A00011C783 /* VMSafePrimitive.cpp */, + 0A5A7E942C602E8C0011C783 /* VMTrivialMethod.h */, + 0A5A7E952C60F5BB0011C783 /* VMTrivialMethod.cpp */, ); path = vmobjects; sourceTree = ""; @@ -881,6 +897,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 */, @@ -946,10 +963,12 @@ 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 */, 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 */, @@ -997,6 +1016,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..452abdc2 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); } @@ -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 bfd9bba9..5b6b2a81 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); @@ -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 2f6c2387..0602190e 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,170 @@ 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 returning self here, we don't consider block methods + if (LastBytecodeIs(0, BC_RETURN_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"); +} + +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); +} + +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); +} + +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); } @@ -245,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]; @@ -272,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); } /** @@ -288,32 +458,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 +499,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 +527,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 +564,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 +597,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 +628,7 @@ bool MethodGenerationContext::InlineToDo() { return false; } - VMMethod* toBeInlined = - static_cast(extractBlockMethodAndRemoveBytecode()); + VMInvokable* toBeInlined = extractBlockMethodAndRemoveBytecode(); toBeInlined->MergeScopeInto(*this); @@ -500,15 +667,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()) { @@ -642,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 @@ -687,7 +854,7 @@ bool MethodGenerationContext::OptimizeDupPopPopSequence() { return false; } - if (lastBytecodeIs(0, BC_INC_FIELD_PUSH)) { + if (LastBytecodeIs(0, BC_INC_FIELD_PUSH)) { return optimizeIncFieldPush(); } @@ -696,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 ea022a31..ba4ea416 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; @@ -115,7 +116,18 @@ 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(); + VMTrivialMethod* assembleFieldGetterFromReturn(uint8_t pushCandidate) { + return nullptr; + } + bool optimizeIncFieldPush() { // TODO: implement return false; @@ -127,20 +139,21 @@ 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 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..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); } @@ -243,8 +246,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(); @@ -260,22 +263,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/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/BytecodeGenerationTest.cpp b/src/unitTests/BytecodeGenerationTest.cpp index 68a28ac4..3b38301f 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 = ( @@ -682,6 +536,65 @@ 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_RETURN_SELF}); + + 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_RETURN_SELF}); +} + +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_RETURN_SELF}); + + tearDown(); +} + /* @pytest.mark.parametrize( "operator,bytecode", @@ -698,68 +611,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 +661,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..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); @@ -66,40 +50,15 @@ 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); 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(); @@ -155,6 +114,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(); @@ -163,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); +}; diff --git a/src/unitTests/TrivialMethodTest.cpp b/src/unitTests/TrivialMethodTest.cpp new file mode 100644 index 00000000..e762c87e --- /dev/null +++ b/src/unitTests/TrivialMethodTest.cpp @@ -0,0 +1,299 @@ +#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 + " )"; + 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"); +} + +void TrivialMethodTest::globalReturn(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 = IsGlobalReturn(m); + CPPUNIT_ASSERT_MESSAGE(expected.data(), result); + + tearDown(); +} + +void TrivialMethodTest::testGlobalReturn() { + globalReturn("Nil"); + globalReturn("system"); + globalReturn("MyClassFooBar"); +} + +void TrivialMethodTest::testNonTrivialGlobalReturn() { + methodToBytecode("test = ( #foo. ^ system )"); + VMInvokable* m = _mgenc->Assemble(); + + 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); +} + +void TrivialMethodTest::testFieldGetter0() { + addField("field"); + methodToBytecode("test = ( ^ field )"); + + 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::testFieldGetterN() { + addField("a"); + addField("b"); + addField("c"); + addField("d"); + addField("e"); + addField("field"); + methodToBytecode("test = ( ^ field )"); + + 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 )"); + + 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); +} + +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 new file mode 100644 index 00000000..13bbac1e --- /dev/null +++ b/src/unitTests/TrivialMethodTest.h @@ -0,0 +1,60 @@ +#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(testGlobalReturn); + CPPUNIT_TEST(testNonTrivialGlobalReturn); + CPPUNIT_TEST(testUnknownGlobalInBlock); + CPPUNIT_TEST(testFieldGetter0); + 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: + 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); + + void testGlobalReturn(); + void globalReturn(std::string source); + + void testNonTrivialGlobalReturn(); + void testUnknownGlobalInBlock(); + + void testFieldGetter0(); + void testFieldGetterN(); + + 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/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 fd33ac45..92934211 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,10 @@ void* vt_primitive; 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_getter; +void* vt_setter; void* vt_string; void* vt_symbol; @@ -65,7 +72,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_string || vt == vt_symbol || vt == vt_literal_return || + 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; @@ -104,6 +112,10 @@ 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_global_return = nullptr; + vt_getter = nullptr; + vt_setter = nullptr; vt_string = nullptr; vt_symbol = nullptr; } @@ -117,11 +129,36 @@ 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; } +bool IsLiteralReturn(vm_oop_t obj) { + assert(vt_literal_return != nullptr); + 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; +} + +bool IsGetter(vm_oop_t obj) { + assert(vt_getter != nullptr); + 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. @@ -167,6 +204,23 @@ 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); + + VMGlobalReturn* gr = new (GetHeap(), 0) + VMGlobalReturn(someValidSymbol, v, someValidSymbol); + vt_global_return = get_vtable(gr); + + VMGetter* get = + 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 d6a8c5de..31da1cc7 100644 --- a/src/vm/IsValidObject.h +++ b/src/vm/IsValidObject.h @@ -6,7 +6,12 @@ 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); +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/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..6e7ca576 100644 --- a/src/vmobjects/ObjectFormats.h +++ b/src/vmobjects/ObjectFormats.h @@ -90,6 +90,11 @@ class VMSafePrimitive; class VMSafeUnaryPrimitive; class VMSafeBinaryPrimitive; class VMSafeTernaryPrimitive; +class VMTrivialMethod; +class VMLiteralReturn; +class VMGlobalReturn; +class VMGetter; +class VMSetter; class VMString; class VMSymbol; @@ -147,9 +152,14 @@ 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 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/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..0fc4ec3f 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) { @@ -232,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, @@ -322,7 +321,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/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/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..6aee2bef --- /dev/null +++ b/src/vmobjects/VMTrivialMethod.cpp @@ -0,0 +1,194 @@ +#include "VMTrivialMethod.h" + +#include +#include +#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 "../vm/Universe.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; +} + +VMTrivialMethod* MakeGlobalReturn(VMSymbol* sig, vector& arguments, + VMSymbol* globalName) { + VMGlobalReturn* result = + new (GetHeap(), 0) VMGlobalReturn(sig, arguments, globalName); + LOG_ALLOCATION("VMGlobalReturn", result->GetObjectSize()); + 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; +} + +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(); + } + 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)); +} + +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; +} + +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) + ")"; +} + +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 new file mode 100644 index 00000000..f65e6569 --- /dev/null +++ b/src/vmobjects/VMTrivialMethod.h @@ -0,0 +1,201 @@ +#pragma once + +#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: + 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); +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: + 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; +}; + +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(VMGlobalReturn); + } + + 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; +}; + +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(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; + + 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: + make_testable(public); + + size_t fieldIndex; + size_t argIndex; + int numberOfArguments; +};