Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
3cfcca5
f$deflate_init
Shamzik Dec 16, 2025
a276c47
f$deflate_add & tests
Shamzik Dec 16, 2025
92ed7c4
format
Shamzik Dec 16, 2025
0034e3b
noexcept
Shamzik Dec 18, 2025
9ec93d8
std::addressof
Shamzik Dec 18, 2025
d94ed89
brace init
Shamzik Dec 18, 2025
53d5b08
rewrite key comparing
Shamzik Dec 18, 2025
7452dd1
small fixes
Shamzik Dec 18, 2025
9df0e83
constexpr
Shamzik Dec 18, 2025
a184c56
move statement to if
Shamzik Dec 18, 2025
35588e6
fix message
Shamzik Dec 19, 2025
f9e1446
fixes
Shamzik Dec 19, 2025
493d261
2025
Shamzik Dec 19, 2025
252a1e8
do not check `zlib_dynamic_free` arg
Shamzik Dec 19, 2025
c31bb20
move value declaration
Shamzik Dec 19, 2025
81c5ae7
constants
Shamzik Dec 19, 2025
db995c8
alloc -> calloc
Shamzik Jan 12, 2026
487b127
format
Shamzik Jan 12, 2026
4083e72
a number
Shamzik Jan 12, 2026
b84e282
default values of options
Shamzik Jan 12, 2026
91f5b7f
#include "zlib/zlib.h"
Shamzik Jan 12, 2026
64bb8a9
swap two lines
Shamzik Jan 12, 2026
283b98f
small refactoring
Shamzik Jan 12, 2026
cf25e79
using make_instance
Shamzik Jan 12, 2026
d4e8751
yet another refactoring
Shamzik Jan 12, 2026
5f3208b
do not use `zError`
Shamzik Jan 12, 2026
bd66d0f
hide `destroy` invocation
Shamzik Jan 12, 2026
164e15c
include <memory>
Shamzik Jan 12, 2026
c1a1f49
remove zlib_format
Shamzik Jan 12, 2026
89c14b9
move "using namespace std::literals" into for loop
Shamzik Jan 12, 2026
27a71d1
const auto& val
Shamzik Jan 12, 2026
6c414ec
constants
Shamzik Jan 13, 2026
702ec51
early return
Shamzik Jan 13, 2026
bc4ee2d
static local constants
Shamzik Jan 13, 2026
d77b5f3
switch-case scopes
Shamzik Jan 13, 2026
9bcabd6
do not call `deflateEnd` if `deflateInit2` returned error
Shamzik Jan 14, 2026
5d7f0a1
to_zlib_window_bits -> lambda
Shamzik Jan 14, 2026
42a3d34
move md5_file from zlib-functions.txt into file-functions.txt
Shamzik Jan 14, 2026
4f05a77
make init method
Shamzik Jan 14, 2026
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
3 changes: 3 additions & 0 deletions builtin-functions/kphp-light/stdlib/file-functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,6 @@ function stream_context_create ($options ::: mixed = array()) ::: mixed;
function fprintf ($stream, $format ::: string, ...$args) ::: int;
/** @kphp-extern-func-info stub */
function fputcsv ($stream, $fields ::: array, $delimiter = ",", $enclosure = "\"", $escape = "\\") ::: int | false;

/** @kphp-extern-func-info stub generation-required */
function md5_file ($s ::: string, $raw_output ::: bool = false) ::: string | false;
42 changes: 16 additions & 26 deletions builtin-functions/kphp-light/stdlib/zlib-functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,6 @@ define('ZLIB_ENCODING_RAW', -0x0f);
define('ZLIB_ENCODING_DEFLATE', 0x0f);
define('ZLIB_ENCODING_GZIP', 0x1f);

function gzcompress ($str ::: string, $level ::: int = -1): string;

function gzuncompress ($str ::: string): string;

function gzencode ($str ::: string, $level ::: int = -1) ::: string;

function gzdecode ($str ::: string) ::: string;

function gzdeflate ($str ::: string, $level ::: int = -1) ::: string;

function gzinflate ($str ::: string) ::: string;

// ===== UNSUPPORTED =====

/** @kphp-extern-func-info stub generation-required */
function md5_file ($s ::: string, $raw_output ::: bool = false) ::: string | false;

define('ZLIB_NO_FLUSH', 0);
define('ZLIB_PARTIAL_FLUSH', 1);
define('ZLIB_SYNC_FLUSH', 2);
Expand All @@ -37,15 +20,22 @@ define('ZLIB_RLE', 3);
define('ZLIB_FIXED', 4);
define('ZLIB_DEFAULT_STRATEGY', 0);

