Skip to content

Commit

Permalink
[k2] move json-encoder builtins into runtime-common (#1169)
Browse files Browse the repository at this point in the history
  • Loading branch information
irlgirl authored Dec 4, 2024
1 parent c4bbc7a commit dda3a5a
Show file tree
Hide file tree
Showing 83 changed files with 387 additions and 1,117 deletions.
3 changes: 3 additions & 0 deletions builtin-functions/kphp-light/array.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ function explode ($delimiter ::: string, $str ::: string, $limit ::: int = PHP_I

/** @kphp-extern-func-info interruptible */
function array_map (callable(^2[*] $x):any $callback, $a ::: array) ::: ^1() [];

function to_array_debug(any $instance, bool $with_class_names = false) ::: mixed[];
function instance_to_array(object $instance, $with_class_names ::: bool = false) ::: mixed[];
7 changes: 1 addition & 6 deletions builtin-functions/kphp-light/functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require_once __DIR__ . '/file.txt';
require_once __DIR__ . '/hash.txt';
require_once __DIR__ . '/job-workers.txt';
require_once __DIR__ . '/rpc.txt';
require_once __DIR__ . '/serialize.txt';
require_once __DIR__ . '/string.txt';
require_once __DIR__ . '/server.txt';
require_once __DIR__ . '/kphp-toggles.txt';
Expand Down Expand Up @@ -143,12 +144,6 @@ function component_server_fetch_request($query ::: ComponentQuery) ::: string;
/** @kphp-extern-func-info interruptible */
function component_server_send_response($query ::: ComponentQuery, $message ::: string) ::: void;

// === Json =======================================================================================

function json_encode ($v ::: mixed, $options ::: int = 0) ::: string | false;

function json_decode ($v ::: string, $assoc ::: bool = false) ::: mixed;

// === Misc =======================================================================================

/** @kphp-extern-func-info cpp_template_call */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,14 @@
<?php

/** @kphp-extern-func-info generate-stub */
function serialize($v ::: mixed) ::: string;
/**
* @kphp-extern-func-info generate-stub
* @kphp-pure-function
*/
function unserialize ($v ::: string) ::: mixed;

/** @kphp-extern-func-info generate-stub */
function msgpack_serialize($v ::: mixed) ::: string | null;
/** @kphp-extern-func-info generate-stub */
function msgpack_deserialize($v ::: string) ::: mixed;
/** @kphp-extern-func-info can_throw generate-stub */
function msgpack_serialize_safe($v ::: mixed) ::: string;
/** @kphp-extern-func-info can_throw generate-stub */
function msgpack_deserialize_safe($v ::: string) ::: mixed;

function instance_serialize(object $instance) ::: string | null;
/** @kphp-extern-func-info can_throw */
function instance_serialize_safe(object $instance) ::: string;
/** @kphp-extern-func-info cpp_template_call */
function instance_deserialize($serialized ::: string, $to_type ::: string) ::: instance<^2>;
/** @kphp-extern-func-info cpp_template_call can_throw */
function instance_deserialize_safe($serialized ::: string, $to_type ::: string) ::: instance<^2>;
function json_encode ($v ::: mixed, $options ::: int = 0) ::: string | false;
function json_decode ($v ::: string, $assoc ::: bool = false) ::: mixed;

define('JSON_UNESCAPED_UNICODE', 1);
define('JSON_FORCE_OBJECT', 16);
define('JSON_PRETTY_PRINT', 128); // TODO: add actual support
define('JSON_PARTIAL_OUTPUT_ON_ERROR', 512);
define('JSON_PRESERVE_ZERO_FRACTION', 1024);

/** @kphp-generate-stub-class */
class JsonEncoder {
const rename_policy = 'none';
const visibility_policy = 'all';
Expand All @@ -42,7 +19,6 @@ class JsonEncoder {

public static function encode(object $instance, int $flags = 0, array $more = []) : string;
public static function decode(string $json, string $class_name) : instance<^2>;
/** @kphp-extern-func-info generate-stub */
public static function getLastError() : string;

// JsonEncoderOrChild::encode(...) is actually replaced by JsonEncoder::to_json_impl('JsonEncoderOrChild', ...)
Expand All @@ -53,3 +29,30 @@ class JsonEncoder {
static function from_json_impl(string $encoder_tag, string $json, string $class_name) ::: instance<^3>;
}

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

/** @kphp-extern-func-info generate-stub */
function serialize($v ::: mixed) ::: string;
/**
* @kphp-extern-func-info generate-stub
* @kphp-pure-function
*/
function unserialize ($v ::: string) ::: mixed;

/** @kphp-extern-func-info generate-stub */
function msgpack_serialize($v ::: mixed) ::: string | null;
/** @kphp-extern-func-info generate-stub */
function msgpack_deserialize($v ::: string) ::: mixed;
/** @kphp-extern-func-info can_throw generate-stub */
function msgpack_serialize_safe($v ::: mixed) ::: string;
/** @kphp-extern-func-info can_throw generate-stub */
function msgpack_deserialize_safe($v ::: string) ::: mixed;

function instance_serialize(object $instance) ::: string | null;
/** @kphp-extern-func-info can_throw */
function instance_serialize_safe(object $instance) ::: string;
/** @kphp-extern-func-info cpp_template_call */
function instance_deserialize($serialized ::: string, $to_type ::: string) ::: instance<^2>;
/** @kphp-extern-func-info cpp_template_call can_throw */
function instance_deserialize_safe($serialized ::: string, $to_type ::: string) ::: instance<^2>;

3 changes: 1 addition & 2 deletions builtin-functions/kphp-light/unsupported-functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ require_once __DIR__ . '/unsupported/kml.txt';
require_once __DIR__ . '/unsupported/kphp-tracing.txt';
require_once __DIR__ . '/unsupported/memcache.txt';
require_once __DIR__ . '/unsupported/misc.txt';
require_once __DIR__ . '/unsupported/serialize.txt';
require_once __DIR__ . '/unsupported/spl.txt';
require_once __DIR__ . '/unsupported/uberh3.txt';
require_once __DIR__ . '/unsupported/vkext.txt';
require_once __DIR__ . '/unsupported/unsupported-server.txt';
require_once __DIR__ . '/unsupported/vkext.txt';
2 changes: 0 additions & 2 deletions builtin-functions/kphp-light/unsupported/arrays.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,3 @@ function usort (&$a ::: array, callable(^1[*] $x, ^1[*] $y):int $callback) ::: v
/** @kphp-extern-func-info cpp_template_call */
function vk_dot_product ($a ::: array, $b ::: array) ::: ^1[*] | ^2[*];

function to_array_debug(any $instance, bool $with_class_names = false) ::: mixed[];
function instance_to_array(object $instance, $with_class_names ::: bool = false) ::: mixed[];
49 changes: 23 additions & 26 deletions compiler/code-gen/declarations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -906,14 +906,10 @@ void ClassDeclaration::compile_accept_json_visitor(CodeGenerator &W, ClassPtr kl
}

void ClassDeclaration::compile_accept_visitor_methods(CodeGenerator &W, ClassPtr klass) {
if (G->is_output_mode_k2()) {
// The current version of runtime-light does not support visitors
return;
}
bool need_generic_accept =
klass->need_to_array_debug_visitor ||
klass->need_instance_cache_visitors ||
klass->need_instance_memory_estimate_visitor;
(klass->need_instance_cache_visitors && !G->is_output_mode_k2()) ||
(klass->need_instance_memory_estimate_visitor && !G->is_output_mode_k2());

if (!need_generic_accept && klass->json_encoders.empty()) {
return;
Expand All @@ -929,14 +925,15 @@ void ClassDeclaration::compile_accept_visitor_methods(CodeGenerator &W, ClassPtr
compile_accept_visitor(W, klass, "ToArrayVisitor");
}

if (klass->need_instance_memory_estimate_visitor ||
// for kphp_instance_cache_value_size statshouse metrics
klass->need_instance_cache_visitors) {
if ((klass->need_instance_memory_estimate_visitor ||
// for kphp_instance_cache_value_size statshouse metrics
klass->need_instance_cache_visitors)
&& !G->is_output_mode_k2()) {
W << NL;
compile_accept_visitor(W, klass, "CommonMemoryEstimateVisitor");
}

if (klass->need_instance_cache_visitors) {
if (klass->need_instance_cache_visitors && !G->is_output_mode_k2()) {
W << NL;
compile_accept_visitor(W, klass, "InstanceReferencesCountingVisitor");
W << NL;
Expand All @@ -950,7 +947,7 @@ void ClassDeclaration::compile_accept_visitor_methods(CodeGenerator &W, ClassPtr

void ClassDeclaration::compile_msgpack_declarations(CodeGenerator &W, ClassPtr klass) {
if (G->is_output_mode_k2()) {
// The current version of runtime-light does not support visitors
// The current version of runtime-light does not support msgpack visitors
return;
}
if (!klass->is_serializable) {
Expand Down Expand Up @@ -1068,16 +1065,12 @@ void ClassDeclaration::compile_job_worker_shared_memory_piece_methods(CodeGenera
}

void ClassMembersDefinition::compile(CodeGenerator &W) const {
if (G->is_output_mode_k2()) {
// The current version of runtime-light does not support visitors
return;
}
bool need_generic_accept =
klass->need_to_array_debug_visitor ||
klass->need_instance_cache_visitors ||
klass->need_instance_memory_estimate_visitor;
(klass->need_instance_cache_visitors && !G->is_output_mode_k2()) ||
(klass->need_instance_memory_estimate_visitor && !G->is_output_mode_k2());

if (!need_generic_accept && !klass->is_serializable && klass->json_encoders.empty()) {
if (!need_generic_accept && (!klass->is_serializable || G->is_output_mode_k2()) && klass->json_encoders.empty()) {
return;
}

Expand All @@ -1100,14 +1093,15 @@ void ClassMembersDefinition::compile(CodeGenerator &W) const {
compile_generic_accept_instantiations(W, klass, "ToArrayVisitor");
}

if (klass->need_instance_memory_estimate_visitor ||
// for kphp_instance_cache_value_size statshouse metrics
klass->need_instance_cache_visitors) {
if ((klass->need_instance_memory_estimate_visitor ||
// for kphp_instance_cache_value_size statshouse metrics
klass->need_instance_cache_visitors)
&& !G->is_output_mode_k2()) {
W << NL;
compile_generic_accept_instantiations(W, klass, "CommonMemoryEstimateVisitor");
}

if (klass->need_instance_cache_visitors) {
if (klass->need_instance_cache_visitors && !G->is_output_mode_k2()) {
W << NL;
compile_generic_accept_instantiations(W, klass, "InstanceReferencesCountingVisitor");
W << NL;
Expand All @@ -1118,10 +1112,13 @@ void ClassMembersDefinition::compile(CodeGenerator &W) const {

W << NL;
compile_accept_json_visitor(W, klass);
W << NL;
compile_msgpack_serialize(W, klass);
W << NL;
compile_msgpack_deserialize(W, klass);

if (!G->is_output_mode_k2()) {
W << NL;
compile_msgpack_serialize(W, klass);
W << NL;
compile_msgpack_deserialize(W, klass);
}

W << CloseNamespace();

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@

#pragma once

#include <string_view>

#include "runtime-common/core/runtime-core.h"
#include "runtime/json-functions.h"
#include "runtime/json-processor-utils.h"
#include "runtime-common/stdlib/serialization/json-functions.h"
#include "runtime-common/stdlib/serialization/json-processor-utils.h"
#include "runtime-common/stdlib/serialization/serialization-context.h"

template<class Tag>
class FromJsonVisitor {
public:
explicit FromJsonVisitor(const mixed &json, bool flatten_class, JsonPath &json_path) noexcept
: json_(json)
, flatten_class_(flatten_class)
, json_path_ (json_path) {}
, json_path_(json_path) {}

template<class T>
void operator()(const char *key, T &value, bool required = false) noexcept {
Expand All @@ -39,23 +38,27 @@ class FromJsonVisitor {
json_path_.leave();
}

bool has_error() const noexcept { return !error_.empty(); }
const string &get_error() const noexcept { return error_; }
bool has_error() const noexcept {
return !error_.empty();
}
const string &get_error() const noexcept {
return error_;
}

static const char *get_json_obj_magic_key() noexcept {
return "__json_obj_magic";
}

private:
[[gnu::noinline]] void on_input_type_mismatch(const mixed &json) noexcept {
error_.assign("unexpected type ");
if (json.is_array()) {
error_.append(json.as_array().is_vector() ? "array" : "object");
} else {
error_.append(json.get_type_str());
}
error_.append(" for key ");
error_.append(json_path_.to_string());
error_.assign("unexpected type ");
if (json.is_array()) {
error_.append(json.as_array().is_vector() ? "array" : "object");
} else {
error_.append(json.get_type_str());
}
error_.append(" for key ");
error_.append(json_path_.to_string());
}

void do_set(bool &value, const mixed &json) noexcept {
Expand Down Expand Up @@ -91,13 +94,13 @@ class FromJsonVisitor {
}

void do_set(JsonRawString &value, const mixed &json) noexcept {
kphp_runtime_context.static_SB.clean();
if (!impl_::JsonEncoder{0, false, get_json_obj_magic_key()}.encode(json)) {
runtime_context_buffer.clean();
if (!impl_::JsonEncoder{0, false, get_json_obj_magic_key()}.encode(json, runtime_context_buffer)) {
error_.append("failed to decode @kphp-json raw_string field ");
error_.append(json_path_.to_string());
return;
}
value.str = kphp_runtime_context.static_SB.str();
value.str = runtime_context_buffer.str();
}

template<class T>
Expand All @@ -113,7 +116,7 @@ class FromJsonVisitor {
void do_set(class_instance<I> &klass, const mixed &json) noexcept;

// just don't fail compilation with empty untyped arrays
void do_set(array<Unknown> &/*array*/, const mixed &/*json*/) noexcept {}
void do_set(array<Unknown> & /*array*/, const mixed & /*json*/) noexcept {}

template<class T>
void do_set_array(array<T> &array, const mixed &json) noexcept {
Expand Down Expand Up @@ -156,7 +159,9 @@ class FromJsonVisitor {
string error_;
const mixed &json_;
bool flatten_class_{false};
JsonPath& json_path_;
JsonPath &json_path_;

string_buffer &runtime_context_buffer{RuntimeContext::get().static_SB};
};

template<class I, class Tag>
Expand All @@ -169,14 +174,14 @@ class_instance<I> from_json_impl(const mixed &json, JsonPath &json_path) noexcep
FromJsonVisitor<Tag> visitor{json, impl_::IsJsonFlattenClass<I>::value, json_path};
instance.get()->accept(visitor);
if (visitor.has_error()) {
JsonEncoderError::msg.append(visitor.get_error());
SerializationLibContext::get().last_json_processor_error.append(visitor.get_error());
return {};
}
}
if constexpr (impl_::HasClassWakeupMethod<I>::value) {
instance.get()->wakeup(instance);
}
return JsonEncoderError::msg.empty() ? instance : class_instance<I>{};
return SerializationLibContext::get().last_json_processor_error.empty() ? instance : class_instance<I>{};
}

template<class Tag>
Expand All @@ -195,19 +200,20 @@ void FromJsonVisitor<Tag>::do_set(class_instance<I> &klass, const mixed &json) n
}

template<class ClassName, class Tag>
ClassName f$JsonEncoder$$from_json_impl(Tag /*tag*/, const string &json_string, const string &/*class_mame*/) noexcept {
JsonEncoderError::msg = {};
ClassName f$JsonEncoder$$from_json_impl(Tag /*tag*/, const string &json_string, const string & /*class_mame*/) noexcept {
auto &msg = SerializationLibContext::get().last_json_processor_error;
msg = {};

auto [json, success] = json_decode(json_string, FromJsonVisitor<Tag>::get_json_obj_magic_key());

if (!success) {
JsonEncoderError::msg.append(json_string.empty() ? "provided empty json string" : "failed to parse json string");
msg.append(json_string.empty() ? "provided empty json string" : "failed to parse json string");
return {};
}
if constexpr (!impl_::IsJsonFlattenClass<typename ClassName::ClassType>::value) {
if (!json.is_array() || json.as_array().is_vector()) {
JsonEncoderError::msg.append("root element of json string must be an object type, got ");
JsonEncoderError::msg.append(json.get_type_c_str());
msg.append("root element of json string must be an object type, got ");
msg.append(json.get_type_c_str());
return {};
}
}
Expand All @@ -216,4 +222,6 @@ ClassName f$JsonEncoder$$from_json_impl(Tag /*tag*/, const string &json_string,
return from_json_impl<typename ClassName::ClassType, Tag>(json, json_path);
}

string f$JsonEncoder$$getLastError() noexcept;
inline string f$JsonEncoder$$getLastError() noexcept {
return SerializationLibContext::get().last_json_processor_error;
}
Loading

0 comments on commit dda3a5a

Please sign in to comment.