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
1,084 changes: 575 additions & 509 deletions docs/bootstrap-inventory.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ext/standard/VmFs.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ final class VmFs
/** @var array<int, \FFI\CData> libc FILE* for VmPopenNative handles (#8250) */
private static array $popenNativeFiles = [];

/** @var array<int, true> gz* stream placeholders — I/O via VmGzStreamNative libz FFI (#8220) */
/** @var array<int, true> gz* stream placeholders — I/O via VmGzStreamPure (#8936, #8220) */
private static array $gzNativePlaceholders = [];

/** @var array<int, int> host stream identity => outstanding VM handle ids (#3384 pfsockopen persistent) */
Expand Down
2 changes: 1 addition & 1 deletion ext/standard/VmGzStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/**
* gzopen/gzwrite/gzread/gzclose — zlib stream resource API (ext/zlib/zlib.c, #6168).
*
* VM via {@see VmGzStreamNative} libz FFI — no host ext/zlib gz* delegation (#8220).
* VM via {@see VmGzStreamPure} buffered I/O + {@see VmZlib} — no libz gzFile FFI (#8936, #8220).
* JIT/AOT via {@see \PHPCompiler\JIT\Builtin\GzStreamIoJit} + {@see JitGzfile} / {@see JitReadgzfile} (#4657 phase 2).
*/
final class VmGzStream
Expand Down
175 changes: 8 additions & 167 deletions ext/standard/VmGzStreamNative.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,198 +5,39 @@
namespace PHPCompiler\ext\standard;

/**
* VM gz* stream I/O via libz FFI — no host ext/zlib gzopen delegation (#6168, #8220).
* VM gz* stream shim — delegates to {@see VmGzStreamPure} (#8936, #6168).
*
* Mirrors {@see \PHPCompiler\JIT\Builtin\GzStreamIoJit} libz externs; php-src ext/zlib/zlib.c.
* libz gzFile FFI removed; compression uses {@see VmZlib} on buffered I/O.
*/
final class VmGzStreamNative
{
/** @var array<int, \FFI\CData> handle id => gzFile */
private static array $gzFiles = [];

private static ?\FFI $ffi = null;

private static bool $ffiUnavailable = false;

public static function available(): bool
{
return null !== self::ffi();
return VmGzStreamPure::available();
}

public static function isNativeHandle(int $handle): bool
{
return isset(self::$gzFiles[$handle]);
}

/** @return \FFI\CData|null gzFile pointer */
public static function lookup(int $handle): ?\FFI\CData
{
return self::$gzFiles[$handle] ?? null;
return VmGzStreamPure::isHandle($handle);
}

public static function gzopen(string $filename, string $mode): int|false
{
$ffi = self::ffi();
if (null === $ffi) {
return false;
}

try {
$fp = $ffi->gzopen($filename, $mode);
if (null === $fp) {
return false;
}
$id = VmFs::adoptGzNativePlaceholder('compress.zlib://'.$filename);
if (false === $id) {
$ffi->gzclose($fp);

return false;
}
self::$gzFiles[$id] = $fp;

return $id;
} catch (\Throwable) {
return false;
}
return VmGzStreamPure::gzopen($filename, $mode);
}

public static function gzwrite(int $handle, string $data, ?int $length = null): int|false
{
$fp = self::$gzFiles[$handle] ?? null;
$ffi = self::ffi();
if (null === $fp || null === $ffi) {
return false;
}
if (null !== $length) {
if ($length < 0) {
return false;
}
if ($length < \strlen($data)) {
$data = \substr($data, 0, $length);
}
}
if ('' === $data) {
return 0;
}

try {
$buf = self::copyBytes($ffi, $data);
$written = (int) $ffi->gzwrite($fp, \FFI::addr($buf[0]), \strlen($data));
if ($written < 0) {
return false;
}

return $written;
} catch (\Throwable) {
return false;
}
return VmGzStreamPure::gzwrite($handle, $data, $length);
}

public static function gzread(int $handle, int $length = 8192): string|false
{
$fp = self::$gzFiles[$handle] ?? null;
$ffi = self::ffi();
if (null === $fp || null === $ffi) {
return false;
}
if ($length < 0) {
return false;
}
if (0 === $length) {
return '';
}

try {
$buf = $ffi->new('unsigned char['.$length.']');
$got = (int) $ffi->gzread($fp, \FFI::addr($buf[0]), $length);
if ($got < 0) {
return false;
}
if (0 === $got) {
return '';
}

return \FFI::string($buf, $got);
} catch (\Throwable) {
return false;
}
return VmGzStreamPure::gzread($handle, $length);
}

public static function gzclose(int $handle): bool
{
$fp = self::$gzFiles[$handle] ?? null;
unset(self::$gzFiles[$handle]);
VmFs::releaseGzNativePlaceholder($handle);
$ffi = self::ffi();
if (null === $fp || null === $ffi) {
return false;
}

try {
return 0 === (int) $ffi->gzclose($fp);
} catch (\Throwable) {
return false;
}
}

private static function copyBytes(\FFI $ffi, string $data): \FFI\CData
{
$len = \strlen($data);
if (0 === $len) {
return $ffi->new('unsigned char[1]');
}
$buf = $ffi->new('unsigned char['.$len.']', false);
\FFI::memcpy($buf, $data, $len);

return $buf;
}

private static function ffiEnabled(): bool
{
$v = getenv('PHP_COMPILER_DISABLE_FFI');
if (false !== $v && '' !== $v && '0' !== $v && 'false' !== strtolower($v)) {
return false;
}

return true;
}

private static function ffi(): ?\FFI
{
if (!self::ffiEnabled()) {
return null;
}
if (self::$ffiUnavailable) {
return null;
}
if (null !== self::$ffi) {
return self::$ffi;
}
if (!\extension_loaded('ffi')) {
self::$ffiUnavailable = true;

return null;
}

$cdef = <<<'CDEF'
typedef void *gzFile;
gzFile gzopen(const char *path, const char *mode);
int gzwrite(gzFile file, void *buf, unsigned len);
int gzread(gzFile file, void *buf, unsigned len);
int gzclose(gzFile file);
CDEF;

foreach (['libz.so.1', 'libz.so'] as $lib) {
try {
self::$ffi = \FFI::cdef($cdef, $lib);

return self::$ffi;
} catch (\Throwable) {
}
}

self::$ffiUnavailable = true;

return null;
return VmGzStreamPure::gzclose($handle);
}
}
Loading