/** @kphp-generate-stub-class */
final class DeflateContext {
/** @kphp-extern-func-info stub generation-required */
private function __construct() ::: DeflateContext;
}
function gzcompress ($str ::: string, $level ::: int = -1): string;

function gzuncompress ($str ::: string): string;

function gzencode ($str ::: string, $level ::: int = -1) ::: string;

function gzdecode ($str ::: string) ::: string;

function gzdeflate ($str ::: string, $level ::: int = -1) ::: string;

function gzinflate ($str ::: string) ::: string;

function deflate_init(int $encoding, array $options = []) ::: ?DeflateContext;

// todo: deflate_init php signature has type array instead mixed
/** @kphp-extern-func-info stub generation-required */
function deflate_init(int $encoding, mixed $options = []) ::: ?DeflateContext;
/** @kphp-extern-func-info stub generation-required */
function deflate_add(DeflateContext $context, string $data, int $flush_mode = ZLIB_SYNC_FLUSH) ::: string | false;

final class DeflateContext {
private function __construct() ::: DeflateContext;
}
63 changes: 63 additions & 0 deletions runtime-light/stdlib/zlib/deflate-context.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Compiler for PHP (aka KPHP)
// Copyright (c) 2025 LLC «V Kontakte»
// Distributed under the GPL v3 License, see LICENSE.notice.txt

#pragma once

#include <cstdint>
#include <memory>
#include <optional>

#include "zlib/zlib.h"

#include "runtime-common/core/allocator/script-malloc-interface.h"
#include "runtime-common/core/class-instance/refcountable-php-classes.h"
#include "runtime-common/stdlib/visitors/dummy-visitor-methods.h"
#include "runtime-light/stdlib/diagnostics/logs.h"

