optimize: convert for-loop post-inc/dec to prefix to avoid zval copy (#79)

* optimize: convert for-loop post-inc/dec to prefix to avoid zval copy

For-loop post-expressions (third clause of `for (init; cond; post)`) always
discard the return value, so $i++ is semantically identical to ++$i in this
context. Converting to prefix increment/decrement avoids the zval copy +
destructor overhead from operator++(int)'s return value, saving ~6ns per
increment per loop iteration.

Changes:
- LoopControlTrait: convert PostInc→PreInc, PostDec→PreDec in for-loop
  post-expressions only (while/do-while body untouched)
- Add phpunit test verifying generated C++ contains ++i/--i for for-loops
  and retains i++ in while/do-while bodies
- Add test data file with 7 test functions covering basic, multi-post,
  nested, mixed, and while/do-while cases

* fix: restrict for-loop post-expr rewrite to simple variables only
master
yuan-dian 2 days ago committed by GitHub
parent cde9ed2b58
commit be5cc71a8b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 97
      phpunit/code/loop/for-loop-post-expr-opt.php
  2. 31
      phpunit/src/LoopControlTest.php
  3. 8
      src/Parser/LoopControlTrait.php

@ -0,0 +1,97 @@
<?php
function test_post_inc_basic(): void {
$sum = 0;
for ($i = 0; $i < 10; $i++) {
$sum += $i;
}
}
function test_post_dec_basic(): void {
$sum = 0;
for ($i = 10; $i > 0; $i--) {
$sum += $i;
}
}
function test_multi_post(): void {
for ($i = 0, $j = 10; $i < 5; $i++, $j--) {
}
}
function test_mixed_post(): void {
$j = 0;
for ($i = 0; $i < 10; $i++, $j += 2) {
}
}
function test_nested_for(): void {
for ($i = 0; $i < 5; $i++) {
for ($j = 0; $j < 5; $j++) {
}
}
}
function test_while_not_affected(): void {
$i = 0;
while ($i < 10) {
$i++;
}
}
function test_do_while_not_affected(): void {
$i = 0;
do {
$i++;
} while ($i < 10);
}
// --- Cases that must NOT be rewritten (property/static-property/array-element) ---
class Counter {
public int $value = 0;
}
class StaticCounter {
public static int $count = 0;
}
function test_property_post_inc_not_rewritten(): void {
$obj = new Counter();
for ($i = 0; $i < 10; $i++) {
$obj->value++;
}
}
function test_property_post_dec_not_rewritten(): void {
$obj = new Counter();
for ($i = 0; $i < 10; $i++) {
$obj->value--;
}
}
function test_static_property_post_inc_not_rewritten(): void {
for ($i = 0; $i < 10; $i++) {
StaticCounter::$count++;
}
}
function test_static_property_post_dec_not_rewritten(): void {
for ($i = 0; $i < 10; $i++) {
StaticCounter::$count--;
}
}
function test_array_element_post_inc_not_rewritten(): void {
$arr = [0, 0, 0];
for ($i = 0; $i < 10; $i++) {
$arr[$i % 3]++;
}
}
function test_array_element_post_dec_not_rewritten(): void {
$arr = [10, 10, 10];
for ($i = 0; $i < 10; $i++) {
$arr[$i % 3]--;
}
}

@ -30,4 +30,35 @@ class LoopControlTest extends \BaseTest
'control-flow/while-body-defined-condition.php',
);
}
public function testForLoopPostIncConvertedToPreInc(): void
{
global $translator;
$compiler = \TypePhp\CompilerTest::create(TYPEPHP_ROOT_PATH);
$translator = $compiler;
$testFile = __DIR__ . '/../code/loop/for-loop-post-expr-opt.php';
$compiler->addFiles([$testFile]);
$compiler->prepareFile($testFile);
$cppFile = $compiler->convertFile($testFile);
$cpp = file_get_contents($cppFile);
// for-loop post-expression $i++ → ++$i, $i-- → --$i
$this->assertStringContainsString('++i', $cpp);
$this->assertStringContainsString('--i', $cpp);
// while/do-while body $i++ should NOT be converted — still i++
$this->assertStringContainsString("\ti++;\n", $cpp);
// property postfix must NOT be rewritten — still postfix
$this->assertMatchesRegularExpression('/\.attr\([^)]+\)[^;]*\+\+/', $cpp);
$this->assertMatchesRegularExpression('/\.attr\([^)]+\)[^;]*--/', $cpp);
// static-property postfix must NOT be rewritten
$this->assertMatchesRegularExpression('/getStaticProperty\([^)]+\)[^;]*\+\+/', $cpp);
$this->assertMatchesRegularExpression('/getStaticProperty\([^)]+\)[^;]*--/', $cpp);
// array-element postfix must NOT be rewritten
$this->assertMatchesRegularExpression('/\.item\([^)]+\)[^;]*\+\+/', $cpp);
$this->assertMatchesRegularExpression('/\.item\([^)]+\)[^;]*--/', $cpp);
}
}

@ -85,6 +85,14 @@ trait LoopControlTrait
$list_loop = [];
foreach ($loop as $expr) {
// for-loop post-expressions discard return value, so $i++ ≡ ++$i.
// Only rewrite simple variables; TypePHP lowers prefix and postfix
// differently for compound lvalues (e.g. static-property write-back).
if ($expr instanceof Node\Expr\PostInc && $this->isVarExpr($expr->var)) {
$expr = new Node\Expr\PreInc($expr->var, $expr->getAttributes());
} elseif ($expr instanceof Node\Expr\PostDec && $this->isVarExpr($expr->var)) {
$expr = new Node\Expr\PreDec($expr->var, $expr->getAttributes());
}
[$loopExpr, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($expr);
$loopExpr = $this->stringifyParsedExpr($loopExpr);
if ($beforeStmts || $afterStmts) {

Loading…
Cancel
Save