-
Notifications
You must be signed in to change notification settings - Fork 111
[k2] implement zip functions in light runtime #1486
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
3cfcca5
a276c47
92ed7c4
0034e3b
9ec93d8
d94ed89
53d5b08
7452dd1
9df0e83
a184c56
35588e6
f9e1446
493d261
252a1e8
c31bb20
81c5ae7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // Compiler for PHP (aka KPHP) | ||
| // Copyright (c) 2025 LLC «V Kontakte» | ||
| // Distributed under the GPL v3 License, see LICENSE.notice.txt | ||
|
|
||
| #pragma once | ||
|
|
||
| #include "zlib/zlib.h" | ||
|
|
||
| #include "runtime-common/core/class-instance/refcountable-php-classes.h" | ||
| #include "runtime-common/stdlib/visitors/dummy-visitor-methods.h" | ||
|
|
||
| struct C$DeflateContext : public refcountable_php_classes<C$DeflateContext>, private DummyVisitorMethods { | ||
apolyakov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| C$DeflateContext() noexcept = default; | ||
| using DummyVisitorMethods::accept; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I'd swap these two lines |
||
|
|
||
| C$DeflateContext(const C$DeflateContext&) = delete; | ||
| C$DeflateContext(C$DeflateContext&&) = delete; | ||
| C$DeflateContext& operator=(const C$DeflateContext&) = delete; | ||
| C$DeflateContext& operator=(C$DeflateContext&&) = delete; | ||
|
|
||
| ~C$DeflateContext() { | ||
| deflateEnd(std::addressof(stream)); | ||
| } | ||
|
|
||
| z_stream stream{}; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |
| #include "zlib/zlib.h" | ||
|
|
||
| #include "common/containers/final_action.h" | ||
| #include "runtime-common/core/allocator/script-malloc-interface.h" | ||
| #include "runtime-common/core/runtime-core.h" | ||
| #include "runtime-light/stdlib/diagnostics/logs.h" | ||
| #include "runtime-light/stdlib/string/string-state.h" | ||
|
|
@@ -36,6 +37,18 @@ voidpf zlib_static_alloc(voidpf opaque, uInt items, uInt size) noexcept { | |
|
|
||
| void zlib_static_free([[maybe_unused]] voidpf opaque, [[maybe_unused]] voidpf address) noexcept {} | ||
|
|
||
| voidpf zlib_dynamic_alloc([[maybe_unused]] voidpf opaque, uInt items, uInt size) noexcept { | ||
| auto* mem{kphp::memory::script::calloc(items, size)}; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please explain the reason you chose
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because old KPHP runtime does
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right. Let's then rename it to |
||
| if (mem == nullptr) [[unlikely]] { | ||
| kphp::log::warning("zlib dynamic alloc: can't allocate {} bytes", items * size); | ||
| } | ||
| return mem; | ||
| } | ||
|
|
||
| void zlib_dynamic_free([[maybe_unused]] voidpf opaque, voidpf address) noexcept { | ||
| kphp::memory::script::free(address); | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| namespace kphp::zlib { | ||
|
|
@@ -73,8 +86,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 {}; | ||
| } | ||
|
|
@@ -104,9 +116,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 {}; | ||
| } | ||
|
|
@@ -115,3 +126,152 @@ 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 { | ||
apolyakov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| int32_t level{-1}; | ||
| int32_t memory{8}; | ||
| int32_t window{15}; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we not have such magic constants? |
||
| auto strategy{Z_DEFAULT_STRATEGY}; | ||
| constexpr auto extract_int_option{[](int32_t lbound, int32_t ubound, const array_iterator<const mixed>& option, int32_t& dst) noexcept { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to see this refactored as now it's hard to undersand |
||
| if (const mixed & value{option.get_value()}; value.is_int() && value.as_int() >= lbound && value.as_int() <= ubound) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: something is wrong with your formatter as it should be |
||
| dst = value.as_int(); | ||
| return true; | ||
| } else { | ||
| kphp::log::warning("option {} should be number between {}..{}", option.get_string_key().c_str(), lbound, ubound); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
| return false; | ||
| } | ||
| }}; | ||
|
|
||
| switch (encoding) { | ||
| case kphp::zlib::ENCODING_RAW: | ||
| case kphp::zlib::ENCODING_DEFLATE: | ||
| case kphp::zlib::ENCODING_GZIP: | ||
| break; | ||
| default: | ||
| kphp::log::warning("encoding should be one of ZLIB_ENCODING_RAW, ZLIB_ENCODING_DEFLATE, ZLIB_ENCODING_GZIP"); | ||
| return {}; | ||
| } | ||
|
|
||
| for (const auto& option : options) { | ||
| if (!option.is_string_key()) { | ||
| kphp::log::warning("unsupported option"); | ||
| return {}; | ||
| } | ||
|
|
||
| const auto& key{option.get_string_key()}; | ||
| if (std::string_view{key.c_str(), key.size()} == "level") { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can have |
||
| if (!extract_int_option(-1, 9, option, level)) { | ||
| return {}; | ||
| } | ||
| } else if (std::string_view{key.c_str(), key.size()} == "memory") { | ||
| if (!extract_int_option(1, 9, option, memory)) { | ||
| return {}; | ||
| } | ||
| } else if (std::string_view{key.c_str(), key.size()} == "window") { | ||
| if (!extract_int_option(8, 15, option, window)) { | ||
| return {}; | ||
| } | ||
| } else if (std::string_view{key.c_str(), key.size()} == "strategy") { | ||
| if (mixed value{option.get_value()}; value.is_int()) { | ||
| switch (value.as_int()) { | ||
| case Z_FILTERED: | ||
| case Z_HUFFMAN_ONLY: | ||
| case Z_RLE: | ||
| case Z_FIXED: | ||
| case Z_DEFAULT_STRATEGY: | ||
| strategy = value.as_int(); | ||
| break; | ||
| default: | ||
| kphp::log::warning("option strategy should be one of ZLIB_FILTERED, ZLIB_HUFFMAN_ONLY, ZLIB_RLE, ZLIB_FIXED or ZLIB_DEFAULT_STRATEGY"); | ||
| return {}; | ||
| } | ||
| } else { | ||
| kphp::log::warning("option strategy should be one of ZLIB_FILTERED, ZLIB_HUFFMAN_ONLY, ZLIB_RLE, ZLIB_FIXED or ZLIB_DEFAULT_STRATEGY"); | ||
| return {}; | ||
| } | ||
| } else if (std::string_view{key.c_str(), key.size()} == "dictionary") { | ||
| kphp::log::warning("option dictionary isn't supported yet"); | ||
| return {}; | ||
| } else { | ||
| kphp::log::warning("unknown option name \"{}\"", option.get_string_key().c_str()); | ||
| return {}; | ||
| } | ||
| } | ||
|
|
||
| class_instance<C$DeflateContext> context; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: there is a nice |
||
| context.alloc(); | ||
|
|
||
| z_stream* stream{std::addressof(context.get()->stream)}; | ||
| stream->zalloc = zlib_dynamic_alloc; | ||
| stream->zfree = zlib_dynamic_free; | ||
| stream->opaque = nullptr; | ||
|
|
||
| if (encoding < 0) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's get rid of all the magic constants |
||
| encoding += 15 - window; | ||
| } else { | ||
| encoding -= 15 - window; | ||
| } | ||
|
|
||
| if (auto err{deflateInit2(stream, level, Z_DEFLATED, encoding, memory, strategy)}; err != Z_OK) { | ||
| kphp::log::warning("zlib error {}", zError(err)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a common pattern here to just print the error number |
||
| context.destroy(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't it enough to just wait for |
||
| return {}; | ||
| } | ||
| return context; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like that we allocate a |
||
| } | ||
|
|
||
| Optional<string> f$deflate_add(const class_instance<C$DeflateContext>& context, const string& data, int64_t flush_type) noexcept { | ||
| constexpr uint64_t EXTRA_OUT_SIZE{30}; | ||
| 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()->stream)}; | ||
| 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); | ||
|
|
||
| std::ptrdiff_t len{}; | ||
| switch (status) { | ||
| case Z_OK: | ||
| len = std::distance(reinterpret_cast<Bytef*>(out.buffer()), stream->next_out); | ||
| out.shrink(len); | ||
| return out; | ||
| case Z_STREAM_END: | ||
| len = std::distance(reinterpret_cast<Bytef*>(out.buffer()), stream->next_out); | ||
| deflateReset(stream); | ||
| out.shrink(len); | ||
| return out; | ||
| default: | ||
| kphp::log::warning("zlib error {}", zError(status)); | ||
| return {}; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| #include <span> | ||
|
|
||
| #include "runtime-common/core/runtime-core.h" | ||
| #include "runtime-light/stdlib/zlib/deflate-context.h" | ||
|
|
||
| namespace kphp::zlib { | ||
|
|
||
|
|
@@ -53,3 +54,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; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing include for zlib here |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| @ok k2_skip | ||
| @ok | ||
| <?php | ||
|
|
||
| $x = ""; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.