Skip to content
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

[CORE-5093] Data Transforms SDK: Schema Registry Support for JavaScript #21491

Merged
merged 10 commits into from
Jul 23, 2024
24 changes: 18 additions & 6 deletions src/transform-sdk/js/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ endif()
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)

if(CMAKE_BUILD_TYPE MATCHES Release)
include(CheckIPOSupported)
check_ipo_supported(RESULT ltosupported OUTPUT error)
# NOTE(oren): rather a blunt instrument. CMake doesn't currently
# offer the ability to customize the LTO setting, and thin LTO is not
# sufficient for our use case, so we blanket enable full LTO for all
# targets. Predicated on compiler ID becuase these options are specific
# to clang.
if(ltosupported AND (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
add_compile_options(-flto=full)
add_link_options(-flto=full)
endif()
endif()

FetchContent_Declare(
quickjs
GIT_REPOSITORY https://github.com/quickjs-ng/quickjs.git
Expand Down Expand Up @@ -65,12 +79,10 @@ target_link_libraries(
qjs Redpanda::transform_sdk Redpanda::js_vm
)

if(CMAKE_BUILD_TYPE MATCHES Release)
include(CheckIPOSupported)
check_ipo_supported(RESULT ltosupported OUTPUT error)
if(ltosupported)
set_property(TARGET redpanda_js_transform PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
endif()
# NOTE(oren): Fall back to standard property if we decided LTO was needed
# and happen to be building non-clang for some reason.
if (ltosupported AND NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
set_property(TARGET redpanda_js_transform PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
endif()

# Tests
Expand Down
50 changes: 46 additions & 4 deletions src/transform-sdk/js/js_vm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <sys/types.h>

#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <expected>
Expand Down Expand Up @@ -126,18 +127,42 @@ value value::array_buffer(JSContext* ctx, std::span<uint8_t> data) {
ctx, data.data(), data.size(), func, opaque, /*is_shared=*/0)};
}

std::expected<value, exception>
value::array_buffer_copy(JSContext* ctx, std::span<uint8_t> data) {
value val{ctx, JS_NewArrayBufferCopy(ctx, data.data(), data.size())};
if (val.is_exception()) {
return std::unexpected(exception::current(ctx));
}
return val;
}

value value::uint8_array(JSContext* ctx, std::span<uint8_t> data) {
void* opaque = nullptr;
// NOLINTNEXTLINE(*easily-swappable-parameters)
JSFreeArrayBufferDataFunc* func = [](JSRuntime*, void* opaque, void* ptr) {
// Nothing to do
};

return {
ctx,
JS_NewUint8Array(
ctx, data.data(), data.size(), func, opaque, /*is_shared=*/0)};
}

// see quickjs source for detailed buffer ownership semantics
// https://github.com/quickjs-ng/quickjs/blob/da5b95dcaf372dcc206019e171a0b08983683bf5/quickjs.c#L49379-L49436
// TL;DR - With copying enabled, the array constructor registers a baked-in
// free_func to be called by the object destructor. The copy of data is made
// and mangaged internally to the quickjs runtime.
std::expected<value, exception>
value::uint8_array_copy(JSContext* ctx, std::span<uint8_t> data) {
value val{ctx, JS_NewUint8ArrayCopy(ctx, data.data(), data.size())};
if (val.is_exception()) {
return std::unexpected(exception::current(ctx));
}
return val;
}

void value::detach_buffer() {
assert(is_array_buffer());
JS_DetachArrayBuffer(_ctx, _underlying);
Expand Down Expand Up @@ -182,6 +207,7 @@ value value::current_exception(JSContext* ctx) {
}
bool value::is_number() const { return JS_IsNumber(_underlying) != 0; }
bool value::is_exception() const { return JS_IsException(_underlying) != 0; }
bool value::is_error() const { return JS_IsError(_ctx, _underlying) != 0; }
bool value::is_function() const {
return JS_IsFunction(_ctx, _underlying) != 0;
}
Expand All @@ -205,6 +231,11 @@ double value::as_number() const {
return JS_VALUE_GET_INT(_underlying);
}

int32_t value::as_integer() const {
auto num = as_number();
return static_cast<int32_t>(std::lround(num));
}

std::expected<value, exception> value::call(std::span<value> values) {
assert(is_function());
std::vector<JSValue> raw;
Expand Down Expand Up @@ -275,6 +306,7 @@ size_t value::array_length() const {
return JS_VALUE_GET_INT(prop.raw());
}

// NOLINTNEXTLINE(*-no-recursion)
std::string value::debug_string() const {
if (is_null()) {
return "null";
Expand All @@ -284,12 +316,18 @@ std::string value::debug_string() const {
}
size_t size = 0;
const char* str = JS_ToCStringLen(_ctx, &size, _underlying);
std::string result;
if (str != nullptr) {
auto result = std::string(str, size);
result = std::string(str, size);
JS_FreeCString(_ctx, str);
return result;
} else {
result = "[exception]";
}
if (is_exception() || is_error()) {
auto stack = get_property("stack");
result += std::format("\n Stack: \n{}", stack.debug_string());
}
return "[exception]";
return result;
}

bool operator==(const value& lhs, const value& rhs) {
Expand Down Expand Up @@ -462,7 +500,11 @@ std::expected<std::monostate, exception> runtime::create_builtins() {
if (!result.has_value()) {
return result;
}
return global_this.set_property("process", process);
result = global_this.set_property("process", process);
if (!result.has_value()) {
return result;
}
return global_this.set_property("self", value::global(_ctx.get()));
}

std::expected<compiled_bytecode, exception>
Expand Down
13 changes: 13 additions & 0 deletions src/transform-sdk/js/js_vm.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ class value {
*/
static value array_buffer(JSContext* ctx, std::span<uint8_t> data);

/** Create a typed array buffer value (copy) */
static std::expected<value, exception>
array_buffer_copy(JSContext* ctx, std::span<uint8_t> data);

/**
* Create an array buffer value (view - no copies).
*
Expand All @@ -150,6 +154,10 @@ class value {
*/
static value uint8_array(JSContext* ctx, std::span<uint8_t> data);

/** Create a typed uint8 array value (copy) */
static std::expected<value, exception>
uint8_array_copy(JSContext* ctx, std::span<uint8_t> data);

/**
* Free the underlying memory to an array buffer.
*/
Expand Down Expand Up @@ -196,6 +204,8 @@ class value {
[[nodiscard]] bool is_number() const;
/** Is this value an exception? */
[[nodiscard]] bool is_exception() const;
/** Is this value an error? */
[[nodiscard]] bool is_error() const;
/** Is this value a function? */
[[nodiscard]] bool is_function() const;
/** Is this value a string? */
Expand All @@ -216,6 +226,9 @@ class value {
/** Get the number from the object. */
[[nodiscard]] double as_number() const;

/** Get an integer from the object. Rounding. */
[[nodiscard]] int32_t as_integer() const;

/**
* Return a reference to the raw JSValue without incrementing the ref
* count.
Expand Down
Loading
Loading