From 77192fa9dd4d0021790b95c1c672fe4658056408 Mon Sep 17 00:00:00 2001
From: Christoph Oelckers <c.oelckers@users.noreply.github.com>
Date: Fri, 2 Dec 2016 00:51:29 +0100
Subject: [PATCH] - branch optimization.

---
 src/scripting/codegeneration/codegen.cpp | 241 ++++++++++++++---------
 src/scripting/codegeneration/codegen.h   |   7 +
 src/scripting/vm/vmbuilder.cpp           |  13 ++
 src/scripting/vm/vmbuilder.h             |   2 +
 4 files changed, 166 insertions(+), 97 deletions(-)

diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp
index b6de3a664..2d93dc866 100644
--- a/src/scripting/codegeneration/codegen.cpp
+++ b/src/scripting/codegeneration/codegen.cpp
@@ -288,6 +288,45 @@ ExpEmit FxExpression::Emit (VMFunctionBuilder *build)
 }
 
 
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+void FxExpression::EmitCompare(VMFunctionBuilder *build, bool invert, TArray<size_t> &patchspots_yes, TArray<size_t> &patchspots_no)
+{
+	ExpEmit op = Emit(build);
+	ExpEmit i;
+	assert(op.RegType != REGT_NIL && op.RegCount == 1 && !op.Konst);
+	switch (op.RegType)
+	{
+	case REGT_INT:
+		build->Emit(OP_EQ_K, !invert, op.RegNum, build->GetConstantInt(0));
+		break;
+
+	case REGT_FLOAT:
+		build->Emit(OP_EQF_K, !invert, op.RegNum, build->GetConstantFloat(0));
+		break;
+
+	case REGT_POINTER:
+		build->Emit(OP_EQA_K, !invert, op.RegNum, build->GetConstantAddress(0, ATAG_GENERIC));
+		break;
+
+	case REGT_STRING:
+		i = ExpEmit(build, REGT_INT);
+		build->Emit(OP_LENS, i.RegNum, op.RegNum);
+		build->Emit(OP_EQ_K, !invert, i.RegNum, build->GetConstantInt(0));
+		i.Free(build);
+		break;
+
+	default:
+		break;
+	}
+	patchspots_no.Push(build->Emit(OP_JMP, 0));
+	op.Free(build);
+}
+
 //==========================================================================
 //
 //
@@ -757,7 +796,6 @@ ExpEmit FxBoolCast::Emit(VMFunctionBuilder *build)
 	{
 		ExpEmit to(build, REGT_INT);
 		from.Free(build);
-		// Preload result with 0.
 		build->Emit(OP_CASTB, to.RegNum, from.RegNum, from.RegType == REGT_INT ? CASTB_I : from.RegType == REGT_FLOAT ? CASTB_F : CASTB_A);
 		return to;
 	}
@@ -773,6 +811,17 @@ ExpEmit FxBoolCast::Emit(VMFunctionBuilder *build)
 //
 //==========================================================================
 
