From c7ef3d87bb5e6f01028878a5bd4900a76c648a5e Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Fri, 9 Aug 2024 21:19:00 +0100 Subject: [PATCH 1/4] Fix EmitPopFieldWithIndex and avoid code duplication Signed-off-by: Stefan Marr --- src/compiler/BytecodeGenerator.cpp | 17 ++++++----------- src/compiler/BytecodeGenerator.h | 3 +-- src/vmobjects/VMMethod.cpp | 4 +--- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/compiler/BytecodeGenerator.cpp b/src/compiler/BytecodeGenerator.cpp index aa2b71a2..8c4ba031 100644 --- a/src/compiler/BytecodeGenerator.cpp +++ b/src/compiler/BytecodeGenerator.cpp @@ -221,13 +221,9 @@ void EmitPOPARGUMENT(MethodGenerationContext& mgenc, long idx, int ctx) { void EmitPOPFIELD(MethodGenerationContext& mgenc, VMSymbol* field) { const uint8_t idx = mgenc.GetFieldIndex(field); - if (idx == 0) { - Emit1(mgenc, BC_POP_FIELD_0, -1); - } else if (idx == 1) { - Emit1(mgenc, BC_POP_FIELD_1, -1); - } else { - Emit2(mgenc, BC_POP_FIELD, idx, -1); } + + EmitPopFieldWithIndex(mgenc, idx); } void EmitSEND(MethodGenerationContext& mgenc, VMSymbol* msg) { @@ -359,19 +355,18 @@ void EmitPushFieldWithIndex(MethodGenerationContext& mgenc, uint8_t fieldIdx) { Emit2(mgenc, BC_PUSH_FIELD, fieldIdx, 1); } -void EmitPopFieldWithIndex(MethodGenerationContext& mgenc, uint8_t fieldIdx, - uint8_t ctxLevel) { +void EmitPopFieldWithIndex(MethodGenerationContext& mgenc, uint8_t fieldIdx) { // if (ctxLevel == 0) { if (fieldIdx == 0) { - Emit1(mgenc, BC_POP_FIELD_0, 1); + Emit1(mgenc, BC_POP_FIELD_0, -1); return; } if (fieldIdx == 1) { - Emit1(mgenc, BC_POP_FIELD_1, 1); + Emit1(mgenc, BC_POP_FIELD_1, -1); return; } // } - Emit2(mgenc, BC_POP_FIELD, fieldIdx, 1); + Emit2(mgenc, BC_POP_FIELD, fieldIdx, -1); } diff --git a/src/compiler/BytecodeGenerator.h b/src/compiler/BytecodeGenerator.h index 1b6597f3..6956aa14 100644 --- a/src/compiler/BytecodeGenerator.h +++ b/src/compiler/BytecodeGenerator.h @@ -73,5 +73,4 @@ size_t Emit3WithDummy(MethodGenerationContext& mgenc, uint8_t bytecode, size_t stackEffect); void EmitPushFieldWithIndex(MethodGenerationContext& mgenc, uint8_t fieldIdx); -void EmitPopFieldWithIndex(MethodGenerationContext& mgenc, uint8_t fieldIdx, - uint8_t ctxLevel); +void EmitPopFieldWithIndex(MethodGenerationContext& mgenc, uint8_t fieldIdx); diff --git a/src/vmobjects/VMMethod.cpp b/src/vmobjects/VMMethod.cpp index 3518b518..3f15c644 100644 --- a/src/vmobjects/VMMethod.cpp +++ b/src/vmobjects/VMMethod.cpp @@ -236,9 +236,7 @@ void VMMethod::inlineInto(MethodGenerationContext& mgenc) { bytecode == BC_PUSH_FIELD_1) { EmitPushFieldWithIndex(mgenc, idx); } else { - EmitPopFieldWithIndex( - mgenc, idx, - 0 /* dummy, self is looked up dynamically at the moment. */); + EmitPopFieldWithIndex(mgenc, idx); } break; } From c8122aa79057f9348970511154ab787e87a59a7a Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Fri, 9 Aug 2024 21:19:58 +0100 Subject: [PATCH 2/4] Implement DEC, INC_FIELD, INC_FIELD_PUSH Signed-off-by: Stefan Marr --- src/compiler/BytecodeGenerator.cpp | 10 + src/compiler/BytecodeGenerator.h | 3 + src/compiler/MethodGenerationContext.cpp | 85 +++++- src/compiler/MethodGenerationContext.h | 6 +- src/compiler/Parser.cpp | 31 ++ src/compiler/Parser.h | 3 + src/interpreter/Interpreter.cpp | 65 +++++ src/interpreter/Interpreter.h | 3 + src/interpreter/InterpreterLoop.h | 18 ++ src/interpreter/bytecodes.cpp | 34 ++- src/interpreter/bytecodes.h | 34 +-- src/unitTests/BytecodeGenerationTest.cpp | 347 +++++++++++++---------- src/unitTests/BytecodeGenerationTest.h | 30 ++ src/vm/Symbols.cpp | 9 + src/vm/Symbols.h | 3 + src/vmobjects/VMMethod.cpp | 4 +- 16 files changed, 492 insertions(+), 193 deletions(-) diff --git a/src/compiler/BytecodeGenerator.cpp b/src/compiler/BytecodeGenerator.cpp index 8c4ba031..92c74fcd 100644 --- a/src/compiler/BytecodeGenerator.cpp +++ b/src/compiler/BytecodeGenerator.cpp @@ -221,6 +221,8 @@ void EmitPOPARGUMENT(MethodGenerationContext& mgenc, long idx, int ctx) { void EmitPOPFIELD(MethodGenerationContext& mgenc, VMSymbol* field) { const uint8_t idx = mgenc.GetFieldIndex(field); + if (mgenc.OptimizeIncField(idx)) { + return; } EmitPopFieldWithIndex(mgenc, idx); @@ -282,6 +284,14 @@ void EmitINC(MethodGenerationContext& mgenc) { Emit1(mgenc, BC_INC, 0); } +void EmitDEC(MethodGenerationContext& mgenc) { + Emit1(mgenc, BC_DEC, 0); +} + +void EmitIncFieldPush(MethodGenerationContext& mgenc, uint8_t fieldIdx) { + Emit2(mgenc, BC_INC_FIELD_PUSH, fieldIdx, 1); +} + void EmitDupSecond(MethodGenerationContext& mgenc) { Emit1(mgenc, BC_DUP_SECOND, 1); } diff --git a/src/compiler/BytecodeGenerator.h b/src/compiler/BytecodeGenerator.h index 6956aa14..920bf4eb 100644 --- a/src/compiler/BytecodeGenerator.h +++ b/src/compiler/BytecodeGenerator.h @@ -61,6 +61,9 @@ void EmitRETURNNONLOCAL(MethodGenerationContext& mgenc); void EmitRETURNFIELD(MethodGenerationContext& mgenc, size_t index); void EmitINC(MethodGenerationContext& mgenc); +void EmitDEC(MethodGenerationContext& mgenc); +void EmitIncFieldPush(MethodGenerationContext& mgenc, uint8_t fieldIdx); + void EmitDupSecond(MethodGenerationContext& mgenc); size_t EmitJumpOnBoolWithDummyOffset(MethodGenerationContext& mgenc, diff --git a/src/compiler/MethodGenerationContext.cpp b/src/compiler/MethodGenerationContext.cpp index dea4a619..381f4de5 100644 --- a/src/compiler/MethodGenerationContext.cpp +++ b/src/compiler/MethodGenerationContext.cpp @@ -843,7 +843,7 @@ void MethodGenerationContext::RemoveLastPopForBlockLocalReturn() { return; } - if (lastBytecodeIsOneOf(0, IsPopSmthBytecode) && + if (lastBytecodeIsOneOf(0, IsPopSmthBytecode) != BC_INVALID && !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 @@ -864,14 +864,17 @@ void MethodGenerationContext::RemoveLastPopForBlockLocalReturn() { // need to push it. last4Bytecodes[3] = BC_INC_FIELD_PUSH; - size_t bcOffset = bytecode.size() - 3; + size_t bcOffset = bytecode.size() - 2; // since the bytecodes have the same length, we can just switch the // opcode - assert(Bytecode::GetBytecodeLength(BC_INC_FIELD_PUSH) == 3); - assert(Bytecode::GetBytecodeLength(BC_INC_FIELD) == 3); + assert(Bytecode::GetBytecodeLength(BC_INC_FIELD_PUSH) == 2); + assert(Bytecode::GetBytecodeLength(BC_INC_FIELD) == 2); assert(bytecode[bcOffset] == BC_INC_FIELD); bytecode[bcOffset] = BC_INC_FIELD_PUSH; + + currentStackDepth += 1; + maxStackDepth = max(maxStackDepth, currentStackDepth); } } @@ -907,6 +910,80 @@ bool MethodGenerationContext::OptimizeDupPopPopSequence() { return true; } +bool MethodGenerationContext::optimizeIncFieldPush() { + assert(Bytecode::GetBytecodeLength(BC_INC_FIELD_PUSH) == 2); + + size_t bcIdx = bytecode.size() - 2; + assert(bytecode.at(bcIdx) == BC_INC_FIELD_PUSH); + + bytecode[bcIdx] = BC_INC_FIELD; + last4Bytecodes[3] = BC_INC_FIELD; + + return true; +} + +/** + * Try using a INC_FIELD bytecode instead of the following sequence. + * + * PUSH_FIELD + * INC + * DUP + * POP_FIELD + * + * return true, if it optimized it. + */ +bool MethodGenerationContext::OptimizeIncField(uint8_t fieldIdx) { + if (isCurrentlyInliningABlock) { + return false; + } + + if (!LastBytecodeIs(0, BC_DUP)) { + return false; + } + + if (!LastBytecodeIs(1, BC_INC)) { + return false; + } + + uint8_t pushCandidate = lastBytecodeIsOneOf(2, IsPushFieldBytecode); + if (pushCandidate == BC_INVALID) { + return false; + } + + assert(Bytecode::GetBytecodeLength(BC_DUP) == 1); + assert(Bytecode::GetBytecodeLength(BC_INC) == 1); + + size_t bcOffset = 1 + 1 + Bytecode::GetBytecodeLength(pushCandidate); + uint8_t candidateFieldIdx = 0; + + switch (pushCandidate) { + case BC_PUSH_FIELD_0: + candidateFieldIdx = 0; + break; + case BC_PUSH_FIELD_1: + candidateFieldIdx = 1; + break; + case BC_PUSH_FIELD: { + assert(bytecode.at(bytecode.size() - bcOffset) == pushCandidate); + candidateFieldIdx = bytecode.at((bytecode.size() - bcOffset) + 1); + break; + } + + default: + ErrorExit("Unexpected bytecode"); + break; + } + + if (candidateFieldIdx == fieldIdx) { + removeLastBytecodes(3); + resetLastBytecodeBuffer(); + EmitIncFieldPush(*this, fieldIdx); + return true; + } + + return false; +} + bool MethodGenerationContext::OptimizeReturnField() { if (isCurrentlyInliningABlock) { return false; diff --git a/src/compiler/MethodGenerationContext.h b/src/compiler/MethodGenerationContext.h index f4f76fee..43342404 100644 --- a/src/compiler/MethodGenerationContext.h +++ b/src/compiler/MethodGenerationContext.h @@ -115,6 +115,7 @@ class MethodGenerationContext { void PatchJumpOffsetToPointToNextInstruction(size_t indexOfOffset); bool OptimizeDupPopPopSequence(); + bool OptimizeIncField(uint8_t fieldIdx); bool OptimizeReturnField(); bool LastBytecodeIs(size_t indexFromEnd, uint8_t bytecode); @@ -127,10 +128,7 @@ class MethodGenerationContext { VMTrivialMethod* assembleFieldSetter(); VMTrivialMethod* assembleFieldGetterFromReturn(uint8_t returnCandidate); - bool optimizeIncFieldPush() { - // TODO: implement - return false; - } + bool optimizeIncFieldPush(); void removeLastBytecodes(size_t numBytecodes); void removeLastBytecodeAt(size_t indexFromEnd); diff --git a/src/compiler/Parser.cpp b/src/compiler/Parser.cpp index 7a92ea2b..a73792e6 100644 --- a/src/compiler/Parser.cpp +++ b/src/compiler/Parser.cpp @@ -604,10 +604,41 @@ void Parser::unaryMessage(MethodGenerationContext& mgenc, bool super) { } } +bool Parser::tryIncOrDecBytecodes(VMSymbol* msg, bool isSuperSend, + MethodGenerationContext& mgenc) { + if (isSuperSend) { + return false; + } + + bool isPlus = msg == load_ptr(symbolPlus); + bool isMinus = msg == load_ptr(symbolMinus); + + if (!isPlus && !isMinus) { + return false; + } + + if (sym != Integer || text != "1") { + return false; + } + + expect(Integer); + if (isPlus) { + EmitINC(mgenc); + } else { + assert(isMinus); + EmitDEC(mgenc); + } + return true; +} + void Parser::binaryMessage(MethodGenerationContext& mgenc, bool super) { std::string msgSelector(text); VMSymbol* msg = binarySelector(); + if (tryIncOrDecBytecodes(msg, super, mgenc)) { + return; + } + binaryOperand(mgenc); if (!super && ((msgSelector == "||" && mgenc.InlineAndOr(true)) || diff --git a/src/compiler/Parser.h b/src/compiler/Parser.h index aa6f76a9..16c7b350 100644 --- a/src/compiler/Parser.h +++ b/src/compiler/Parser.h @@ -94,6 +94,9 @@ class Parser { std::string variable(); void messages(MethodGenerationContext& mgenc, bool super); void unaryMessage(MethodGenerationContext& mgenc, bool super); + + bool tryIncOrDecBytecodes(VMSymbol* msg, bool isSuperSend, + MethodGenerationContext& mgenc); void binaryMessage(MethodGenerationContext& mgenc, bool super); bool binaryOperand(MethodGenerationContext& mgenc); void keywordMessage(MethodGenerationContext& mgenc, bool super); diff --git a/src/interpreter/Interpreter.cpp b/src/interpreter/Interpreter.cpp index 1172be52..e40b3c06 100644 --- a/src/interpreter/Interpreter.cpp +++ b/src/interpreter/Interpreter.cpp @@ -459,6 +459,71 @@ void Interpreter::doInc() { GetFrame()->SetTop(store_root(val)); } +void Interpreter::doDec() { + vm_oop_t val = GetFrame()->Top(); + + if (IS_TAGGED(val) || CLASS_OF(val) == load_ptr(integerClass)) { + int64_t result = (int64_t)INT_VAL(val) - 1; + val = NEW_INT(result); + } else if (CLASS_OF(val) == load_ptr(doubleClass)) { + double d = static_cast(val)->GetEmbeddedDouble(); + val = Universe::NewDouble(d - 1.0); + } else { + ErrorExit("unsupported"); + } + + GetFrame()->SetTop(store_root(val)); +} + +void Interpreter::doIncField(uint8_t fieldIdx) { + vm_oop_t self = GetSelf(); + + if (unlikely(IS_TAGGED(self))) { + ErrorExit("Integers do not have fields!"); + } + + VMObject* selfObj = (VMObject*)self; + + vm_oop_t val = selfObj->GetField(fieldIdx); + + if (IS_TAGGED(val) || CLASS_OF(val) == load_ptr(integerClass)) { + int64_t result = (int64_t)INT_VAL(val) + 1; + val = NEW_INT(result); + } else if (CLASS_OF(val) == load_ptr(doubleClass)) { + double d = static_cast(val)->GetEmbeddedDouble(); + val = Universe::NewDouble(d + 1.0); + } else { + ErrorExit("unsupported"); + } + + selfObj->SetField(fieldIdx, val); +} + +void Interpreter::doIncFieldPush(uint8_t fieldIdx) { + vm_oop_t self = GetSelf(); + + if (unlikely(IS_TAGGED(self))) { + ErrorExit("Integers do not have fields!"); + } + + VMObject* selfObj = (VMObject*)self; + + vm_oop_t val = selfObj->GetField(fieldIdx); + + if (IS_TAGGED(val) || CLASS_OF(val) == load_ptr(integerClass)) { + int64_t result = (int64_t)INT_VAL(val) + 1; + val = NEW_INT(result); + } else if (CLASS_OF(val) == load_ptr(doubleClass)) { + double d = static_cast(val)->GetEmbeddedDouble(); + val = Universe::NewDouble(d + 1.0); + } else { + ErrorExit("unsupported"); + } + + selfObj->SetField(fieldIdx, val); + GetFrame()->Push(val); +} + bool Interpreter::checkIsGreater() { vm_oop_t top = GetFrame()->Top(); vm_oop_t top2 = GetFrame()->Top2(); diff --git a/src/interpreter/Interpreter.h b/src/interpreter/Interpreter.h index 4ae58bd1..f60f7ee7 100644 --- a/src/interpreter/Interpreter.h +++ b/src/interpreter/Interpreter.h @@ -109,5 +109,8 @@ class Interpreter { static void doReturnLocal(); static void doReturnNonLocal(); static void doInc(); + static void doDec(); + static void doIncField(uint8_t fieldIndex); + static void doIncFieldPush(uint8_t fieldIndex); static bool checkIsGreater(); }; diff --git a/src/interpreter/InterpreterLoop.h b/src/interpreter/InterpreterLoop.h index ce79d8f6..2835cf4e 100644 --- a/src/interpreter/InterpreterLoop.h +++ b/src/interpreter/InterpreterLoop.h @@ -46,6 +46,9 @@ vm_oop_t Start() { &&LABEL_BC_RETURN_FIELD_1, &&LABEL_BC_RETURN_FIELD_2, &&LABEL_BC_INC, + &&LABEL_BC_DEC, + &&LABEL_BC_INC_FIELD, + &&LABEL_BC_INC_FIELD_PUSH, &&LABEL_BC_JUMP, &&LABEL_BC_JUMP_ON_FALSE_POP, &&LABEL_BC_JUMP_ON_TRUE_POP, @@ -293,6 +296,21 @@ LABEL_BC_RETURN_SELF: { doInc(); DISPATCH_NOGC(); +LABEL_BC_DEC: + PROLOGUE(1); + doDec(); + DISPATCH_NOGC(); + +LABEL_BC_INC_FIELD: + PROLOGUE(2); + doIncField(currentBytecodes[bytecodeIndexGlobal - 1]); + DISPATCH_NOGC(); + +LABEL_BC_INC_FIELD_PUSH: + PROLOGUE(2); + doIncFieldPush(currentBytecodes[bytecodeIndexGlobal - 1]); + DISPATCH_NOGC(); + LABEL_BC_JUMP: { uint8_t offset = currentBytecodes[bytecodeIndexGlobal + 1]; bytecodeIndexGlobal += offset; diff --git a/src/interpreter/bytecodes.cpp b/src/interpreter/bytecodes.cpp index 133cd838..dc3f42e0 100644 --- a/src/interpreter/bytecodes.cpp +++ b/src/interpreter/bytecodes.cpp @@ -71,6 +71,9 @@ const uint8_t Bytecode::bytecodeLengths[] = { 1, // BC_RETURN_FIELD_1 1, // BC_RETURN_FIELD_2 1, // BC_INC + 1, // BC_DEC + 2, // BC_INC_FIELD + 2, // BC_INC_FIELD_PUSH 3, // BC_JUMP 3, // BC_JUMP_ON_FALSE_POP @@ -131,20 +134,23 @@ const char* Bytecode::bytecodeNames[] = { "RETURN_FIELD_1 ", // 38 "RETURN_FIELD_2 ", // 39 "INC ", // 40 - "JUMP ", // 41 - "JUMP_ON_FALSE_POP", // 42 - "JUMP_ON_TRUE_POP", // 43 - "JUMP_ON_FALSE_TOP_NIL", // 44 - "JUMP_ON_TRUE_TOP_NIL", // 45 - "JUMP_IF_GREATER ", // 46 - "JUMP_BACKWARD ", // 47 - "JUMP2 ", // 48 - "JUMP2_ON_FALSE_POP", // 49 - "JUMP2_ON_TRUE_POP", // 50 - "JUMP2_ON_FALSE_TOP_NIL", // 51 - "JUMP2_ON_TRUE_TOP_NIL", // 52 - "JUMP2_IF_GREATER", // 53 - "JUMP2_BACKWARD ", // 54 + "DEC ", // 41 + "INC_FIELD ", // 42 + "INC_FIELD_PUSH ", // 43 + "JUMP ", // 44 + "JUMP_ON_FALSE_POP", // 45 + "JUMP_ON_TRUE_POP", // 46 + "JUMP_ON_FALSE_TOP_NIL", // 47 + "JUMP_ON_TRUE_TOP_NIL", // 48 + "JUMP_IF_GREATER ", // 49 + "JUMP_BACKWARD ", // 50 + "JUMP2 ", // 51 + "JUMP2_ON_FALSE_POP", // 52 + "JUMP2_ON_TRUE_POP", // 53 + "JUMP2_ON_FALSE_TOP_NIL", // 54 + "JUMP2_ON_TRUE_TOP_NIL", // 55 + "JUMP2_IF_GREATER", // 56 + "JUMP2_BACKWARD ", // 57 }; bool IsJumpBytecode(uint8_t bc) { diff --git a/src/interpreter/bytecodes.h b/src/interpreter/bytecodes.h index 6bb11b49..cce9fc34 100644 --- a/src/interpreter/bytecodes.h +++ b/src/interpreter/bytecodes.h @@ -74,20 +74,23 @@ #define BC_RETURN_FIELD_1 38 #define BC_RETURN_FIELD_2 39 #define BC_INC 40 -#define BC_JUMP 41 -#define BC_JUMP_ON_FALSE_POP 42 -#define BC_JUMP_ON_TRUE_POP 43 -#define BC_JUMP_ON_FALSE_TOP_NIL 44 -#define BC_JUMP_ON_TRUE_TOP_NIL 45 -#define BC_JUMP_IF_GREATER 46 -#define BC_JUMP_BACKWARD 47 -#define BC_JUMP2 48 -#define BC_JUMP2_ON_FALSE_POP 49 -#define BC_JUMP2_ON_TRUE_POP 50 -#define BC_JUMP2_ON_FALSE_TOP_NIL 51 -#define BC_JUMP2_ON_TRUE_TOP_NIL 52 -#define BC_JUMP2_IF_GREATER 53 -#define BC_JUMP2_BACKWARD 54 +#define BC_DEC 41 +#define BC_INC_FIELD 42 +#define BC_INC_FIELD_PUSH 43 +#define BC_JUMP 44 +#define BC_JUMP_ON_FALSE_POP 45 +#define BC_JUMP_ON_TRUE_POP 46 +#define BC_JUMP_ON_FALSE_TOP_NIL 47 +#define BC_JUMP_ON_TRUE_TOP_NIL 48 +#define BC_JUMP_IF_GREATER 49 +#define BC_JUMP_BACKWARD 50 +#define BC_JUMP2 51 +#define BC_JUMP2_ON_FALSE_POP 52 +#define BC_JUMP2_ON_TRUE_POP 53 +#define BC_JUMP2_ON_FALSE_TOP_NIL 54 +#define BC_JUMP2_ON_TRUE_TOP_NIL 55 +#define BC_JUMP2_IF_GREATER 56 +#define BC_JUMP2_BACKWARD 57 #define _LAST_BYTECODE BC_JUMP2_BACKWARD @@ -98,9 +101,6 @@ // they were already named in ported code, and it seemed nicer to just // already include that code // clang-format off -#define BC_INC_FIELD 254 -#define BC_INC_FIELD_PUSH 253 -#define BC_DEC 251 #define BC_SEND_N 250 #define BC_SEND_3 249 #define BC_SEND_2 248 diff --git a/src/unitTests/BytecodeGenerationTest.cpp b/src/unitTests/BytecodeGenerationTest.cpp index 0366cdc0..18eec87f 100644 --- a/src/unitTests/BytecodeGenerationTest.cpp +++ b/src/unitTests/BytecodeGenerationTest.cpp @@ -651,70 +651,53 @@ void BytecodeGenerationTest::testNestedIfsAndLocals() { BC_RETURN_SELF}); } -/* - @pytest.mark.parametrize( - "operator,bytecode", - [ - ("+", Bytecodes.inc), - ("-", Bytecodes.dec), - ], - ) - def test_inc_dec_bytecodes(mgenc, operator, bytecode): - bytecodes = method_to_bytecodes(mgenc, "test = ( 1 OP 1 )".replace("OP", - operator)) - - assert len(bytecodes) == 3 - check(bytecodes, [Bytecodes.push_1, bytecode, Bytecodes.return_self]) - - - def test_if_true_and_inc_field(cgenc, mgenc): - add_field(cgenc, "field") - bytecodes = method_to_bytecodes( - mgenc, - """ - test: arg = ( - #start. - (self key: 5) ifTrue: [ field := field + 1 ]. - #end - )""", - ) +void BytecodeGenerationTest::testIncDecBytecodes() { + incDecBytecodes("+", BC_INC); + incDecBytecodes("-", BC_DEC); +} - assert len(bytecodes) == 18 - check( - bytecodes, - [ - (6, Bytecodes.send_2), - BC(Bytecodes.jump_on_false_top_nil, 6, note="jump offset"), - Bytecodes.inc_field_push, - Bytecodes.pop, - Bytecodes.push_constant, - ], - ) +void BytecodeGenerationTest::incDecBytecodes(std::string sel, uint8_t bc) { + std::string source = "test = ( 1 " + sel + " 1 )"; + auto bytecodes = methodToBytecode(source.data()); + check(bytecodes, {BC_PUSH_1, bc, BC_RETURN_SELF}); - def test_if_true_and_inc_arg(mgenc): - bytecodes = method_to_bytecodes( - mgenc, - """ - test: arg = ( - #start. - (self key: 5) ifTrue: [ arg + 1 ]. - #end - )""", - ) + tearDown(); +} + +void BytecodeGenerationTest::testIfTrueAndIncField() { + addField("field"); + + auto bytecodes = methodToBytecode(R"""( + test: arg = ( + #start. + (self key: 5) ifTrue: [ field := field + 1 ]. + #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, 5, 0), + BC(BC_INC_FIELD_PUSH, 0), BC_POP, BC(BC_PUSH_CONSTANT, 3), + BC_RETURN_SELF}); +} + +void BytecodeGenerationTest::testIfTrueAndIncArg() { + auto bytecodes = methodToBytecode(R"""( + test: arg = ( + #start. + (self key: 5) ifTrue: [ arg + 1 ]. + #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, 5, 0), BC_PUSH_ARG_1, + BC_INC, BC_POP, BC(BC_PUSH_CONSTANT, 3), BC_RETURN_SELF}); +} + +/* - assert len(bytecodes) == 19 - check( - bytecodes, - [ - (6, Bytecodes.send_2), - BC(Bytecodes.jump_on_false_top_nil, 7, note="jump offset"), - BC(Bytecodes.push_argument, 1, 0), - Bytecodes.inc, - Bytecodes.pop, - Bytecodes.push_constant, - ], - ) def test_nested_ifs_and_non_inlined_blocks(cgenc, mgenc): @@ -921,111 +904,126 @@ void BytecodeGenerationTest::trivialMethodInlining(std::string literal, tearDown(); } -/* - @pytest.mark.parametrize("field_num", range(0, 7)) - def test_inc_field(cgenc, mgenc, field_num): - add_field(cgenc, "field0") - add_field(cgenc, "field1") - add_field(cgenc, "field2") - add_field(cgenc, "field3") - add_field(cgenc, "field4") - add_field(cgenc, "field5") - add_field(cgenc, "field6") - - field_name = "field" + str(field_num) - bytecodes = method_to_bytecodes( - mgenc, "test = ( " + field_name + " := " + field_name + " + 1 )" - ) +void BytecodeGenerationTest::testIncField() { + incField(0); + incField(1); + incField(2); + incField(3); + incField(4); + incField(5); + incField(6); +} - check( - bytecodes, - [ - BC(Bytecodes.inc_field, field_num, 0), - Bytecodes.return_self, - ], - ) +void BytecodeGenerationTest::incField(size_t fieldNum) { + addField("field0"); + addField("field1"); + addField("field2"); + addField("field3"); + addField("field4"); + addField("field5"); + addField("field6"); + std::string fieldName = "field" + to_string(fieldNum); + std::string source = + "test = ( " + fieldName + " := " + fieldName + " + 1 )"; - @pytest.mark.parametrize("field_num", range(0, 7)) - def test_inc_field_non_trivial(cgenc, mgenc, field_num): - add_field(cgenc, "field0") - add_field(cgenc, "field1") - add_field(cgenc, "field2") - add_field(cgenc, "field3") - add_field(cgenc, "field4") - add_field(cgenc, "field5") - add_field(cgenc, "field6") + auto bytecodes = methodToBytecode(source.data()); - field_name = "field" + str(field_num) - bytecodes = method_to_bytecodes( - mgenc, "test = ( 1. " + field_name + " := " + field_name + " + 1. 2 )" - ) - check( - bytecodes, - [ - Bytecodes.push_1, - Bytecodes.pop, - BC(Bytecodes.inc_field, field_num, 0), - Bytecodes.push_constant_1, - Bytecodes.return_self, - ], - ) + check(bytecodes, {BC(BC_INC_FIELD, fieldNum), BC_RETURN_SELF}); + tearDown(); +} - @pytest.mark.parametrize("field_num", range(0, 7)) - def test_return_inc_field(cgenc, mgenc, field_num): - add_field(cgenc, "field0") - add_field(cgenc, "field1") - add_field(cgenc, "field2") - add_field(cgenc, "field3") - add_field(cgenc, "field4") - add_field(cgenc, "field5") - add_field(cgenc, "field6") +void BytecodeGenerationTest::testIncFieldNonTrivial() { + incFieldNonTrivial(0); + incFieldNonTrivial(1); + incFieldNonTrivial(2); + incFieldNonTrivial(3); + incFieldNonTrivial(4); + incFieldNonTrivial(5); + incFieldNonTrivial(6); +} - field_name = "field" + str(field_num) - bytecodes = method_to_bytecodes( - mgenc, "test = ( #foo. ^ " + field_name + " := " + field_name + " + 1 - )" - ) - check( - bytecodes, - [ - Bytecodes.push_constant_0, - Bytecodes.pop, - BC(Bytecodes.inc_field_push, field_num, 0), - Bytecodes.return_local, - ], - ) +void BytecodeGenerationTest::incFieldNonTrivial(size_t fieldNum) { + addField("field0"); + addField("field1"); + addField("field2"); + addField("field3"); + addField("field4"); + addField("field5"); + addField("field6"); + std::string fieldName = "field" + to_string(fieldNum); + std::string source = + "test = ( 1. " + fieldName + " := " + fieldName + " + 1. 2 )"; - @pytest.mark.parametrize("field_num", range(0, 7)) - def test_return_inc_field_from_block(cgenc, bgenc, field_num): - add_field(cgenc, "field0") - add_field(cgenc, "field1") - add_field(cgenc, "field2") - add_field(cgenc, "field3") - add_field(cgenc, "field4") - add_field(cgenc, "field5") - add_field(cgenc, "field6") + auto bytecodes = methodToBytecode(source.data()); - field_name = "field" + str(field_num) - bytecodes = block_to_bytecodes( - bgenc, "[ #foo. " + field_name + " := " + field_name + " + 1 ]" - ) + check(bytecodes, {BC_PUSH_1, BC_POP, BC(BC_INC_FIELD, fieldNum), + BC_PUSH_CONSTANT_0, BC_RETURN_SELF}); - check( - bytecodes, - [ - Bytecodes.push_constant_0, - Bytecodes.pop, - BC(Bytecodes.inc_field_push, field_num, 1), - Bytecodes.return_local, - ], - ) + tearDown(); +} + +void BytecodeGenerationTest::testReturnIncField() { + returnIncField(0); + returnIncField(1); + returnIncField(2); + returnIncField(3); + returnIncField(4); + returnIncField(5); + returnIncField(6); +} + +void BytecodeGenerationTest::returnIncField(size_t fieldNum) { + addField("field0"); + addField("field1"); + addField("field2"); + addField("field3"); + addField("field4"); + addField("field5"); + addField("field6"); + std::string fieldName = "field" + to_string(fieldNum); + std::string source = + "test = ( #foo. ^ " + fieldName + " := " + fieldName + " + 1 )"; + + auto bytecodes = methodToBytecode(source.data()); + check(bytecodes, {BC_PUSH_CONSTANT_0, BC_POP, + BC(BC_INC_FIELD_PUSH, fieldNum), BC_RETURN_LOCAL}); - */ + tearDown(); +} + +void BytecodeGenerationTest::testReturnIncFieldFromBlock() { + returnIncFieldFromBlock(0); + returnIncFieldFromBlock(1); + returnIncFieldFromBlock(2); + returnIncFieldFromBlock(3); + returnIncFieldFromBlock(4); + returnIncFieldFromBlock(5); + returnIncFieldFromBlock(6); +} +void BytecodeGenerationTest::returnIncFieldFromBlock(size_t fieldNum) { + addField("field0"); + addField("field1"); + addField("field2"); + addField("field3"); + addField("field4"); + addField("field5"); + addField("field6"); + + std::string fieldName = "field" + to_string(fieldNum); + std::string source = "[ #foo. " + fieldName + " := " + fieldName + " + 1 ]"; + + auto bytecodes = blockToBytecode(source.data()); + + check(bytecodes, {BC_PUSH_CONSTANT_0, BC_POP, + BC(BC_INC_FIELD_PUSH, fieldNum), BC_RETURN_LOCAL}); + + tearDown(); +} void BytecodeGenerationTest::testReturnField() { returnField(0, BC_RETURN_FIELD_0, true); @@ -1116,3 +1114,50 @@ void BytecodeGenerationTest::testJumpQueuesOrdering() { CPPUNIT_ASSERT_EQUAL((size_t)32, backJumpsToPatch.top().backwardsJumpIdx); backJumpsToPatch.pop(); } + +void BytecodeGenerationTest::testFieldReadIncWrite() { + addField("counter"); + auto bytecodes = methodToBytecode(R""""( + benchmark = ( | iter | + counter := 0. + iter := 20000. + + [ iter > 0 ] whileTrue: [ + iter := iter - 1. + counter := counter + 1. + counter := counter + 1. + ]. + + ^ counter + ) + )""""); + check(bytecodes, {// counter := 0 + BC_PUSH_0, BC_POP_FIELD_0, + + // iter := 20000 + BC_PUSH_CONSTANT_0, BC_POP_LOCAL_0, + + // iter > 0 + BC_PUSH_LOCAL_0, BC_PUSH_0, BC(BC_SEND, 1), + + // whileTrue + BC(BC_JUMP_ON_FALSE_POP, 14, 0), + + // iter := iter - 1 + BC_PUSH_LOCAL_0, BC_DEC, BC_POP_LOCAL_0, + + // counter := counter + 1 + BC(BC_INC_FIELD, 0), + + // counter := counter + 1 + BC(BC_INC_FIELD_PUSH, 0), + + // return to top + BC_POP, BC(BC_JUMP_BACKWARD, 15, 0), + + // end loop + BC_PUSH_NIL, BC_POP, + + // ^ counter + BC_RETURN_FIELD_0}); +} diff --git a/src/unitTests/BytecodeGenerationTest.h b/src/unitTests/BytecodeGenerationTest.h index 04010d59..5019422d 100644 --- a/src/unitTests/BytecodeGenerationTest.h +++ b/src/unitTests/BytecodeGenerationTest.h @@ -57,6 +57,11 @@ class BytecodeGenerationTest : public TestWithParsing { CPPUNIT_TEST(testJumpQueuesOrdering); CPPUNIT_TEST(testNestedIfs); CPPUNIT_TEST(testNestedIfsAndLocals); + + CPPUNIT_TEST(testIncDecBytecodes); + CPPUNIT_TEST(testIfTrueAndIncField); + CPPUNIT_TEST(testIfTrueAndIncArg); + CPPUNIT_TEST(testFieldReadInlining); CPPUNIT_TEST(testReturnField); CPPUNIT_TEST(testTrivialMethodInlining); @@ -64,6 +69,11 @@ class BytecodeGenerationTest : public TestWithParsing { CPPUNIT_TEST(testBlockIfTrueMethodArg); CPPUNIT_TEST(testIfTrueIfFalseReturn); CPPUNIT_TEST(testBlockIfReturnNonLocal); + CPPUNIT_TEST(testIncField); + CPPUNIT_TEST(testIncFieldNonTrivial); + CPPUNIT_TEST(testReturnIncFieldFromBlock); + CPPUNIT_TEST(testReturnIncField); + CPPUNIT_TEST(testFieldReadIncWrite); CPPUNIT_TEST_SUITE_END(); @@ -142,6 +152,12 @@ class BytecodeGenerationTest : public TestWithParsing { void testNestedIfs(); void testNestedIfsAndLocals(); + void testIncDecBytecodes(); + void incDecBytecodes(std::string sel, uint8_t bc); + + void testIfTrueAndIncField(); + void testIfTrueAndIncArg(); + void testFieldReadInlining(); void testReturnField(); void returnField(size_t fieldNum, BC bytecode, bool isReturnFieldBc); @@ -156,4 +172,18 @@ class BytecodeGenerationTest : public TestWithParsing { void testBlockIfReturnNonLocal(); void blockIfReturnNonLocal(std::string sel, BC bc); + + void testIncField(); + void incField(size_t fieldNum); + + void testIncFieldNonTrivial(); + void incFieldNonTrivial(size_t fieldNum); + + void testReturnIncField(); + void returnIncField(size_t fieldNum); + + void testReturnIncFieldFromBlock(); + void returnIncFieldFromBlock(size_t fieldNum); + + void testFieldReadIncWrite(); }; diff --git a/src/vm/Symbols.cpp b/src/vm/Symbols.cpp index aa8fcaa5..a9653a12 100644 --- a/src/vm/Symbols.cpp +++ b/src/vm/Symbols.cpp @@ -19,6 +19,9 @@ GCSymbol* symbolBlockSelf; GCSymbol* symbolIfTrue; GCSymbol* symbolIfFalse; +GCSymbol* symbolPlus; +GCSymbol* symbolMinus; + VMSymbol* NewSymbol(const size_t length, const char* str) { VMSymbol* result = new (GetHeap(), PADDED_SIZE(length)) VMSymbol(length, str); @@ -43,6 +46,9 @@ void InitializeSymbols() { symbolBlockSelf = store_root(SymbolFor("$blockSelf")); symbolIfTrue = store_root(SymbolFor("ifTrue:")); symbolIfFalse = store_root(SymbolFor("ifFalse:")); + + symbolPlus = store_root(SymbolFor("+")); + symbolMinus = store_root(SymbolFor("-")); } void WalkSymbols(walk_heap_fn walk) { @@ -60,4 +66,7 @@ void WalkSymbols(walk_heap_fn walk) { symbolBlockSelf = static_cast(walk(symbolBlockSelf)); symbolIfTrue = static_cast(walk(symbolIfTrue)); symbolIfFalse = static_cast(walk(symbolIfFalse)); + + symbolPlus = static_cast(walk(symbolPlus)); + symbolMinus = static_cast(walk(symbolMinus)); } diff --git a/src/vm/Symbols.h b/src/vm/Symbols.h index e22f033d..5f44898d 100644 --- a/src/vm/Symbols.h +++ b/src/vm/Symbols.h @@ -22,6 +22,9 @@ extern GCSymbol* symbolBlockSelf; extern GCSymbol* symbolIfTrue; extern GCSymbol* symbolIfFalse; +extern GCSymbol* symbolPlus; +extern GCSymbol* symbolMinus; + const char* const strBlockSelf = "$blockSelf"; const char* const strSuper = "super"; const char* const strSelf = "self"; diff --git a/src/vmobjects/VMMethod.cpp b/src/vmobjects/VMMethod.cpp index 3f15c644..2fe74fc7 100644 --- a/src/vmobjects/VMMethod.cpp +++ b/src/vmobjects/VMMethod.cpp @@ -261,9 +261,7 @@ void VMMethod::inlineInto(MethodGenerationContext& mgenc) { case BC_INC_FIELD: case BC_INC_FIELD_PUSH: { const uint8_t idx = bytecodes[i + 1]; - const uint8_t ctxLevel = bytecodes[i + 2]; - assert(ctxLevel > 0); - Emit3(mgenc, bytecode, idx, ctxLevel - 1, 1); + Emit2(mgenc, bytecode, idx, bytecode == BC_INC_FIELD ? 0 : 1); break; } case BC_PUSH_LOCAL: From 353544b15c64a2d28434b3ebff62533989cef1c3 Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Fri, 9 Aug 2024 23:44:06 +0100 Subject: [PATCH 3/4] Add support for INC_FIELD* to disassembler and reduce code duplication Signed-off-by: Stefan Marr --- src/compiler/Disassembler.cpp | 66 +++++++++++------------------------ 1 file changed, 21 insertions(+), 45 deletions(-) diff --git a/src/compiler/Disassembler.cpp b/src/compiler/Disassembler.cpp index f53c478c..65c0263f 100644 --- a/src/compiler/Disassembler.cpp +++ b/src/compiler/Disassembler.cpp @@ -176,28 +176,7 @@ void Disassembler::dumpMethod(uint8_t* bytecodes, size_t numberOfBytecodes, bytecodes[bc_idx + 2]); break; } - case BC_PUSH_FIELD: { - long fieldIdx = bytecodes[bc_idx + 1]; - if (method != nullptr && printObjects) { - VMClass* holder = - dynamic_cast((VMObject*)method->GetHolder()); - if (holder) { - VMSymbol* name = holder->GetInstanceFieldName(fieldIdx); - if (name != nullptr) { - DebugPrint("(index: %d) field: %s\n", - bytecodes[bc_idx + 1], - name->GetStdString().c_str()); - } else { - DebugPrint("(index: %d) field: !nullptr!: error!\n", - bytecodes[bc_idx + 1]); - } - break; - } - } - DebugPrint("(index: %d)\n", bytecodes[bc_idx + 1]); - break; - } case BC_PUSH_BLOCK: { size_t indent_size = strlen(indent) + 1 + 1; char* nindent = new char[indent_size]; @@ -258,15 +237,23 @@ void Disassembler::dumpMethod(uint8_t* bytecodes, size_t numberOfBytecodes, DebugPrint("argument: %d, context: %d\n", bytecodes[bc_idx + 1], bytecodes[bc_idx + 2]); break; - case BC_POP_FIELD: { + case BC_INC_FIELD: + case BC_INC_FIELD_PUSH: + case BC_POP_FIELD: + case BC_PUSH_FIELD: { long fieldIdx = bytecodes[bc_idx + 1]; if (method != nullptr && printObjects) { VMClass* holder = dynamic_cast((VMObject*)method->GetHolder()); if (holder) { VMSymbol* name = holder->GetInstanceFieldName(fieldIdx); - DebugPrint("(index: %d) field: %s\n", fieldIdx, - name->GetStdString().c_str()); + if (name != nullptr) { + DebugPrint("(index: %d) field: %s\n", fieldIdx, + name->GetStdString().c_str()); + } else { + DebugPrint("(index: %d) field: !nullptr!: error!\n", + fieldIdx); + } } else { DebugPrint( "(index: %d) block holder is not a class!!\n", @@ -450,24 +437,6 @@ void Disassembler::DumpBytecode(VMFrame* frame, VMMethod* method, long bc_idx) { DebugPrint("\n"); break; } - case BC_PUSH_FIELD: { - VMFrame* ctxt = frame->GetOuterContext(); - vm_oop_t arg = ctxt->GetArgumentInCurrentContext(0); - uint8_t field_index = BC_1; - - vm_oop_t o = ((VMObject*)arg)->GetField(field_index); - VMClass* c = CLASS_OF(o); - VMSymbol* cname = c->GetName(); - long fieldIdx = BC_1; - VMSymbol* name = - method->GetHolder()->GetInstanceFieldName(fieldIdx); - DebugPrint("(index: %d) field: %s <(%s) ", BC_1, - name->GetStdString().c_str(), - cname->GetStdString().c_str()); - dispatch(o); - DebugPrint(">\n"); - break; - } case BC_PUSH_BLOCK: { DebugPrint("block: (index: %d) ", BC_1); VMMethod* meth = dynamic_cast( @@ -540,14 +509,21 @@ void Disassembler::DumpBytecode(VMFrame* frame, VMMethod* method, long bc_idx) { DebugPrint(">\n"); break; } + case BC_INC_FIELD: + case BC_INC_FIELD_PUSH: + case BC_PUSH_FIELD: case BC_POP_FIELD: { - vm_oop_t o = frame->GetStackElement(0); + VMFrame* ctxt = frame->GetOuterContext(); + vm_oop_t arg = ctxt->GetArgumentInCurrentContext(0); + uint8_t fieldIndex = BC_1; + + vm_oop_t o = ((VMObject*)arg)->GetField(fieldIndex); VMClass* c = CLASS_OF(o); + VMSymbol* cname = c->GetName(); long fieldIdx = BC_1; VMSymbol* name = method->GetHolder()->GetInstanceFieldName(fieldIdx); - VMSymbol* cname = c->GetName(); - DebugPrint("(index: %d) field: %s <(%s) ", fieldIdx, + DebugPrint("(index: %d) field: %s <(%s) ", BC_1, name->GetStdString().c_str(), cname->GetStdString().c_str()); dispatch(o); From a031337d22d6d62bc627ce01133eec745dc4a16a Mon Sep 17 00:00:00 2001 From: Stefan Marr Date: Fri, 9 Aug 2024 23:53:18 +0100 Subject: [PATCH 4/4] Make TestWithParsing::dump try to select the right method to dump Signed-off-by: Stefan Marr --- src/unitTests/TestWithParsing.cpp | 17 ++++++++++++----- src/unitTests/TestWithParsing.h | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/unitTests/TestWithParsing.cpp b/src/unitTests/TestWithParsing.cpp index 4c0e460f..d6f59cd5 100644 --- a/src/unitTests/TestWithParsing.cpp +++ b/src/unitTests/TestWithParsing.cpp @@ -18,6 +18,13 @@ #include "../vmobjects/VMMethod.h" void TestWithParsing::dump(MethodGenerationContext* mgenc) { + if (mgenc == nullptr) { + if (_bgenc != nullptr) { + mgenc = _bgenc; + } else { + mgenc = _mgenc; + } + } Disassembler::DumpMethod(mgenc, ""); } @@ -106,7 +113,7 @@ void TestWithParsing::check(std::vector actual, i, bci, Bytecode::GetBytecodeName(expectedBc.bytecode), Bytecode::GetBytecodeName(actualBc)); if (expectedBc.bytecode != actualBc) { - dump(_mgenc); + dump(); } CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, expectedBc.bytecode, actualBc); @@ -117,7 +124,7 @@ void TestWithParsing::check(std::vector actual, (size_t)bcLength); if (expectedBc.size != bcLength) { - dump(_mgenc); + dump(); } CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, expectedBc.size, (size_t)bcLength); @@ -127,7 +134,7 @@ void TestWithParsing::check(std::vector actual, Bytecode::GetBytecodeName(expectedBc.bytecode), expectedBc.arg1, actual.at(bci + 1)); if (expectedBc.arg1 != actual.at(bci + 1)) { - dump(_mgenc); + dump(); } CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, expectedBc.arg1, actual.at(bci + 1)); @@ -138,7 +145,7 @@ void TestWithParsing::check(std::vector actual, i, Bytecode::GetBytecodeName(expectedBc.bytecode), expectedBc.arg2, actual.at(bci + 2)); if (expectedBc.arg2 != actual.at(bci + 2)) { - dump(_mgenc); + dump(); } CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, expectedBc.arg2, actual.at(bci + 2)); @@ -149,7 +156,7 @@ void TestWithParsing::check(std::vector actual, bci += bcLength; } if (expected.size() != i || actual.size() != bci) { - dump(_mgenc); + dump(); } CPPUNIT_ASSERT_EQUAL_MESSAGE("All expected bytecodes covered", diff --git a/src/unitTests/TestWithParsing.h b/src/unitTests/TestWithParsing.h index 98aa378b..8fb1bf57 100644 --- a/src/unitTests/TestWithParsing.h +++ b/src/unitTests/TestWithParsing.h @@ -52,7 +52,7 @@ class TestWithParsing : public CPPUNIT_NS::TestCase { std::vector blockToBytecode(const char* source, bool dumpBytecodes = false); - void dump(MethodGenerationContext* mgenc); + void dump(MethodGenerationContext* mgenc = nullptr); void check(std::vector actual, std::vector expected); };