Skip to content

Commit 14ffdfd

Browse files
authored
Inline #ifTrue:, #ifFalse:, #ifTrue:ifFalse:, #ifFalse:ifTrue:, #||, #or:, #&&, #and: (#35)
This PR adds the inlining for the various messages. It reduces the median run time by 23% https://rebench.dev/SOMpp/compare/2dc14379c566f40d51791d0e6eca95d743952799..692cc6c47727a7fa76923daf92e0bb8c7d4be3b1
2 parents 2dc1437 + 7a105bf commit 14ffdfd

9 files changed

+472
-343
lines changed

src/compiler/BytecodeGenerator.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,13 @@ size_t EmitJumpOnBoolWithDummyOffset(MethodGenerationContext& mgenc,
278278
return idx;
279279
}
280280

281+
size_t EmitJumpWithDumyOffset(MethodGenerationContext& mgenc) {
282+
Emit1(mgenc, BC_JUMP, 0);
283+
size_t idx = mgenc.AddBytecodeArgumentAndGetIndex(0);
284+
mgenc.AddBytecodeArgument(0);
285+
return idx;
286+
}
287+
281288
void EmitJumpBackwardWithOffset(MethodGenerationContext& mgenc,
282289
size_t jumpOffset) {
283290
uint8_t jumpBytecode =

src/compiler/BytecodeGenerator.h

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ void EmitRETURNNONLOCAL(MethodGenerationContext& mgenc);
6060

6161
size_t EmitJumpOnBoolWithDummyOffset(MethodGenerationContext& mgenc,
6262
bool isIfTrue, bool needsPop);
63+
size_t EmitJumpWithDumyOffset(MethodGenerationContext& mgenc);
6364
void EmitJumpBackwardWithOffset(MethodGenerationContext& mgenc,
6465
size_t jumpOffset);
6566
size_t Emit3WithDummy(MethodGenerationContext& mgenc, uint8_t bytecode,

src/compiler/MethodGenerationContext.cpp

+115
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,10 @@ void MethodGenerationContext::removeLastBytecodes(size_t numBytecodes) {
259259
bytecode.erase(bytecode.end() - bytesToRemove, bytecode.end());
260260
}
261261

262+
bool MethodGenerationContext::hasOneLiteralBlockArgument() {
263+
return lastBytecodeIs(0, BC_PUSH_BLOCK);
264+
}
265+
262266
bool MethodGenerationContext::hasTwoLiteralBlockArguments() {
263267
if (!lastBytecodeIs(0, BC_PUSH_BLOCK)) {
264268
return false;
@@ -280,6 +284,16 @@ vm_oop_t MethodGenerationContext::getLastBlockMethodAndFreeLiteral(
280284
return block;
281285
}
282286

287+
vm_oop_t MethodGenerationContext::extractBlockMethodAndRemoveBytecode() {
288+
uint8_t blockLitIdx = bytecode.at(bytecode.size() - 1);
289+
290+
vm_oop_t toBeInlined = getLastBlockMethodAndFreeLiteral(blockLitIdx);
291+
292+
removeLastBytecodes(1);
293+
294+
return toBeInlined;
295+
}
296+
283297
std::tuple<vm_oop_t, vm_oop_t>
284298
MethodGenerationContext::extractBlockMethodsAndRemoveBytecodes() {
285299
uint8_t block1LitIdx = bytecode.at(bytecode.size() - 3);
@@ -294,6 +308,74 @@ MethodGenerationContext::extractBlockMethodsAndRemoveBytecodes() {
294308
return {toBeInlined1, toBeInlined2};
295309
}
296310

311+
bool MethodGenerationContext::InlineIfTrueOrIfFalse(bool isIfTrue) {
312+
// HACK: We do assume that the receiver on the stack is a boolean,
313+
// HACK: similar to the IfTrueIfFalseNode.
314+
// HACK: We don't support anything but booleans at the moment.
315+
assert(Bytecode::GetBytecodeLength(BC_PUSH_BLOCK) == 2);
316+
if (!hasOneLiteralBlockArgument()) {
317+
return false;
318+
}
319+
320+
VMMethod* toBeInlined =
321+
static_cast<VMMethod*>(extractBlockMethodAndRemoveBytecode());
322+
323+
size_t jumpOffsetIdxToSkipBody =
324+
EmitJumpOnBoolWithDummyOffset(*this, isIfTrue, false);
325+
326+
isCurrentlyInliningABlock = true;
327+
328+
toBeInlined->InlineInto(*this);
329+
PatchJumpOffsetToPointToNextInstruction(jumpOffsetIdxToSkipBody);
330+
331+
// with the jumping, it's best to prevent any subsequent optimizations here
332+
// otherwise we may not have the correct jump target
333+
resetLastBytecodeBuffer();
334+
335+
return true;
336+
}
337+
338+
bool MethodGenerationContext::InlineIfTrueFalse(bool isIfTrue) {
339+
// HACK: We do assume that the receiver on the stack is a boolean,
340+
// HACK: similar to the IfTrueIfFalseNode.
341+
// HACK: We don't support anything but booleans at the moment.
342+
343+
if (!hasTwoLiteralBlockArguments()) {
344+
return false;
345+
}
346+
347+
assert(Bytecode::GetBytecodeLength(BC_PUSH_BLOCK) == 2);
348+
349+
std::tuple<vm_oop_t, vm_oop_t> methods =
350+
extractBlockMethodsAndRemoveBytecodes();
351+
VMMethod* condMethod = static_cast<VMMethod*>(std::get<0>(methods));
352+
VMMethod* bodyMethod = static_cast<VMMethod*>(std::get<1>(methods));
353+
354+
size_t jumpOffsetIdxToSkipTrueBranch =
355+
EmitJumpOnBoolWithDummyOffset(*this, isIfTrue, true);
356+
357+
isCurrentlyInliningABlock = true;
358+
condMethod->InlineInto(*this);
359+
360+
size_t jumpOffsetIdxToSkipFalseBranch = EmitJumpWithDumyOffset(*this);
361+
362+
PatchJumpOffsetToPointToNextInstruction(jumpOffsetIdxToSkipTrueBranch);
363+
364+
// prevent optimizations between blocks to avoid issues with jump targets
365+
resetLastBytecodeBuffer();
366+
367+
bodyMethod->InlineInto(*this);
368+
369+
isCurrentlyInliningABlock = false;
370+
371+
PatchJumpOffsetToPointToNextInstruction(jumpOffsetIdxToSkipFalseBranch);
372+
373+
// prevent optimizations messing with the final jump target
374+
resetLastBytecodeBuffer();
375+
376+
return true;
377+
}
378+
297379
bool MethodGenerationContext::InlineWhile(Parser& parser, bool isWhileTrue) {
298380
if (!hasTwoLiteralBlockArguments()) {
299381
return false;
@@ -324,6 +406,39 @@ bool MethodGenerationContext::InlineWhile(Parser& parser, bool isWhileTrue) {
324406
return true;
325407
}
326408

409+
bool MethodGenerationContext::InlineAndOr(bool isOr) {
410+
// HACK: We do assume that the receiver on the stack is a boolean,
411+
// HACK: similar to the IfTrueIfFalseNode.
412+
// HACK: We don't support anything but booleans at the moment.
413+
414+
assert(Bytecode::GetBytecodeLength(BC_PUSH_BLOCK) == 2);
415+
if (!hasOneLiteralBlockArgument()) {
416+
return false;
417+
}
418+
419+
VMMethod* toBeInlined =
420+
static_cast<VMMethod*>(extractBlockMethodAndRemoveBytecode());
421+
422+
size_t jumpOffsetIdxToSkipBranch =
423+
EmitJumpOnBoolWithDummyOffset(*this, !isOr, true);
424+
425+
isCurrentlyInliningABlock = true;
426+
toBeInlined->InlineInto(*this);
427+
isCurrentlyInliningABlock = false;
428+
429+
size_t jumpOffsetIdxToSkipPushTrue = EmitJumpWithDumyOffset(*this);
430+
431+
PatchJumpOffsetToPointToNextInstruction(jumpOffsetIdxToSkipBranch);
432+
EmitPUSHCONSTANT(*this,
433+
isOr ? load_ptr(trueObject) : load_ptr(falseObject));
434+
435+
PatchJumpOffsetToPointToNextInstruction(jumpOffsetIdxToSkipPushTrue);
436+
437+
resetLastBytecodeBuffer();
438+
439+
return true;
440+
}
441+
327442
void MethodGenerationContext::CompleteLexicalScope() {
328443
lexicalScope = new LexicalScope(
329444
outerGenc == nullptr ? nullptr : outerGenc->lexicalScope, arguments,

src/compiler/MethodGenerationContext.h

+6
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ class MethodGenerationContext {
9494
std::vector<uint8_t> GetBytecodes() { return bytecode; }
9595

9696
bool InlineWhile(Parser& parser, bool isWhileTrue);
97+
bool InlineIfTrueOrIfFalse(bool isIfTrue);
98+
bool InlineIfTrueFalse(bool isIfTrue);
99+
bool InlineAndOr(bool isOr);
97100

98101
inline size_t OffsetOfNextInstruction() { return bytecode.size(); }
99102

@@ -107,9 +110,12 @@ class MethodGenerationContext {
107110

108111
private:
109112
void removeLastBytecodes(size_t numBytecodes);
113+
bool hasOneLiteralBlockArgument();
110114
bool hasTwoLiteralBlockArguments();
111115
bool lastBytecodeIs(size_t indexFromEnd, uint8_t bytecode);
112116
std::tuple<vm_oop_t, vm_oop_t> extractBlockMethodsAndRemoveBytecodes();
117+
vm_oop_t extractBlockMethodAndRemoveBytecode();
118+
113119
vm_oop_t getLastBlockMethodAndFreeLiteral(uint8_t blockLiteralIdx);
114120

115121
void completeJumpsAndEmitReturningNil(Parser& parser, size_t loopBeginIdx,

src/compiler/Parser.cpp

+18-2
Original file line numberDiff line numberDiff line change
@@ -593,10 +593,16 @@ void Parser::unaryMessage(MethodGenerationContext& mgenc, bool super) {
593593
}
594594

595595
void Parser::binaryMessage(MethodGenerationContext& mgenc, bool super) {
596+
std::string msgSelector(text);
596597
VMSymbol* msg = binarySelector();
597598

598599
binaryOperand(mgenc);
599600

601+
if (!super && (msgSelector == "||" && mgenc.InlineAndOr(true)) ||
602+
(msgSelector == "&&" && mgenc.InlineAndOr(false))) {
603+
return;
604+
}
605+
600606
if (super) {
601607
EmitSUPERSEND(mgenc, msg);
602608
} else {
@@ -628,8 +634,18 @@ void Parser::keywordMessage(MethodGenerationContext& mgenc, bool super) {
628634

629635
if (!super) {
630636
if (numParts == 1 &&
631-
((kw == "whileTrue:" && mgenc.InlineWhile(*this, true)) ||
632-
(kw == "whileFalse:" && mgenc.InlineWhile(*this, false)))) {
637+
((kw == "ifTrue:" && mgenc.InlineIfTrueOrIfFalse(true)) ||
638+
(kw == "ifFalse:" && mgenc.InlineIfTrueOrIfFalse(false)) ||
639+
(kw == "whileTrue:" && mgenc.InlineWhile(*this, true)) ||
640+
(kw == "whileFalse:" && mgenc.InlineWhile(*this, false)) ||
641+
(kw == "or:" && mgenc.InlineAndOr(true)) ||
642+
(kw == "and:" && mgenc.InlineAndOr(false)))) {
643+
return;
644+
}
645+
646+
if (numParts == 2 &&
647+
((kw == "ifTrue:ifFalse:" && mgenc.InlineIfTrueFalse(true)) ||
648+
(kw == "ifFalse:ifTrue:" && mgenc.InlineIfTrueFalse(false)))) {
633649
return;
634650
}
635651
}

src/interpreter/bytecodes.cpp

+47-25
Original file line numberDiff line numberDiff line change
@@ -82,31 +82,53 @@ const uint8_t Bytecode::bytecodeLengths[] = {
8282
};
8383

8484
const char* Bytecode::bytecodeNames[] = {
85-
"HALT ", "DUP ",
86-
"PUSH_LOCAL ", "PUSH_LOCAL_0 ",
87-
"PUSH_LOCAL_1 ", "PUSH_LOCAL_2 ",
88-
"PUSH_ARGUMENT ", "PUSH_SELF ",
89-
"PUSH_ARG_1 ", "PUSH_ARG_2 ",
90-
"PUSH_FIELD ", "PUSH_FIELD_0 ",
91-
"PUSH_FIELD_1 ", "PUSH_BLOCK ",
92-
"PUSH_CONSTANT ", "PUSH_CONSTANT_0 ",
93-
"PUSH_CONSTANT_1 ", "PUSH_CONSTANT_2 ",
94-
"PUSH_0 ", "PUSH_1 ",
95-
"PUSH_NIL ", "PUSH_GLOBAL ",
96-
"POP ", "POP_LOCAL ",
97-
"POP_LOCAL_0 ", "POP_LOCAL_1 ",
98-
"POP_LOCAL_2 ", "POP_ARGUMENT ",
99-
"POP_FIELD ", "POP_FIELD_0 ",
100-
"POP_FIELD_1 ", "SEND ",
101-
"SUPER_SEND ", "RETURN_LOCAL ",
102-
"RETURN_NON_LOCAL", "BC_JUMP ",
103-
"BC_JUMP_ON_TRUE_TOP_NIL", "BC_JUMP_ON_FALSE_TOP_NIL",
104-
"BC_JUMP_ON_TRUE_POP", "BC_JUMP_ON_FALSE_POP",
105-
"BC_JUMP_BACKWARD",
106-
107-
"BC_JUMP2 ", "BC_JUMP2_ON_TRUE_TOP_NIL",
108-
"BC_JUMP2_ON_FALSE_TOP_NIL", "BC_JUMP2_ON_TRUE_POP",
109-
"BC_JUMP2_ON_FALSE_POP", "BC_JUMP2_BACKWARD",
85+
"HALT ", // 0
86+
"DUP ", // 1
87+
"PUSH_LOCAL ", // 2
88+
"PUSH_LOCAL_0 ", // 3
89+
"PUSH_LOCAL_1 ", // 4
90+
"PUSH_LOCAL_2 ", // 5
91+
"PUSH_ARGUMENT ", // 6
92+
"PUSH_SELF ", // 7
93+
"PUSH_ARG_1 ", // 8
94+
"PUSH_ARG_2 ", // 9
95+
"PUSH_FIELD ", // 10
96+
"PUSH_FIELD_0 ", // 11
97+
"PUSH_FIELD_1 ", // 12
98+
"PUSH_BLOCK ", // 13
99+
"PUSH_CONSTANT ", // 14
100+
"PUSH_CONSTANT_0 ", // 15
101+
"PUSH_CONSTANT_1 ", // 16
102+
"PUSH_CONSTANT_2 ", // 17
103+
"PUSH_0 ", // 18
104+
"PUSH_1 ", // 19
105+
"PUSH_NIL ", // 20
106+
"PUSH_GLOBAL ", // 21
107+
"POP ", // 22
108+
"POP_LOCAL ", // 23
109+
"POP_LOCAL_0 ", // 24
110+
"POP_LOCAL_1 ", // 25
111+
"POP_LOCAL_2 ", // 26
112+
"POP_ARGUMENT ", // 27
113+
"POP_FIELD ", // 28
114+
"POP_FIELD_0 ", // 29
115+
"POP_FIELD_1 ", // 30
116+
"SEND ", // 31
117+
"SUPER_SEND ", // 32
118+
"RETURN_LOCAL ", // 33
119+
"RETURN_NON_LOCAL", // 34
120+
"BC_JUMP ", // 35
121+
"BC_JUMP_ON_FALSE_POP", // 36
122+
"BC_JUMP_ON_TRUE_POP", // 37
123+
"BC_JUMP_ON_FALSE_TOP_NIL", // 38
124+
"BC_JUMP_ON_TRUE_TOP_NIL", // 39
125+
"BC_JUMP_BACKWARD", // 40
126+
"BC_JUMP2 ", // 41
127+
"BC_JUMP2_ON_FALSE_POP", // 42
128+
"BC_JUMP2_ON_TRUE_POP", // 43
129+
"BC_JUMP2_ON_FALSE_TOP_NIL", // 44
130+
"BC_JUMP2_ON_TRUE_TOP_NIL", // 45
131+
"BC_JUMP2_BACKWARD", // 46
110132
};
111133

112134
bool IsJumpBytecode(uint8_t bc) {

0 commit comments

Comments
 (0)