Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion ext/sqlite3/JitSqlite3.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
* zim_SQLite3_querySingle / zim_SQLite3_lastInsertRowID / zim_SQLite3_changes /
* zim_SQLite3_lastErrorCode / zim_SQLite3_lastErrorMsg (#35966) /
* zim_SQLite3_busyTimeout (#35972) / zim_SQLite3_enableExceptions (#35975) /
* zim_SQLite3_escapeString (#35977) / zim_SQLite3_version (#35991 leftover of #35977)
* zim_SQLite3_escapeString (#35977) / zim_SQLite3_version (#35991 leftover of #35977) /
* zim_SQLite3_open (#36001 leftover of #35991)
*/
final class JitSqlite3
{
Expand Down Expand Up @@ -172,6 +173,84 @@ public static function close(Context $context, JITVariable ...$args): Value
return self::boxBool($context, true);
}

/**
* SQLite3::open leftover of version (#36001 / #35991).
* php-src zim_sqlite3_open: throw Exception when already open; reopen after close.
* Thin AOT folds open/reopen onto {@see Sqlite3JitSupport} props (same honesty class as
* __construct — no libsqlite3 FFI at runtime).
*/
public static function open(Context $context, JITVariable ...$args): Value
{
if (!VmClassMethod::requireJitUserArgCountRange($context, $args, 'SQLite3::open', 1, 3)) {
return VmClassMethod::jitArgcDummyReturn($context);
}
$obj = self::readObject($context, $args[0]);
JitStringBuiltinArg::lower(
$context,
$args[1],
'SQLite3::open',
1,
'filename'
);
if (\count($args) >= 3) {
JitLongArg::lower($context, $args[2], 'SQLite3::open(): Argument #2 ($flags)');
}
if (\count($args) >= 4) {
JitStringBuiltinArg::lower(
$context,
$args[3],
'SQLite3::open',
3,
'encryption_key'
);
}

$i64 = $context->getTypeFromString('int64');
$id = self::loadLong($context, $obj, Sqlite3JitSupport::PROP_ID);
$alreadyOpen = $context->builder->icmp(
\PHPLLVM\Builder::INT_NE,
$id,
$i64->constInt(0, false)
);
$bbThrow = BasicBlockHelper::append($context, 'sqlite3_open_already');
$bbOk = BasicBlockHelper::append($context, 'sqlite3_open_ok');
$context->builder->branchIf($alreadyOpen, $bbThrow, $bbOk);

$context->builder->positionAtEnd($bbThrow);
if (null !== \PHPCompiler\JIT\TryCatchHelper::resolveThrowHandler($context)) {
\PHPCompiler\JIT\TryCatchHelper::emitCatchableClassError(
$context,
'Exception',
'Already initialised DB Object'
);
} else {
ExceptionBridge::emitErrorAndAbort($context, 'Already initialised DB Object');
}
$seal = BasicBlockHelper::tryGetInsertBlock($context);
if (null !== $seal && null === $seal->getTerminator()) {
ExceptionBridge::emitErrorAndAbort($context, 'Already initialised DB Object');
}

$context->builder->positionAtEnd($bbOk);
self::storeLong($context, $obj, Sqlite3JitSupport::PROP_ID, $i64->constInt(1, false));
self::storeLong($context, $obj, Sqlite3JitSupport::PROP_ROW, $i64->constInt(0, false));
self::storeLong($context, $obj, Sqlite3JitSupport::PROP_HAS, $i64->constInt(0, false));
self::storeLong($context, $obj, Sqlite3JitSupport::PROP_LAST_ROWID, $i64->constInt(0, false));
self::storeLong($context, $obj, Sqlite3JitSupport::PROP_CHANGES, $i64->constInt(0, false));
self::storeLong($context, $obj, Sqlite3JitSupport::PROP_ROW_COUNT, $i64->constInt(0, false));
self::storeLong($context, $obj, Sqlite3JitSupport::PROP_SUM, $i64->constInt(0, false));
self::storeLong($context, $obj, Sqlite3JitSupport::PROP_INT_PK, $i64->constInt(0, false));
// Keep PROP_EXCEPTIONS across close/reopen (php-src retains exception mode on the object).

$slot = JitValueBox::alloc($context);
$context->builder->call(
$context->lookupFunction('__value__writeNull'),
JitValueBox::pointer($context, $slot)
);

return JitValueBox::pointer($context, $slot);
}

public static function lastInsertRowID(Context $context, JITVariable ...$args): Value
{
if (!VmClassMethod::requireExactJitUserArgCount($context, $args, 'SQLite3::lastInsertRowID', 0)) {
Expand Down
9 changes: 6 additions & 3 deletions lib/JIT/Call/Sqlite3Method.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
/**
* SQLite3 thin-AOT methods — __construct / exec / querySingle / close /
* lastInsertRowID / changes / lastErrorCode / lastErrorMsg / busyTimeout /
* enableExceptions / escapeString / version
* enableExceptions / escapeString / version / open
* (#35931 leftover of #35914; lastError leftover #35966; busyTimeout leftover #35972;
* enableExceptions leftover #35975; escapeString leftover #35977;
* version leftover #35991).
* version leftover #35991; open leftover #36001).
*
* php-src: ext/sqlite3/sqlite3.c
*/
Expand Down Expand Up @@ -55,6 +55,8 @@ public function __construct(
// Static ZEND_METHOD — no implicit $this for SQLite3::version() (#35991).
$this->paramNames = [];
$this->namedArgsReceiverPrefix = 0;
} elseif ('open' === $lc) {
$this->paramNames = ['filename', 'flags=', 'encryption_key='];
}
}

Expand All @@ -78,8 +80,9 @@ public function call(Context $context, Variable ...$args): Value
'enableexceptions' => JitSqlite3::enableExceptions($context, ...$args),
'escapestring' => JitSqlite3::escapeString($context, ...$args),
'version' => JitSqlite3::version($context, ...$args),
'open' => JitSqlite3::open($context, ...$args),
default => throw new \LogicException(
'SQLite3::'.$this->method.'() JIT dispatch missing (#35931 / #35991)'
'SQLite3::'.$this->method.'() JIT dispatch missing (#35931 / #35991 / #36001)'
),
};
}
Expand Down
4 changes: 2 additions & 2 deletions lib/JIT/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -2296,10 +2296,10 @@ private function defineBuiltins(int $loadType): void {
$zipMethod
);
}
// SQLite3 construct/exec/querySingle/lastInsertRowID/changes/lastError*/busyTimeout/enableExceptions/escapeString/version — NestedJIT leftover of advertise-only AOT (#35931 / #35966 / #35972 / #35975 / #35977 / #35991 / #20565).
// SQLite3 construct/exec/querySingle/lastInsertRowID/changes/lastError*/busyTimeout/enableExceptions/escapeString/version/open — NestedJIT leftover of advertise-only AOT (#35931 / #35966 / #35972 / #35975 / #35977 / #35991 / #36001 / #20565).
if (CompilerVersion::supportsSqlite3()) {
$this->type->object->lookup('SQLite3');
foreach (['__construct', 'exec', 'querySingle', 'close', 'lastInsertRowID', 'changes', 'lastErrorCode', 'lastErrorMsg', 'busyTimeout', 'enableExceptions', 'escapeString', 'version'] as $sqliteMethod) {
foreach (['__construct', 'exec', 'querySingle', 'close', 'lastInsertRowID', 'changes', 'lastErrorCode', 'lastErrorMsg', 'busyTimeout', 'enableExceptions', 'escapeString', 'version', 'open'] as $sqliteMethod) {
$this->functionProxies['sqlite3::'.strtolower($sqliteMethod)] = new Call\Sqlite3Method(
$sqliteMethod
);
Expand Down
21 changes: 21 additions & 0 deletions test/repro/sqlite3_open_aot.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
/**
* AOT: SQLite3::open leftover of version (#36001 / #35991).
* php-src: ext/sqlite3/sqlite3.c zim_sqlite3_open — Already initialised DB Object
*/
$db = new SQLite3(':memory:');
try {
$db->open(':memory:');
echo "no-throw\n";
} catch (Throwable $e) {
echo get_class($e), ':', $e->getMessage(), "\n";
}
echo 'close=', var_export($db->close(), true), "\n";
try {
$r = $db->open(':memory:');
echo 'reopen=', var_export($r, true), "\n";
} catch (Throwable $e) {
echo 'reopen-ex:', get_class($e), ':', $e->getMessage(), "\n";
}
$db->exec('CREATE TABLE t(x); INSERT INTO t VALUES (7);');
echo 'q=', var_export($db->querySingle('SELECT x FROM t'), true), "\n";
94 changes: 94 additions & 0 deletions test/unit/Sqlite3OpenAotTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

declare(strict_types=1);

namespace PHPCompiler;

use PHPUnit\Framework\TestCase;

/**
* AOT: SQLite3::open NestedJIT (#36001 leftover of #35991).
*
* @see php-src ext/sqlite3/sqlite3.c zim_sqlite3_open
*
* @group llvm
* @group aot
*/
final class Sqlite3OpenAotTest extends TestCase
{
public function testOpenAotMatchesVm(): void
{
if (!LlvmToolchain::hasLibrary(dirname(__DIR__, 2))) {
$this->markTestSkipped('LLVM 9 toolchain not available');
}

$src = __DIR__.'/../repro/sqlite3_open_aot.php';
$this->assertSame($this->runVm($src), $this->runAot($src));
}

public function testProxyRegisteredForOpen(): void
{
$root = dirname(__DIR__, 2);
$src = (string) file_get_contents($root.'/lib/JIT/Context.php');
$this->assertStringContainsString("'version', 'open'", $src);
$dispatch = (string) file_get_contents($root.'/lib/JIT/Call/Sqlite3Method.php');
$this->assertStringContainsString("'open'", $dispatch);
$jit = (string) file_get_contents($root.'/ext/sqlite3/JitSqlite3.php');
$this->assertStringContainsString('function open(', $jit);
$this->assertStringContainsString('#36001', $jit);
$this->assertFileDoesNotExist($root.'/lib/AOT/runtime/sqlite3_open.c');
$this->assertFileDoesNotExist($root.'/runtime/sqlite3_open.c');
}

private function runVm(string $src): string
{
return $this->runEnv(['PHP_COMPILER_PROFILE=8.4'], 'bin/vm.php', $src);
}

private function runAot(string $src): string
{
$root = dirname(__DIR__, 2);
$bin = sys_get_temp_dir().'/sq3_open_'.getmypid().'_'.md5($src);
$compile = 'env PHP_COMPILER_PROFILE=8.4 PHP_COMPILER_HELPER_RUNTIME_O=0 '
.escapeshellarg(PHP_BINARY).' '
.escapeshellarg($root.'/bin/compile.php').' -o '
.escapeshellarg($bin).' '
.escapeshellarg($src);
$cwd = getcwd();
chdir($root);
try {
exec($compile.' 2>&1', $cout, $crc);
$this->assertSame(0, $crc, implode("\n", $cout));
$this->assertFileExists($bin);
exec(escapeshellarg($bin).' 2>&1', $out, $rc);
$this->assertSame(0, $rc, implode("\n", $out));

return implode("\n", $out);
} finally {
@unlink($bin);
chdir($cwd);
}
}

/**
* @param list<string> $env
*/
private function runEnv(array $env, string $binRel, string $src): string
{
$root = dirname(__DIR__, 2);
$cmd = 'env '.implode(' ', $env).' '
.escapeshellarg(PHP_BINARY).' '
.escapeshellarg($root.'/'.$binRel).' '
.escapeshellarg($src);
$cwd = getcwd();
chdir($root);
try {
exec($cmd.' 2>&1', $out, $rc);
$this->assertSame(0, $rc, implode("\n", $out));

return implode("\n", $out);
} finally {
chdir($cwd);
}
}
}
Loading