+void FxBoolCast::EmitCompare(VMFunctionBuilder *build, bool invert, TArray<size_t> &patchspots_yes, TArray<size_t> &patchspots_no)
+{
+	basex->EmitCompare(build, invert, patchspots_yes, patchspots_no);
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
 FxIntCast::FxIntCast(FxExpression *x, bool nowarn, bool explicitly)
 : FxExpression(EFX_IntCast, x->ScriptPosition)
 {
@@ -1872,6 +1921,17 @@ ExpEmit FxUnaryNotBoolean::Emit(VMFunctionBuilder *build)
 //
 //==========================================================================
 
+void FxUnaryNotBoolean::EmitCompare(VMFunctionBuilder *build, bool invert, TArray<size_t> &patchspots_yes, TArray<size_t> &patchspots_no)
+{
+	Operand->EmitCompare(build, !invert, patchspots_yes, patchspots_no);
+}
+
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
 FxSizeAlign::FxSizeAlign(FxExpression *operand, int which)
 	: FxExpression(EFX_SizeAlign, operand->ScriptPosition)
 {
@@ -3124,7 +3184,7 @@ FxExpression *FxCompareRel::Resolve(FCompileContext& ctx)
 //
 //==========================================================================
 
-ExpEmit FxCompareRel::Emit(VMFunctionBuilder *build)
+ExpEmit FxCompareRel::EmitCommon(VMFunctionBuilder *build, bool forcompare, bool invert)
 {
 	ExpEmit op1 = left->Emit(build);
 	ExpEmit op2 = right->Emit(build);
@@ -3154,11 +3214,15 @@ ExpEmit FxCompareRel::Emit(VMFunctionBuilder *build)
 		{
 			op2.Free(build);
 		}
+		if (invert) a ^= CMP_CHECK;
 
-		build->Emit(OP_LI, to.RegNum, 0, 0);
+		if (!forcompare) build->Emit(OP_LI, to.RegNum, 0, 0);
 		build->Emit(OP_CMPS, a, op1.RegNum, op2.RegNum);
-		build->Emit(OP_JMP, 1);
-		build->Emit(OP_LI, to.RegNum, 1);
+		if (!forcompare)
+		{
+			build->Emit(OP_JMP, 1);
+			build->Emit(OP_LI, to.RegNum, 1);
+		}
 		return to;
 	}
 	else
@@ -3197,16 +3261,32 @@ ExpEmit FxCompareRel::Emit(VMFunctionBuilder *build)
 		{
 			op1.Free(build);
 		}
+		if (invert) check ^= 1;
 
 		// See FxBoolCast for comments, since it's the same thing.
-		build->Emit(OP_LI, to.RegNum, 0, 0);
+		if (!forcompare) build->Emit(OP_LI, to.RegNum, 0, 0);
 		build->Emit(instr, check, op1.RegNum, op2.RegNum);
-		build->Emit(OP_JMP, 1);
-		build->Emit(OP_LI, to.RegNum, 1);
+		if (!forcompare)
+		{
+			build->Emit(OP_JMP, 1);
+			build->Emit(OP_LI, to.RegNum, 1);
+		}
 		return to;
 	}
 }
 
+ExpEmit FxCompareRel::Emit(VMFunctionBuilder *build)
+{
+	return EmitCommon(build, false, false);
+}
+
+void FxCompareRel::EmitCompare(VMFunctionBuilder *build, bool invert, TArray<size_t> &patchspots_yes, TArray<size_t> &patchspots_no)
+{
+	ExpEmit emit = EmitCommon(build, true, invert);
+	emit.Free(build);
+	patchspots_no.Push(build->Emit(OP_JMP, 0));
+}
+
 //==========================================================================
 //
 //
@@ -3404,7 +3484,7 @@ error:
 //
 //==========================================================================
 
-ExpEmit FxCompareEq::Emit(VMFunctionBuilder *build)
+ExpEmit FxCompareEq::EmitCommon(VMFunctionBuilder *build, bool forcompare, bool invert)
 {
 	ExpEmit op1 = left->Emit(build);
 	ExpEmit op2 = right->Emit(build);
@@ -3418,13 +3498,17 @@ ExpEmit FxCompareEq::Emit(VMFunctionBuilder *build)
 		int a = Operator == TK_Eq ? CMP_EQ :
 			Operator == TK_Neq ? CMP_EQ | CMP_CHECK : CMP_EQ | CMP_APPROX;
 
-		if (op1.Konst) a|= CMP_BK;
+		if (op1.Konst) a |= CMP_BK;
 		if (op2.Konst) a |= CMP_CK;
+		if (invert) a ^= CMP_CHECK;
 
-		build->Emit(OP_LI, to.RegNum, 0, 0);
+		if (!forcompare) build->Emit(OP_LI, to.RegNum, 0, 0);
 		build->Emit(OP_CMPS, a, op1.RegNum, op2.RegNum);
-		build->Emit(OP_JMP, 1);
-		build->Emit(OP_LI, to.RegNum, 1);
+		if (!forcompare)
+		{
+			build->Emit(OP_JMP, 1);
+			build->Emit(OP_LI, to.RegNum, 1);
+		}
 		op1.Free(build);
 		op2.Free(build);
 		return to;
@@ -3457,14 +3541,29 @@ ExpEmit FxCompareEq::Emit(VMFunctionBuilder *build)
 		}
 
 		// See FxUnaryNotBoolean for comments, since it's the same thing.
-		build->Emit(OP_LI, to.RegNum, 0, 0);
-		build->Emit(instr, Operator == TK_ApproxEq ? CMP_APPROX : ((Operator != TK_Eq) ? CMP_CHECK : 0), op1.RegNum, op2.RegNum);
-		build->Emit(OP_JMP, 1);
-		build->Emit(OP_LI, to.RegNum, 1);
+		if (!forcompare) build->Emit(OP_LI, to.RegNum, 0, 0);
+		build->Emit(instr, int(invert) ^ (Operator == TK_ApproxEq ? CMP_APPROX : ((Operator != TK_Eq) ? CMP_CHECK : 0)), op1.RegNum, op2.RegNum);
+		if (!forcompare)
+		{
+			build->Emit(OP_JMP, 1);
+			build->Emit(OP_LI, to.RegNum, 1);
+		}
 		return to;
 	}
 }
 
