From 7a105bfa22fee1a8b7895c88f4bb2fdb2ea91a0e Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Thu, 1 Aug 2024 23:40:30 +0100 Subject: [PATCH] Add inlining of #&&, #||, #and:, #or: Signed-off-by: Stefan Marr --- src/compiler/MethodGenerationContext.cpp | 33 +++++++++ src/compiler/MethodGenerationContext.h | 1 + src/compiler/Parser.cpp | 10 ++- src/unitTests/BytecodeGenerationTest.cpp | 91 ++++++++++++------------ src/unitTests/BytecodeGenerationTest.h | 8 +++ 5 files changed, 98 insertions(+), 45 deletions(-) diff --git a/src/compiler/MethodGenerationContext.cpp b/src/compiler/MethodGenerationContext.cpp index b368093f..8a2e3192 100644 --- a/src/compiler/MethodGenerationContext.cpp +++ b/src/compiler/MethodGenerationContext.cpp @@ -406,6 +406,39 @@ bool MethodGenerationContext::InlineWhile(Parser& parser, bool isWhileTrue) { return true; } +bool MethodGenerationContext::InlineAndOr(bool isOr) { + // HACK: We do assume that the receiver on the stack is a boolean, + // HACK: similar to the IfTrueIfFalseNode. + // HACK: We don't support anything but booleans at the moment. + + assert(Bytecode::GetBytecodeLength(BC_PUSH_BLOCK) == 2); + if (!hasOneLiteralBlockArgument()) { + return false; + } + + VMMethod* toBeInlined = + static_cast(extractBlockMethodAndRemoveBytecode()); + + size_t jumpOffsetIdxToSkipBranch = + EmitJumpOnBoolWithDummyOffset(*this, !isOr, true); + + isCurrentlyInliningABlock = true; + toBeInlined->InlineInto(*this); + isCurrentlyInliningABlock = false; + + size_t jumpOffsetIdxToSkipPushTrue = EmitJumpWithDumyOffset(*this); + + PatchJumpOffsetToPointToNextInstruction(jumpOffsetIdxToSkipBranch); + EmitPUSHCONSTANT(*this, + isOr ? load_ptr(trueObject) : load_ptr(falseObject)); + + PatchJumpOffsetToPointToNextInstruction(jumpOffsetIdxToSkipPushTrue); + + resetLastBytecodeBuffer(); + + return true; +} + void MethodGenerationContext::CompleteLexicalScope() { lexicalScope = new LexicalScope( outerGenc == nullptr ? nullptr : outerGenc->lexicalScope, arguments, diff --git a/src/compiler/MethodGenerationContext.h b/src/compiler/MethodGenerationContext.h index 0505f7e0..bff0a30b 100644 --- a/src/compiler/MethodGenerationContext.h +++ b/src/compiler/MethodGenerationContext.h @@ -96,6 +96,7 @@ class MethodGenerationContext { bool InlineWhile(Parser& parser, bool isWhileTrue); bool InlineIfTrueOrIfFalse(bool isIfTrue); bool InlineIfTrueFalse(bool isIfTrue); + bool InlineAndOr(bool isOr); inline size_t OffsetOfNextInstruction() { return bytecode.size(); } diff --git a/src/compiler/Parser.cpp b/src/compiler/Parser.cpp index 314117b9..41ae6975 100644 --- a/src/compiler/Parser.cpp +++ b/src/compiler/Parser.cpp @@ -593,10 +593,16 @@ void Parser::unaryMessage(MethodGenerationContext& mgenc, bool super) { } void Parser::binaryMessage(MethodGenerationContext& mgenc, bool super) { + std::string msgSelector(text); VMSymbol* msg = binarySelector(); binaryOperand(mgenc); + if (!super && (msgSelector == "||" && mgenc.InlineAndOr(true)) || + (msgSelector == "&&" && mgenc.InlineAndOr(false))) { + return; + } + if (super) { EmitSUPERSEND(mgenc, msg); } else { @@ -631,7 +637,9 @@ void Parser::keywordMessage(MethodGenerationContext& mgenc, bool super) { ((kw == "ifTrue:" && mgenc.InlineIfTrueOrIfFalse(true)) || (kw == "ifFalse:" && mgenc.InlineIfTrueOrIfFalse(false)) || (kw == "whileTrue:" && mgenc.InlineWhile(*this, true)) || - (kw == "whileFalse:" && mgenc.InlineWhile(*this, false)))) { + (kw == "whileFalse:" && mgenc.InlineWhile(*this, false)) || + (kw == "or:" && mgenc.InlineAndOr(true)) || + (kw == "and:" && mgenc.InlineAndOr(false)))) { return; } diff --git a/src/unitTests/BytecodeGenerationTest.cpp b/src/unitTests/BytecodeGenerationTest.cpp index b935b302..98d754bd 100644 --- a/src/unitTests/BytecodeGenerationTest.cpp +++ b/src/unitTests/BytecodeGenerationTest.cpp @@ -16,7 +16,6 @@ #include "../compiler/Parser.h" #include "../interpreter/bytecodes.h" #include "../misc/StringUtil.h" -#include "../misc/debug.h" #include "../vm/Symbols.h" #include "../vmobjects/VMMethod.h" @@ -654,6 +653,53 @@ void BytecodeGenerationTest::testIfTrueIfFalseNlrArg2() { BC_PUSH_ARG_2, BC_RETURN_LOCAL, BC_POP, BC_PUSH_CONSTANT_2, BC_POP, BC_PUSH_SELF, BC_RETURN_LOCAL}); } +void BytecodeGenerationTest::testInliningOfOr() { + inliningOfOr("or:"); + inliningOfOr("||"); +} + +void BytecodeGenerationTest::inliningOfOr(std::string selector) { + std::string source = "test = ( true OR_SEL [ #val ] )"; + bool wasReplaced = ReplacePattern(source, "OR_SEL", selector); + assert(wasReplaced); + + auto bytecodes = methodToBytecode(source.data()); + check(bytecodes, + {BC_PUSH_CONSTANT_0, BC(BC_JUMP_ON_TRUE_POP, 7, 0), + // true branch + BC_PUSH_CONSTANT_1, // push the `#val` + BC(BC_JUMP, 4, 0), + // false branch, jump_on_true target, push true + BC_PUSH_CONSTANT_0, BC_POP, + // target of the jump in the true branch + BC_PUSH_SELF, BC_RETURN_LOCAL}); + + tearDown(); +} + +void BytecodeGenerationTest::testInliningOfAnd() { + inliningOfAnd("and:"); + inliningOfAnd("&&"); +} + +void BytecodeGenerationTest::inliningOfAnd(std::string selector) { + std::string source = "test = ( true AND_SEL [ #val ] )"; + bool wasReplaced = ReplacePattern(source, "AND_SEL", selector); + assert(wasReplaced); + + auto bytecodes = methodToBytecode(source.data()); + check(bytecodes, + {BC_PUSH_CONSTANT_0, BC(BC_JUMP_ON_FALSE_POP, 7, 0), + // true branch + BC_PUSH_CONSTANT_1, // push the `#val` + BC(BC_JUMP, 4, 0), + // false branch, jump_on_false target, push false + BC_PUSH_CONSTANT_2, BC_POP, + // target of the jump in the true branch + BC_PUSH_SELF, BC_RETURN_LOCAL}); + + tearDown(); +} /* @pytest.mark.parametrize( @@ -1265,50 +1311,7 @@ void BytecodeGenerationTest::testIfTrueIfFalseNlrArg2() { ) - @pytest.mark.parametrize("and_sel", ["and:", "&&"]) - def test_inlining_of_and(mgenc, and_sel): - bytecodes = method_to_bytecodes( - mgenc, "test = ( true AND_SEL [ #val ] )".replace("AND_SEL", and_sel) - ) - - assert len(bytecodes) == 11 - check( - bytecodes, - [ - Bytecodes.push_constant_0, - BC(Bytecodes.jump_on_false_pop, 7), - # true branch - BC_PUSH_CONSTANT_2, # push the `#val` - BC(Bytecodes.jump, 5), - # false branch, jump_on_false target, push false - Bytecodes.push_constant, - # target of the jump in the true branch - Bytecodes.return_self, - ], - ) - - - @pytest.mark.parametrize("or_sel", ["or:", "||"]) - def test_inlining_of_or(mgenc, or_sel): - bytecodes = method_to_bytecodes( - mgenc, "test = ( true OR_SEL [ #val ] )".replace("OR_SEL", or_sel) - ) - assert len(bytecodes) == 10 - check( - bytecodes, - [ - Bytecodes.push_constant_0, - BC(Bytecodes.jump_on_true_pop, 7), - # true branch - BC_PUSH_CONSTANT_2, # push the `#val` - BC(Bytecodes.jump, 4), - # false branch, jump_on_true target, push true - Bytecodes.push_constant_0, - # target of the jump in the true branch - Bytecodes.return_self, - ], - ) def test_field_read_inlining(cgenc, mgenc): diff --git a/src/unitTests/BytecodeGenerationTest.h b/src/unitTests/BytecodeGenerationTest.h index a56fe0cb..71c53925 100644 --- a/src/unitTests/BytecodeGenerationTest.h +++ b/src/unitTests/BytecodeGenerationTest.h @@ -63,6 +63,8 @@ class BytecodeGenerationTest : public CPPUNIT_NS::TestCase { CPPUNIT_TEST(testIfTrueIfFalseArg); CPPUNIT_TEST(testIfTrueIfFalseNlrArg1); CPPUNIT_TEST(testIfTrueIfFalseNlrArg2); + CPPUNIT_TEST(testInliningOfOr); + CPPUNIT_TEST(testInliningOfAnd); CPPUNIT_TEST(testJumpQueuesOrdering); @@ -151,6 +153,12 @@ class BytecodeGenerationTest : public CPPUNIT_NS::TestCase { void testIfTrueIfFalseArg(); void testIfTrueIfFalseNlrArg1(); void testIfTrueIfFalseNlrArg2(); + + void testInliningOfOr(); + void inliningOfOr(std::string selector); + void testInliningOfAnd(); + void inliningOfAnd(std::string selector); + void testJumpQueuesOrdering(); void dump(MethodGenerationContext* mgenc);