class C$DeflateContext : public refcountable_php_classes<C$DeflateContext>, private DummyVisitorMethods {
std::optional<z_stream> m_stream;

static voidpf dynamic_calloc([[maybe_unused]] voidpf opaque, uInt items, uInt size) noexcept {
auto* mem{kphp::memory::script::calloc(items, size)};
if (mem == nullptr) [[unlikely]] {
kphp::log::warning("zlib dynamic calloc: can't allocate {} bytes", items * size);
}
return mem;
}

static void dynamic_free([[maybe_unused]] voidpf opaque, voidpf address) noexcept {
kphp::memory::script::free(address);
}

public:
using DummyVisitorMethods::accept;
C$DeflateContext() noexcept = default;

C$DeflateContext(const C$DeflateContext&) = delete;
C$DeflateContext(C$DeflateContext&&) = delete;
C$DeflateContext& operator=(const C$DeflateContext&) = delete;
C$DeflateContext& operator=(C$DeflateContext&&) = delete;

bool init(int32_t level, int32_t window_bits, int32_t memory, int32_t strategy) noexcept {
z_stream* stream{std::addressof(m_stream.emplace(z_stream{.zalloc = dynamic_calloc, .zfree = dynamic_free, .opaque = nullptr}))};

if (auto err{deflateInit2(stream, level, Z_DEFLATED, window_bits, memory, strategy)}; err != Z_OK) {
kphp::log::warning("zlib error {}", err);
return false;
}

return true;
}

z_stream& operator*() noexcept {
kphp::log::assertion(m_stream.has_value());
return *m_stream;
}

~C$DeflateContext() {
if (m_stream.has_value()) [[likely]] {
deflateEnd(std::addressof(*m_stream));
}
}
};
149 changes: 145 additions & 4 deletions runtime-light/stdlib/zlib/zlib-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ std::optional<string> encode(std::span<const char> data, int64_t level, int64_t
zstrm.avail_out = out_size_upper_bound;
zstrm.next_out = reinterpret_cast<Bytef*>(runtime_ctx.static_SB.buffer());

const auto deflate_res{deflate(std::addressof(zstrm), Z_FINISH)};
if (deflate_res != Z_STREAM_END) [[unlikely]] {
if (const auto deflate_res{deflate(std::addressof(zstrm), Z_FINISH)}; deflate_res != Z_STREAM_END) [[unlikely]] {
kphp::log::warning("can't encode data of length {} due to zlib error {}", data.size(), deflate_res);
return {};
}
Expand Down Expand Up @@ -104,9 +103,8 @@ std::optional<string> decode(std::span<const char> data, int64_t encoding) noexc
runtime_ctx.static_SB.clean().reserve(StringInstanceState::STATIC_BUFFER_LENGTH);
zstrm.avail_out = StringInstanceState::STATIC_BUFFER_LENGTH;
zstrm.next_out = reinterpret_cast<Bytef*>(runtime_ctx.static_SB.buffer());
const auto inflate_res{inflate(std::addressof(zstrm), Z_NO_FLUSH)};

if (inflate_res != Z_STREAM_END) [[unlikely]] {
if (const auto inflate_res{inflate(std::addressof(zstrm), Z_NO_FLUSH)}; inflate_res != Z_STREAM_END) [[unlikely]] {
kphp::log::warning("can't decode data of length {} due to zlib error {}", data.size(), inflate_res);
return {};
}
Expand All @@ -115,3 +113,146 @@ std::optional<string> decode(std::span<const char> data, int64_t encoding) noexc
}

} // namespace kphp::zlib

class_instance<C$DeflateContext> f$deflate_init(int64_t encoding, const array<mixed>& options) noexcept {
static constexpr int32_t DEFAULT_MEMORY{8};
static constexpr int32_t DEFAULT_WINDOW{15};

if (encoding != kphp::zlib::ENCODING_RAW && encoding != kphp::zlib::ENCODING_DEFLATE && encoding != kphp::zlib::ENCODING_GZIP) {
kphp::log::warning("encoding should be one of ZLIB_ENCODING_RAW, ZLIB_ENCODING_DEFLATE, ZLIB_ENCODING_GZIP");
return {};
}

int32_t level{Z_DEFAULT_COMPRESSION};
int32_t memory{DEFAULT_MEMORY};
int32_t window{DEFAULT_WINDOW};
int32_t strategy{Z_DEFAULT_STRATEGY};

for (const auto& it : options) {
using namespace std::literals;

if (!it.is_string_key()) {
kphp::log::warning("unsupported option");
return {};
}

const auto& key{it.get_string_key()};
const std::string_view key_view{key.c_str(), key.size()};
const auto& val{it.get_value()};

if (key_view == "level"sv) {
if (!val.is_int() || val.as_int() < kphp::zlib::MIN_COMPRESSION_LEVEL || val.as_int() > kphp::zlib::MAX_COMPRESSION_LEVEL) {
kphp::log::warning("option level should be a number between -1..9");
return {};
}
level = val.as_int();
} else if (key_view == "memory"sv) {
if (!val.is_int() || val.as_int() < kphp::zlib::MIN_MEMORY_LEVEL || val.as_int() > kphp::zlib::MAX_MEMORY_LEVEL) {
kphp::log::warning("option memory should be a number between 1..9");
return {};
}
memory = val.as_int();
} else if (key_view == "window"sv) {
if (!val.is_int() || val.as_int() < kphp::zlib::MIN_WINDOW_SIZE || val.as_int() > kphp::zlib::MAX_WINDOW_SIZE) {
kphp::log::warning("option window should be a number between 8..15");
return {};
}
window = val.as_int();
} else if (key_view == "strategy"sv) {
if (!val.is_int()) {
kphp::log::warning("option strategy should be one of ZLIB_FILTERED, ZLIB_HUFFMAN_ONLY, ZLIB_RLE, ZLIB_FIXED or ZLIB_DEFAULT_STRATEGY");
return {};
}
const auto s{val.as_int()};
if (s != Z_DEFAULT_STRATEGY && s != Z_FILTERED && s != Z_HUFFMAN_ONLY && s != Z_RLE && s != Z_FIXED) {
kphp::log::warning("option strategy should be one of ZLIB_FILTERED, ZLIB_HUFFMAN_ONLY, ZLIB_RLE, ZLIB_FIXED or ZLIB_DEFAULT_STRATEGY");
return {};
}
strategy = s;
} else if (key_view == "dictionary"sv) {
kphp::log::warning("option dictionary isn't supported yet");
return {};
} else {
kphp::log::warning("unknown option name \"{}\"", key.c_str());
return {};
}
}

const int32_t window_bits{[encoding, window] noexcept {
switch (encoding) {
case kphp::zlib::ENCODING_GZIP:
return 16 + window;
case kphp::zlib::ENCODING_RAW:
return -window;
case kphp::zlib::ENCODING_DEFLATE:
return window;
default:
kphp::log::error("deflate_init: unknown encoding {}", encoding);
}
}()};

auto context{make_instance<C$DeflateContext>()};

if (!context->init(level, window_bits, memory, strategy)) {
return {};
}
return context;
}

Optional<string> f$deflate_add(const class_instance<C$DeflateContext>& context, const string& data, int64_t flush_type) noexcept {
static constexpr uint64_t EXTRA_OUT_SIZE{30};
static constexpr uint64_t MIN_OUT_SIZE{64};

switch (flush_type) {
case Z_BLOCK:
case Z_NO_FLUSH:
case Z_PARTIAL_FLUSH:
case Z_SYNC_FLUSH:
case Z_FULL_FLUSH:
case Z_FINISH:
break;
default:
kphp::log::warning("flush type should be one of ZLIB_NO_FLUSH, ZLIB_PARTIAL_FLUSH, ZLIB_SYNC_FLUSH, ZLIB_FULL_FLUSH, ZLIB_FINISH, ZLIB_BLOCK, ZLIB_TREES");
return {};
}

z_stream* stream{std::addressof(**context.get())};
auto out_size{deflateBound(stream, data.size()) + EXTRA_OUT_SIZE};
out_size = out_size < MIN_OUT_SIZE ? MIN_OUT_SIZE : out_size;
string out{static_cast<string::size_type>(out_size), false};
stream->next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(data.c_str()));
stream->next_out = reinterpret_cast<Bytef*>(out.buffer());
stream->avail_in = data.size();
stream->avail_out = out_size;

auto status{Z_OK};
uint64_t buffer_used{};
do {
if (stream->avail_out == 0) {
out.reserve_at_least(out_size + MIN_OUT_SIZE);
out_size += MIN_OUT_SIZE;
stream->avail_out = MIN_OUT_SIZE;
stream->next_out = reinterpret_cast<Bytef*>(std::next(out.buffer(), buffer_used));
}
status = deflate(stream, flush_type);
buffer_used = out_size - stream->avail_out;
} while (status == Z_OK && stream->avail_out == 0);

switch (status) {
case Z_OK: {
auto len{std::distance(reinterpret_cast<Bytef*>(out.buffer()), stream->next_out)};
out.shrink(len);
return out;
}
case Z_STREAM_END: {
auto len{std::distance(reinterpret_cast<Bytef*>(out.buffer()), stream->next_out)};
deflateReset(stream);
out.shrink(len);
return out;
}
default: {
kphp::log::warning("zlib error {}", status);
return {};
}
}
}
25 changes: 19 additions & 6 deletions runtime-light/stdlib/zlib/zlib-functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,26 @@
#include <optional>
#include <span>