+ExpEmit FxCompareEq::Emit(VMFunctionBuilder *build)
+{
+	return EmitCommon(build, false, false);
+}
+
+void FxCompareEq::EmitCompare(VMFunctionBuilder *build, bool invert, TArray<size_t> &patchspots_yes, TArray<size_t> &patchspots_no)
+{
+	ExpEmit emit = EmitCommon(build, true, invert);
+	emit.Free(build);
+	patchspots_no.Push(build->Emit(OP_JMP, 0));
+}
+
 //==========================================================================
 //
 //
@@ -4447,21 +4546,17 @@ FxExpression *FxConditional::Resolve(FCompileContext& ctx)
 
 ExpEmit FxConditional::Emit(VMFunctionBuilder *build)
 {
-	size_t truejump, falsejump;
-	ExpEmit out;
+	size_t truejump;
+	ExpEmit out, falseout;
 
 	// The true and false expressions ought to be assigned to the
 	// same temporary instead of being copied to it. Oh well; good enough
 	// for now.
-	ExpEmit cond = condition->Emit(build);
-	assert(cond.RegType == REGT_INT && !cond.Konst);
+	TArray<size_t> yes, no;
+	condition->EmitCompare(build, false, yes, no);
 
-	// Test condition.
-	build->Emit(OP_EQ_K, 1, cond.RegNum, build->GetConstantInt(0));
-	falsejump = build->Emit(OP_JMP, 0);
-	cond.Free(build);
+	build->BackpatchListToHere(yes);
 
-	// Evaluate true expression.
 	if (truex->isConstant() && truex->ValueType->GetRegType() == REGT_INT)
 	{
 		out = ExpEmit(build, REGT_INT);
@@ -4501,7 +4596,7 @@ ExpEmit FxConditional::Emit(VMFunctionBuilder *build)
 	truejump = build->Emit(OP_JMP, 0);
 
 	// Evaluate false expression.
-	build->BackpatchToHere(falsejump);
+	build->BackpatchListToHere(no);
 	if (falsex->isConstant() && falsex->ValueType->GetRegType() == REGT_INT)
 	{
 		build->EmitLoadInt(out.RegNum, static_cast<FxConstant *>(falsex)->GetValue().GetInt());
@@ -8858,68 +8953,28 @@ FxExpression *FxIfStatement::Resolve(FCompileContext &ctx)
 ExpEmit FxIfStatement::Emit(VMFunctionBuilder *build)
 {
 	ExpEmit v;
-	size_t jumpspot;
-	FxExpression *path1, *path2;
-	int condcheck;
+	size_t jumpspot = ~0u;
 
-	// This is pretty much copied from FxConditional, except we don't
-	// keep any results.
-	ExpEmit cond = Condition->Emit(build);
-	assert(cond.RegType != REGT_STRING && !cond.Konst);
+	TArray<size_t> yes, no;
+	Condition->EmitCompare(build, false, yes, no);
 
 	if (WhenTrue != nullptr)
 	{
-		path1 = WhenTrue;
-		path2 = WhenFalse;
-		condcheck = 1;
+		build->BackpatchListToHere(yes);
+		WhenTrue->Emit(build);
+	}
+	if (WhenFalse != nullptr)
+	{
+		if (!WhenTrue->CheckReturn()) jumpspot = build->Emit(OP_JMP, 0);	// no need to emit a jump if the block returns.
+		build->BackpatchListToHere(no);
+		WhenFalse->Emit(build);
+		if (jumpspot != ~0u) build->BackpatchToHere(jumpspot);
+		if (WhenTrue == nullptr) build->BackpatchListToHere(yes);
 	}
 	else
 	{
-		// When there is only a false path, reverse the condition so we can
-		// treat it as a true path.
-		assert(WhenFalse != nullptr);
-		path1 = WhenFalse;
-		path2 = nullptr;
-		condcheck = 0;
+		build->BackpatchListToHere(no);
 	}
-
-	// Test condition.
-
-	switch (cond.RegType)
-	{
-	default:
-	case REGT_INT:
-		build->Emit(OP_EQ_K, condcheck, cond.RegNum, build->GetConstantInt(0));
-		break;
-
-	case REGT_FLOAT:
-		build->Emit(OP_EQF_K, condcheck, cond.RegNum, build->GetConstantFloat(0));
-		break;
-
-	case REGT_POINTER:
-		build->Emit(OP_EQA_K, condcheck, cond.RegNum, build->GetConstantAddress(nullptr, ATAG_GENERIC));
-		break;
-	}
-	jumpspot = build->Emit(OP_JMP, 0);
-	cond.Free(build);
-
-	// Evaluate first path
-	v = path1->Emit(build);
-	v.Free(build);
-	if (path2 != nullptr)
-	{
-		size_t path1jump;
-		
-		// if the branch ends with a return we do not need a terminating jmp.
-		if (!path1->CheckReturn()) path1jump = build->Emit(OP_JMP, 0);
-		else path1jump = 0xffffffff;
-		// Evaluate second path
-		build->BackpatchToHere(jumpspot);
-		v = path2->Emit(build);
-		v.Free(build);
-		jumpspot = path1jump;
-	}
-	if (jumpspot != 0xffffffff) build->BackpatchToHere(jumpspot);
 	return ExpEmit();
 }
 
@@ -9027,19 +9082,17 @@ ExpEmit FxWhileLoop::Emit(VMFunctionBuilder *build)
 	assert(Condition->ValueType == TypeBool);
 
 	size_t loopstart, loopend;
-	size_t jumpspot;
+	TArray<size_t> yes, no;
 
 	// Evaluate the condition and execute/break out of the loop.
 	loopstart = build->GetAddress();
 	if (!Condition->isConstant())
 	{
-		ExpEmit cond = Condition->Emit(build);
-		build->Emit(OP_TEST, cond.RegNum, 0);
-		jumpspot = build->Emit(OP_JMP, 0);
-		cond.Free(build);
+		Condition->EmitCompare(build, false, yes, no);
 	}
 	else assert(static_cast<FxConstant *>(Condition)->GetValue().GetBool() == true);
 
+	build->BackpatchListToHere(yes);
 	// Execute the loop's content.
 	if (Code != nullptr)
 	{
@@ -9049,13 +9102,8 @@ ExpEmit FxWhileLoop::Emit(VMFunctionBuilder *build)
 
 	// Loop back.
 	build->Backpatch(build->Emit(OP_JMP, 0), loopstart);
+	build->BackpatchListToHere(no);
 	loopend = build->GetAddress();
-
-	if (!Condition->isConstant()) 
-	{
-		build->Backpatch(jumpspot, loopend);
-	}
-
 	Backpatch(build, loopstart, loopend);
 	return ExpEmit();
 }
@@ -9132,17 +9180,16 @@ ExpEmit FxDoWhileLoop::Emit(VMFunctionBuilder *build)
 	loopstart = build->GetAddress();
 	if (!Condition->isConstant())
 	{
-		ExpEmit cond = Condition->Emit(build);
-		build->Emit(OP_TEST, cond.RegNum, 1);
-		cond.Free(build);
-		build->Backpatch(build->Emit(OP_JMP, 0), codestart);
+		TArray<size_t> yes, no;
+		Condition->EmitCompare(build, true, yes, no);
+		build->BackpatchList(no, codestart);
+		build->BackpatchListToHere(yes);
 	}
 	else if (static_cast<FxConstant *>(Condition)->GetValue().GetBool() == true)
 	{ // Always looping
 		build->Backpatch(build->Emit(OP_JMP, 0), codestart);
 	}
 	loopend = build->GetAddress();
-
 	Backpatch(build, loopstart, loopend);
 
 	return ExpEmit();
diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h
index c54c8dc62..c5e6f4e68 100644
--- a/src/scripting/codegeneration/codegen.h
+++ b/src/scripting/codegeneration/codegen.h
@@ -328,6 +328,7 @@ public:
 	bool IsObject() const { return ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) && !ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer)) && ValueType != TypeNullPtr && static_cast<PPointer*>(ValueType)->PointedType->IsKindOf(RUNTIME_CLASS(PClass)); }
 
 	virtual ExpEmit Emit(VMFunctionBuilder *build);
