Skip to content

Commit 7490bff

Browse files
authored
Add inlining for #to:do: loops (#36)
This is based on SOM-st/PySOM#35 which was never merged because of the impact on the JIT compiler. Though, since we have a pure interpreter here, it's a good win on benchmarks that use a `#to:do:` loop. The median run time is reduced by only 2% though. https://rebench.dev/SOMpp/compare/692cc6c47727a7fa76923daf92e0bb8c7d4be3b1..252923132894fff18b7d1364dfb2981f439f146c
2 parents 14ffdfd + b1cfa10 commit 7490bff

18 files changed

+342
-121
lines changed

src/compiler/BytecodeGenerator.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,14 @@ void EmitRETURNNONLOCAL(MethodGenerationContext& mgenc) {
254254
Emit1(mgenc, BC_RETURN_NON_LOCAL, 0);
255255
}
256256

257+
void EmitINC(MethodGenerationContext& mgenc) {
258+
Emit1(mgenc, BC_INC, 0);
259+
}
260+
261+
void EmitDupSecond(MethodGenerationContext& mgenc) {
262+
Emit1(mgenc, BC_DUP_SECOND, 1);
263+
}
264+
257265
size_t EmitJumpOnBoolWithDummyOffset(MethodGenerationContext& mgenc,
258266
bool isIfTrue, bool needsPop) {
259267
// Remember: true and false seem flipped here.
@@ -285,6 +293,13 @@ size_t EmitJumpWithDumyOffset(MethodGenerationContext& mgenc) {
285293
return idx;
286294
}
287295

296+
size_t EmitJumpIfGreaterWithDummyOffset(MethodGenerationContext& mgenc) {
297+
Emit1(mgenc, BC_JUMP_IF_GREATER, 0);
298+
size_t idx = mgenc.AddBytecodeArgumentAndGetIndex(0);
299+
mgenc.AddBytecodeArgument(0);
300+
return idx;
301+
}
302+
288303
void EmitJumpBackwardWithOffset(MethodGenerationContext& mgenc,
289304
size_t jumpOffset) {
290305
uint8_t jumpBytecode =

src/compiler/BytecodeGenerator.h

+4
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,13 @@ void EmitSUPERSEND(MethodGenerationContext& mgenc, VMSymbol* msg);
5858
void EmitRETURNLOCAL(MethodGenerationContext& mgenc);
5959
void EmitRETURNNONLOCAL(MethodGenerationContext& mgenc);
6060

61+
void EmitINC(MethodGenerationContext& mgenc);
62+
void EmitDupSecond(MethodGenerationContext& mgenc);
63+
6164
size_t EmitJumpOnBoolWithDummyOffset(MethodGenerationContext& mgenc,
6265
bool isIfTrue, bool needsPop);
6366
size_t EmitJumpWithDumyOffset(MethodGenerationContext& mgenc);
67+
size_t EmitJumpIfGreaterWithDummyOffset(MethodGenerationContext& mgenc);
6468
void EmitJumpBackwardWithOffset(MethodGenerationContext& mgenc,
6569
size_t jumpOffset);
6670
size_t Emit3WithDummy(MethodGenerationContext& mgenc, uint8_t bytecode,

src/compiler/LexicalScope.h

+8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ class LexicalScope {
2020
locals.push_back(var);
2121
}
2222

23+
const Variable* GetArgument(size_t index, size_t contextLevel) {
24+
if (contextLevel > 0) {
25+
return outer->GetArgument(index, contextLevel - 1);
26+
}
27+
28+
return &arguments.at(index);
29+
}
30+
2331
const Variable* GetLocal(size_t index, uint8_t ctxLevel) {
2432
if (ctxLevel > 0) {
2533
return outer->GetLocal(index, ctxLevel - 1);

src/compiler/MethodGenerationContext.cpp

+53-8
Original file line numberDiff line numberDiff line change
@@ -439,26 +439,71 @@ bool MethodGenerationContext::InlineAndOr(bool isOr) {
439439
return true;
440440
}
441441

442+
bool MethodGenerationContext::InlineToDo() {
443+
// HACK: We do assume that the receiver on the stack is a integer,
444+
// HACK: similar to the other inlined messages.
445+
// HACK: We don't support anything but integer at the moment.
446+
assert(Bytecode::GetBytecodeLength(BC_PUSH_BLOCK) == 2);
447+
if (!hasOneLiteralBlockArgument()) {
448+
return false;
449+
}
450+
451+
VMMethod* toBeInlined =
452+
static_cast<VMMethod*>(extractBlockMethodAndRemoveBytecode());
453+
454+
toBeInlined->MergeScopeInto(*this);
455+
456+
const Variable* blockArg = toBeInlined->GetArgument(1, 0);
457+
uint8_t iVarIdx = GetInlinedLocalIdx(blockArg);
458+
459+
isCurrentlyInliningABlock = true;
460+
EmitDupSecond(*this);
461+
462+
size_t loopBeginIdx = OffsetOfNextInstruction();
463+
size_t jumpOffsetIdxToEnd = EmitJumpIfGreaterWithDummyOffset(*this);
464+
465+
EmitDUP(*this);
466+
467+
EmitPOPLOCAL(*this, iVarIdx, 0);
468+
469+
toBeInlined->InlineInto(*this, false);
470+
471+
EmitPOP(*this);
472+
EmitINC(*this);
473+
474+
EmitBackwardsJumpOffsetToTarget(loopBeginIdx);
475+
476+
PatchJumpOffsetToPointToNextInstruction(jumpOffsetIdxToEnd);
477+
478+
isCurrentlyInliningABlock = false;
479+
480+
return true;
481+
}
482+
442483
void MethodGenerationContext::CompleteLexicalScope() {
443484
lexicalScope = new LexicalScope(
444485
outerGenc == nullptr ? nullptr : outerGenc->lexicalScope, arguments,
445486
locals);
446487
}
447488

448489
void MethodGenerationContext::MergeIntoScope(LexicalScope& scopeToBeInlined) {
449-
assert(scopeToBeInlined.GetNumberOfArguments() == 1);
450-
size_t numLocals = scopeToBeInlined.GetNumberOfLocals();
451-
if (numLocals > 0) {
452-
inlineLocals(scopeToBeInlined);
490+
if (scopeToBeInlined.GetNumberOfArguments() > 1) {
491+
inlineAsLocals(scopeToBeInlined.arguments);
492+
}
493+
494+
if (scopeToBeInlined.GetNumberOfLocals() > 0) {
495+
inlineAsLocals(scopeToBeInlined.locals);
453496
}
454497
}
455498

456-
void MethodGenerationContext::inlineLocals(LexicalScope& scopeToBeInlined) {
457-
for (const Variable& local : scopeToBeInlined.locals) {
458-
Variable freshCopy = local.CopyForInlining(this->locals.size());
499+
void MethodGenerationContext::inlineAsLocals(vector<Variable>& vars) {
500+
for (const Variable& var : vars) {
501+
Variable freshCopy = var.CopyForInlining(this->locals.size());
459502
if (freshCopy.IsValid()) {
503+
assert(!freshCopy.IsArgument());
504+
460505
// freshCopy can be invalid, because we don't need the $blockSelf
461-
std::string qualifiedName = local.MakeQualifiedName();
506+
std::string qualifiedName = var.MakeQualifiedName();
462507
assert(!Contains(this->locals, qualifiedName));
463508
lexicalScope->AddInlinedLocal(freshCopy);
464509
this->locals.push_back(freshCopy);

src/compiler/MethodGenerationContext.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class MethodGenerationContext {
9797
bool InlineIfTrueOrIfFalse(bool isIfTrue);
9898
bool InlineIfTrueFalse(bool isIfTrue);
9999
bool InlineAndOr(bool isOr);
100+
bool InlineToDo();
100101

101102
inline size_t OffsetOfNextInstruction() { return bytecode.size(); }
102103

@@ -120,7 +121,7 @@ class MethodGenerationContext {
120121

121122
void completeJumpsAndEmitReturningNil(Parser& parser, size_t loopBeginIdx,
122123
size_t jumpOffsetIdxToSkipLoopBody);
123-
void inlineLocals(LexicalScope& scopeToBeInlined);
124+
void inlineAsLocals(vector<Variable>& vars);
124125
void checkJumpOffset(size_t jumpOffset, uint8_t bytecode);
125126
void resetLastBytecodeBuffer();
126127

src/compiler/Parser.cpp

+4-3
Original file line numberDiff line numberDiff line change
@@ -598,8 +598,8 @@ void Parser::binaryMessage(MethodGenerationContext& mgenc, bool super) {
598598

599599
binaryOperand(mgenc);
600600

601-
if (!super && (msgSelector == "||" && mgenc.InlineAndOr(true)) ||
602-
(msgSelector == "&&" && mgenc.InlineAndOr(false))) {
601+
if (!super && ((msgSelector == "||" && mgenc.InlineAndOr(true)) ||
602+
(msgSelector == "&&" && mgenc.InlineAndOr(false)))) {
603603
return;
604604
}
605605

@@ -645,7 +645,8 @@ void Parser::keywordMessage(MethodGenerationContext& mgenc, bool super) {
645645

646646
if (numParts == 2 &&
647647
((kw == "ifTrue:ifFalse:" && mgenc.InlineIfTrueFalse(true)) ||
648-
(kw == "ifFalse:ifTrue:" && mgenc.InlineIfTrueFalse(false)))) {
648+
(kw == "ifFalse:ifTrue:" && mgenc.InlineIfTrueFalse(false)) ||
649+
(kw == "to:do:" && mgenc.InlineToDo()))) {
649650
return;
650651
}
651652
}

src/compiler/Variable.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ std::string Variable::MakeQualifiedName() const {
2121
Variable Variable::CopyForInlining(size_t newIndex) const {
2222
if (isArgument) {
2323
if (name == strBlockSelf) {
24+
// that's invalid
2425
return Variable();
2526
}
26-
return Variable(this, newIndex, true);
2727
}
28+
// arguments that are inlined need to turn into variables, too
2829
return Variable(this, newIndex, false);
2930
}

src/compiler/Variable.h

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class Variable {
3434

3535
Variable CopyForInlining(size_t newIndex) const;
3636

37+
bool IsArgument() const { return isArgument; }
38+
3739
protected:
3840
std::string name;
3941
uint8_t index;

src/interpreter/Interpreter.cpp

+34
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "../interpreter/bytecodes.h" // NOLINT(misc-include-cleaner) it's required for InterpreterLoop.h
3535
#include "../memory/Heap.h"
3636
#include "../misc/defs.h"
37+
#include "../vm/Globals.h"
3738
#include "../vm/IsValidObject.h"
3839
#include "../vm/Universe.h"
3940
#include "../vmobjects/IntegerBox.h"
@@ -42,6 +43,7 @@
4243
#include "../vmobjects/VMArray.h"
4344
#include "../vmobjects/VMBlock.h"
4445
#include "../vmobjects/VMClass.h"
46+
#include "../vmobjects/VMDouble.h"
4547
#include "../vmobjects/VMFrame.h"
4648
#include "../vmobjects/VMInvokable.h"
4749
#include "../vmobjects/VMMethod.h"
@@ -407,6 +409,38 @@ void Interpreter::doReturnNonLocal() {
407409
popFrameAndPushResult(result);
408410
}
409411

412+
void Interpreter::doInc() {
413+
vm_oop_t val = GetFrame()->Top();
414+
415+
if (IS_TAGGED(val) || CLASS_OF(val) == load_ptr(integerClass)) {
416+
int64_t result = (int64_t)INT_VAL(val) + 1;
417+
val = NEW_INT(result);
418+
} else if (CLASS_OF(val) == load_ptr(doubleClass)) {
419+
double d = static_cast<VMDouble*>(val)->GetEmbeddedDouble();
420+
val = GetUniverse()->NewDouble(d + 1.0);
421+
} else {
422+
GetUniverse()->ErrorExit("unsupported");
423+
}
424+
425+
GetFrame()->SetTop(store_root(val));
426+
}
427+
428+
bool Interpreter::checkIsGreater() {
429+
vm_oop_t top = GetFrame()->Top();
430+
vm_oop_t top2 = GetFrame()->Top2();
431+
432+
if ((IS_TAGGED(top) || CLASS_OF(top) == load_ptr(integerClass)) &&
433+
(IS_TAGGED(top2) || CLASS_OF(top2) == load_ptr(integerClass))) {
434+
return INT_VAL(top) > INT_VAL(top2);
435+
} else if ((CLASS_OF(top) == load_ptr(doubleClass)) &&
436+
(CLASS_OF(top2) == load_ptr(doubleClass))) {
437+
return static_cast<VMDouble*>(top)->GetEmbeddedDouble() >
438+
static_cast<VMDouble*>(top2)->GetEmbeddedDouble();
439+
}
440+
441+
return false;
442+
}
443+
410444
void Interpreter::WalkGlobals(walk_heap_fn walk) {
411445
method = load_ptr(static_cast<GCMethod*>(walk(tmp_ptr(method))));
412446

src/interpreter/Interpreter.h

+2
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ class Interpreter {
106106
void doSuperSend(long bytecodeIndex);
107107
void doReturnLocal();
108108
void doReturnNonLocal();
109+
void doInc();
110+
bool checkIsGreater();
109111
};
110112

111113
inline VMFrame* Interpreter::GetFrame() const {

src/interpreter/InterpreterLoop.h

+41
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ vm_oop_t Start() {
77

88
void* loopTargets[] = {&&LABEL_BC_HALT,
99
&&LABEL_BC_DUP,
10+
&&LABEL_BC_DUP_SECOND,
1011
&&LABEL_BC_PUSH_LOCAL,
1112
&&LABEL_BC_PUSH_LOCAL_0,
1213
&&LABEL_BC_PUSH_LOCAL_1,
@@ -40,17 +41,20 @@ vm_oop_t Start() {
4041
&&LABEL_BC_SUPER_SEND,
4142
&&LABEL_BC_RETURN_LOCAL,
4243
&&LABEL_BC_RETURN_NON_LOCAL,
44+
&&LABEL_BC_INC,
4345
&&LABEL_BC_JUMP,
4446
&&LABEL_BC_JUMP_ON_FALSE_POP,
4547
&&LABEL_BC_JUMP_ON_TRUE_POP,
4648
&&LABEL_BC_JUMP_ON_FALSE_TOP_NIL,
4749
&&LABEL_BC_JUMP_ON_TRUE_TOP_NIL,
50+
&&LABEL_BC_JUMP_IF_GREATER,
4851
&&LABEL_BC_JUMP_BACKWARD,
4952
&&LABEL_BC_JUMP2,
5053
&&LABEL_BC_JUMP2_ON_FALSE_POP,
5154
&&LABEL_BC_JUMP2_ON_TRUE_POP,
5255
&&LABEL_BC_JUMP2_ON_FALSE_TOP_NIL,
5356
&&LABEL_BC_JUMP2_ON_TRUE_TOP_NIL,
57+
&&LABEL_BC_JUMP2_IF_GREATER,
5458
&&LABEL_BC_JUMP2_BACKWARD};
5559

5660
goto* loopTargets[currentBytecodes[bytecodeIndexGlobal]];
@@ -66,6 +70,14 @@ vm_oop_t Start() {
6670
doDup();
6771
DISPATCH_NOGC();
6872

73+
LABEL_BC_DUP_SECOND:
74+
PROLOGUE(1);
75+
{
76+
vm_oop_t elem = GetFrame()->GetStackElement(1);
77+
GetFrame()->Push(elem);
78+
}
79+
DISPATCH_NOGC();
80+
6981
LABEL_BC_PUSH_LOCAL:
7082
PROLOGUE(3);
7183
doPushLocal(bytecodeIndexGlobal - 3);
@@ -249,6 +261,11 @@ vm_oop_t Start() {
249261
doReturnNonLocal();
250262
DISPATCH_NOGC();
251263

264+
LABEL_BC_INC:
265+
PROLOGUE(1);
266+
doInc();
267+
DISPATCH_NOGC();
268+
252269
LABEL_BC_JUMP: {
253270
uint8_t offset = currentBytecodes[bytecodeIndexGlobal + 1];
254271
bytecodeIndexGlobal += offset;
@@ -305,6 +322,17 @@ LABEL_BC_JUMP_ON_TRUE_TOP_NIL: {
305322
}
306323
DISPATCH_NOGC();
307324

325+
LABEL_BC_JUMP_IF_GREATER: {
326+
if (checkIsGreater()) {
327+
bytecodeIndexGlobal += currentBytecodes[bytecodeIndexGlobal + 1];
328+
GetFrame()->Pop();
329+
GetFrame()->Pop();
330+
} else {
331+
bytecodeIndexGlobal += 3;
332+
}
333+
}
334+
DISPATCH_NOGC();
335+
308336
LABEL_BC_JUMP_BACKWARD: {
309337
uint8_t offset = currentBytecodes[bytecodeIndexGlobal + 1];
310338
bytecodeIndexGlobal -= offset;
@@ -376,6 +404,19 @@ LABEL_BC_JUMP2_ON_TRUE_TOP_NIL: {
376404
}
377405
DISPATCH_NOGC();
378406

407+
LABEL_BC_JUMP2_IF_GREATER: {
408+
if (checkIsGreater()) {
409+
bytecodeIndexGlobal +=
410+
ComputeOffset(currentBytecodes[bytecodeIndexGlobal + 1],
411+
currentBytecodes[bytecodeIndexGlobal + 2]);
412+
GetFrame()->Pop();
413+
GetFrame()->Pop();
414+
} else {
415+
bytecodeIndexGlobal += 3;
416+
}
417+
}
418+
DISPATCH_NOGC();
419+
379420
LABEL_BC_JUMP2_BACKWARD: {
380421
uint16_t offset = ComputeOffset(currentBytecodes[bytecodeIndexGlobal + 1],
381422
currentBytecodes[bytecodeIndexGlobal + 2]);

0 commit comments

Comments
 (0)