#include "zlib/zlib.h"

#include "runtime-common/core/runtime-core.h"
#include "runtime-light/stdlib/zlib/deflate-context.h"

namespace kphp::zlib {

inline constexpr int64_t ENCODING_RAW = -0x0f;
inline constexpr int64_t ENCODING_DEFLATE = 0x0f;
inline constexpr int64_t ENCODING_GZIP = 0x1f;
inline constexpr int64_t ENCODING_RAW{-0x0f};
inline constexpr int64_t ENCODING_DEFLATE{0x0f};
inline constexpr int64_t ENCODING_GZIP{0x1f};

inline constexpr int64_t MIN_COMPRESSION_LEVEL{-1};
inline constexpr int64_t MAX_COMPRESSION_LEVEL{9};
inline constexpr int64_t DEFAULT_COMPRESSION_LEVEL{6};

inline constexpr int64_t MIN_MEMORY_LEVEL{1};
inline constexpr int64_t MAX_MEMORY_LEVEL{9};

inline constexpr int64_t MIN_COMPRESSION_LEVEL = -1;
inline constexpr int64_t MAX_COMPRESSION_LEVEL = 9;
inline constexpr int64_t DEFAULT_COMPRESSION_LEVEL = 6;
inline constexpr int64_t MIN_WINDOW_SIZE{8};
inline constexpr int64_t MAX_WINDOW_SIZE{15};

std::optional<string> encode(std::span<const char> data, int64_t level, int64_t encoding) noexcept;

Expand Down Expand Up @@ -53,3 +62,7 @@ inline string f$gzdeflate(const string& data, int64_t level = kphp::zlib::MIN_CO
inline string f$gzinflate(const string& data) noexcept {
return kphp::zlib::decode({data.c_str(), static_cast<size_t>(data.size())}, kphp::zlib::ENCODING_RAW).value_or(string{});
}

class_instance<C$DeflateContext> f$deflate_init(int64_t encoding, const array<mixed>& options = {}) noexcept;

Optional<string> f$deflate_add(const class_instance<C$DeflateContext>& context, const string& data, int64_t flush_type = Z_SYNC_FLUSH) noexcept;
2 changes: 1 addition & 1 deletion tests/phpt/pk/016_gzip.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@ok k2_skip
@ok
<?php

$x = "";
Expand Down
Loading