+	virtual void EmitCompare(VMFunctionBuilder *build, bool invert, TArray<size_t> &patchspots_yes, TArray<size_t> &patchspots_no);
 
 	FScriptPosition ScriptPosition;
 	PType *ValueType = nullptr;
@@ -565,6 +566,7 @@ public:
 	FxExpression *Resolve(FCompileContext&);
 
 	ExpEmit Emit(VMFunctionBuilder *build);
+	void EmitCompare(VMFunctionBuilder *build, bool invert, TArray<size_t> &patchspots_yes, TArray<size_t> &patchspots_no);
 };
 
 class FxIntCast : public FxExpression
@@ -734,6 +736,7 @@ public:
 	~FxUnaryNotBoolean();
 	FxExpression *Resolve(FCompileContext&);
 	ExpEmit Emit(VMFunctionBuilder *build);
+	void EmitCompare(VMFunctionBuilder *build, bool invert, TArray<size_t> &patchspots_yes, TArray<size_t> &patchspots_no);
 };
 
 //==========================================================================
@@ -934,7 +937,9 @@ public:
 
 	FxCompareRel(int, FxExpression*, FxExpression*);
 	FxExpression *Resolve(FCompileContext&);
+	ExpEmit EmitCommon(VMFunctionBuilder *build, bool forcompare, bool invert);
 	ExpEmit Emit(VMFunctionBuilder *build);
+	void EmitCompare(VMFunctionBuilder *build, bool invert, TArray<size_t> &patchspots_yes, TArray<size_t> &patchspots_no);
 };
 
 //==========================================================================
@@ -949,7 +954,9 @@ public:
 
 	FxCompareEq(int, FxExpression*, FxExpression*);
 	FxExpression *Resolve(FCompileContext&);
+	ExpEmit EmitCommon(VMFunctionBuilder *build, bool forcompare, bool invert);
 	ExpEmit Emit(VMFunctionBuilder *build);
+	void EmitCompare(VMFunctionBuilder *build, bool invert, TArray<size_t> &patchspots_yes, TArray<size_t> &patchspots_no);
 };
 
 //==========================================================================
diff --git a/src/scripting/vm/vmbuilder.cpp b/src/scripting/vm/vmbuilder.cpp
index f358fce7b..9b399ad60 100644
--- a/src/scripting/vm/vmbuilder.cpp
+++ b/src/scripting/vm/vmbuilder.cpp
@@ -734,6 +734,13 @@ void VMFunctionBuilder::Backpatch(size_t loc, size_t target)
 	Code[loc].i24 = offset;
 }
 
+void VMFunctionBuilder::BackpatchList(TArray<size_t> &locs, size_t target)
+{
+	for (auto loc : locs)
+		Backpatch(loc, target);
+}
+
+
 //==========================================================================
 //
 // VMFunctionBuilder :: BackpatchToHere
@@ -748,6 +755,12 @@ void VMFunctionBuilder::BackpatchToHere(size_t loc)
 	Backpatch(loc, Code.Size());
 }
 
+void VMFunctionBuilder::BackpatchListToHere(TArray<size_t> &locs)
+{
+	for (auto loc : locs)
+		Backpatch(loc, Code.Size());
+}
+
 //==========================================================================
 //
 // FFunctionBuildList
diff --git a/src/scripting/vm/vmbuilder.h b/src/scripting/vm/vmbuilder.h
index d53393518..7434f3a21 100644
--- a/src/scripting/vm/vmbuilder.h
+++ b/src/scripting/vm/vmbuilder.h
@@ -69,6 +69,8 @@ public:
 
 	void Backpatch(size_t addr, size_t target);
 	void BackpatchToHere(size_t addr);
+	void BackpatchList(TArray<size_t> &addrs, size_t target);
+	void BackpatchListToHere(TArray<size_t> &addrs);
 
 	// Write out complete constant tables.
 	void FillIntConstants(int *konst);