ebox/native/c/ebox_module.c

632 lines
22 KiB
C

#include <emacs-module.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <errno.h>
#include <io.h>
#else
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#endif
typedef struct {
unsigned char *data;
size_t len;
} ebox_native_bytes;
typedef struct {
int64_t base;
int64_t hypothetical;
int64_t min_main;
int64_t max_main;
double grow;
double shrink;
bool max_known;
} ebox_native_flex_item;
typedef bool (*ebox_readiness_signal_fn)(int32_t fd);
typedef void (*ebox_readiness_close_fn)(int32_t fd);
extern void *ebox_native_session_create(uint32_t workers,
uint32_t max_jobs,
uint32_t max_results,
uint64_t max_result_bytes,
ebox_native_bytes *error);
extern int64_t ebox_native_session_submit(void *session,
uint64_t generation,
const unsigned char *payload,
size_t payload_len,
ebox_native_bytes *error);
extern bool ebox_native_session_ready(void *session,
uint64_t generation,
int64_t key);
extern ebox_native_bytes ebox_native_session_take(void *session,
uint64_t generation,
int64_t key);
extern bool ebox_native_session_confirm_frame(void *session,
uint64_t generation,
int64_t key,
uint64_t confirmed_revision,
ebox_native_bytes *error);
extern bool ebox_native_session_attach_readiness_channel(
void *session, int32_t fd, ebox_readiness_signal_fn signal,
ebox_readiness_close_fn close);
extern void ebox_native_session_detach_readiness_channel(void *session);
extern void ebox_native_session_cancel(void *session, uint64_t generation);
extern ebox_native_bytes ebox_native_session_stats(void *session);
extern void ebox_native_session_release(void *session);
extern void ebox_native_session_finalize(void *session);
extern void ebox_native_bytes_free(ebox_native_bytes bytes);
extern bool ebox_native_layout_ready(void);
extern bool ebox_native_render_proof(const unsigned char *payload,
size_t payload_len,
ebox_native_bytes *output);
extern bool ebox_native_flex_size_lines(const ebox_native_flex_item *items,
size_t item_count,
const size_t *line_offsets,
size_t line_count,
int64_t main_limit,
int64_t main_gap,
ebox_native_bytes *output);
static emacs_value
ebox_nil(emacs_env *env)
{
return env->intern(env, "nil");
}
static emacs_value
ebox_true(emacs_env *env)
{
return env->intern(env, "t");
}
static emacs_value
ebox_signal_error(emacs_env *env, const char *message, size_t length)
{
emacs_value string = env->make_string(env, message, (ptrdiff_t) length);
emacs_value data = env->funcall(env, env->intern(env, "list"), 1, &string);
env->non_local_exit_signal(env, env->intern(env, "error"), data);
return ebox_nil(env);
}
static emacs_value
ebox_signal_native_error(emacs_env *env, ebox_native_bytes error,
const char *fallback)
{
emacs_value result;
if (error.data != NULL && error.len > 0)
result = ebox_signal_error(env, (const char *) error.data, error.len);
else
result = ebox_signal_error(env, fallback, strlen(fallback));
ebox_native_bytes_free(error);
return result;
}
static bool
ebox_copy_string(emacs_env *env, emacs_value value,
unsigned char **output, size_t *output_len)
{
ptrdiff_t required = 0;
if (!env->copy_string_contents(env, value, NULL, &required) || required <= 0)
return false;
unsigned char *buffer = malloc((size_t) required);
if (buffer == NULL) {
static const char message[] = "Native reflow input allocation failed";
ebox_signal_error(env, message, sizeof message - 1);
return false;
}
ptrdiff_t available = required;
if (!env->copy_string_contents(env, value, (char *) buffer, &available)) {
free(buffer);
return false;
}
*output = buffer;
*output_len = (size_t) (available - 1);
return true;
}
static void
ebox_session_finalizer(void *pointer)
{
if (pointer != NULL)
ebox_native_session_finalize(pointer);
}
static void *
ebox_session_pointer(emacs_env *env, emacs_value value)
{
void *session = env->get_user_ptr(env, value);
if (session == NULL) {
static const char message[] = "Native reflow session has been released";
ebox_signal_error(env, message, sizeof message - 1);
}
return session;
}
static emacs_value
ebox_module_version(emacs_env *env, ptrdiff_t nargs, emacs_value *args,
void *data)
{
(void) nargs;
(void) args;
(void) data;
static const char version[] = "8:3:9";
return env->make_string(env, version, (ptrdiff_t) (sizeof version - 1));
}
static bool
ebox_signal_readiness_fd(int32_t fd)
{
unsigned char byte = 1;
#ifdef _WIN32
int written = _write(fd, &byte, 1);
#else
ssize_t written = write(fd, &byte, 1);
#endif
if (written == 1)
return true;
return errno == EAGAIN
#ifdef EWOULDBLOCK
|| errno == EWOULDBLOCK
#endif
;
}
static void
ebox_close_readiness_fd(int32_t fd)
{
#ifdef _WIN32
(void) _close(fd);
#else
(void) close(fd);
#endif
}
static bool
ebox_make_nonblocking(int32_t fd)
{
#ifdef _WIN32
(void) fd;
return false;
#else
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
return false;
if ((flags & O_NONBLOCK) != 0)
return true;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
#endif
}
static emacs_value
ebox_module_layout_ready(emacs_env *env, ptrdiff_t nargs, emacs_value *args,
void *data)
{
(void) nargs;
(void) args;
(void) data;
return ebox_native_layout_ready() ? ebox_true(env) : ebox_nil(env);
}
static emacs_value
ebox_module_create_session(emacs_env *env, ptrdiff_t nargs, emacs_value *args,
void *data)
{
(void) nargs;
(void) data;
uint32_t workers = (uint32_t) env->extract_integer(env, args[0]);
uint32_t max_jobs = (uint32_t) env->extract_integer(env, args[1]);
uint32_t max_results = (uint32_t) env->extract_integer(env, args[2]);
uint64_t max_result_bytes = (uint64_t) env->extract_integer(env, args[3]);
if (env->non_local_exit_check(env) != emacs_funcall_exit_return)
return ebox_nil(env);
ebox_native_bytes error = {0};
void *session = ebox_native_session_create(workers, max_jobs, max_results,
max_result_bytes, &error);
if (session == NULL)
return ebox_signal_native_error(env, error,
"Native reflow session creation failed");
return env->make_user_ptr(env, ebox_session_finalizer, session);
}
static emacs_value
ebox_module_submit(emacs_env *env, ptrdiff_t nargs, emacs_value *args,
void *data)
{
(void) nargs;
(void) data;
void *session = ebox_session_pointer(env, args[0]);
uint64_t generation = (uint64_t) env->extract_integer(env, args[1]);
if (session == NULL ||
env->non_local_exit_check(env) != emacs_funcall_exit_return)
return ebox_nil(env);
unsigned char *payload = NULL;
size_t payload_len = 0;
if (!ebox_copy_string(env, args[2], &payload, &payload_len))
return ebox_nil(env);
ebox_native_bytes error = {0};
int64_t accepted = ebox_native_session_submit(
session, generation, payload, payload_len, &error);
free(payload);
if (accepted < 0)
return ebox_signal_native_error(env, error,
"Native reflow submission failed");
return env->make_integer(env, accepted);
}
static emacs_value
ebox_module_ready(emacs_env *env, ptrdiff_t nargs, emacs_value *args,
void *data)
{
(void) nargs;
(void) data;
void *session = ebox_session_pointer(env, args[0]);
uint64_t generation = (uint64_t) env->extract_integer(env, args[1]);
int64_t key = (int64_t) env->extract_integer(env, args[2]);
if (session == NULL ||
env->non_local_exit_check(env) != emacs_funcall_exit_return)
return ebox_nil(env);
return ebox_native_session_ready(session, generation, key)
? ebox_true(env) : ebox_nil(env);
}
static emacs_value
ebox_module_take(emacs_env *env, ptrdiff_t nargs, emacs_value *args,
void *data)
{
(void) nargs;
(void) data;
void *session = ebox_session_pointer(env, args[0]);
uint64_t generation = (uint64_t) env->extract_integer(env, args[1]);
int64_t key = (int64_t) env->extract_integer(env, args[2]);
if (session == NULL ||
env->non_local_exit_check(env) != emacs_funcall_exit_return)
return ebox_nil(env);
ebox_native_bytes bytes = ebox_native_session_take(session, generation, key);
if (bytes.data == NULL)
return ebox_nil(env);
emacs_value result = env->make_unibyte_string(
env, (const char *) bytes.data, (ptrdiff_t) bytes.len);
ebox_native_bytes_free(bytes);
return result;
}
static emacs_value
ebox_module_render_proof(emacs_env *env, ptrdiff_t nargs, emacs_value *args,
void *data)
{
(void) nargs;
(void) data;
unsigned char *payload = NULL;
size_t payload_len = 0;
if (!ebox_copy_string(env, args[0], &payload, &payload_len))
return ebox_nil(env);
ebox_native_bytes output = {0};
bool ok = ebox_native_render_proof(payload, payload_len, &output);
free(payload);
if (!ok)
return ebox_signal_native_error(env, output,
"Native proof render failed");
emacs_value result = env->make_unibyte_string(
env, (const char *) output.data, (ptrdiff_t) output.len);
ebox_native_bytes_free(output);
return result;
}
static bool
ebox_extract_flex_integer(emacs_env *env, emacs_value value, int64_t *output)
{
*output = (int64_t) env->extract_integer(env, value);
return env->non_local_exit_check(env) == emacs_funcall_exit_return;
}
static bool
ebox_extract_flex_float(emacs_env *env, emacs_value value, double *output)
{
*output = env->extract_float(env, value);
return env->non_local_exit_check(env) == emacs_funcall_exit_return;
}
static emacs_value
ebox_module_flex_size_lines(emacs_env *env, ptrdiff_t nargs,
emacs_value *args, void *data)
{
(void) nargs;
(void) data;
ptrdiff_t packed_size = env->vec_size(env, args[0]);
if (env->non_local_exit_check(env) != emacs_funcall_exit_return ||
packed_size < 1) {
return ebox_nil(env);
}
int64_t line_count_value = 0;
if (!ebox_extract_flex_integer(env, env->vec_get(env, args[0], 0),
&line_count_value) ||
line_count_value < 0 || line_count_value > 2048) {
return ebox_nil(env);
}
size_t line_count = (size_t) line_count_value;
size_t *line_offsets = calloc(line_count + 1, sizeof *line_offsets);
ebox_native_flex_item *items = NULL;
size_t item_capacity = 0;
size_t item_count = 0;
ptrdiff_t cursor = 1;
bool valid = line_offsets != NULL;
for (size_t line = 0; valid && line < line_count; line++) {
int64_t line_length_value = 0;
if (cursor >= packed_size ||
!ebox_extract_flex_integer(env, env->vec_get(env, args[0], cursor++),
&line_length_value) ||
line_length_value < 0 || line_length_value > 8192) {
valid = false;
break;
}
size_t line_length = (size_t) line_length_value;
if (item_count > 8192 - line_length) {
valid = false;
break;
}
if (item_count + line_length > item_capacity) {
size_t next_capacity = item_count + line_length;
ebox_native_flex_item *next = realloc(
items, next_capacity * sizeof *items);
if (next == NULL) {
valid = false;
break;
}
items = next;
item_capacity = next_capacity;
}
line_offsets[line] = item_count;
for (size_t item = 0; item < line_length && valid; item++) {
ebox_native_flex_item *target = &items[item_count++];
if (cursor + 6 > packed_size ||
!ebox_extract_flex_integer(env, env->vec_get(env, args[0], cursor++),
&target->base) ||
!ebox_extract_flex_integer(env, env->vec_get(env, args[0], cursor++),
&target->hypothetical) ||
!ebox_extract_flex_integer(env, env->vec_get(env, args[0], cursor++),
&target->min_main)) {
valid = false;
break;
}
emacs_value max_value = env->vec_get(env, args[0], cursor++);
target->max_known = env->is_not_nil(env, max_value);
if (env->non_local_exit_check(env) != emacs_funcall_exit_return ||
(target->max_known &&
!ebox_extract_flex_integer(env, max_value, &target->max_main)) ||
!ebox_extract_flex_float(
env, env->vec_get(env, args[0], cursor++), &target->grow) ||
!ebox_extract_flex_float(
env, env->vec_get(env, args[0], cursor++), &target->shrink)) {
valid = false;
break;
}
}
line_offsets[line + 1] = item_count;
}
int64_t main_limit = 0;
int64_t main_gap = 0;
if (cursor != packed_size ||
!ebox_extract_flex_integer(env, args[1], &main_limit) ||
!ebox_extract_flex_integer(env, args[2], &main_gap)) {
valid = false;
}
if (!valid) {
free(items);
free(line_offsets);
return ebox_nil(env);
}
ebox_native_bytes output = {0};
bool ok = ebox_native_flex_size_lines(
items, item_count, line_offsets, line_count, main_limit, main_gap,
&output);
free(items);
free(line_offsets);
if (!ok || output.data == NULL) {
ebox_native_bytes_free(output);
return ebox_nil(env);
}
emacs_value result = env->make_unibyte_string(
env, (const char *) output.data, (ptrdiff_t) output.len);
ebox_native_bytes_free(output);
return result;
}
static emacs_value
ebox_module_confirm_frame(emacs_env *env, ptrdiff_t nargs, emacs_value *args,
void *data)
{
(void) nargs;
(void) data;
void *session = ebox_session_pointer(env, args[0]);
uint64_t generation = (uint64_t) env->extract_integer(env, args[1]);
int64_t key = (int64_t) env->extract_integer(env, args[2]);
uint64_t confirmed_revision = (uint64_t) env->extract_integer(env, args[3]);
if (session == NULL ||
env->non_local_exit_check(env) != emacs_funcall_exit_return)
return ebox_nil(env);
ebox_native_bytes error = {0};
bool confirmed = ebox_native_session_confirm_frame(
session, generation, key, confirmed_revision, &error);
if (!confirmed && error.data != NULL)
return ebox_signal_native_error(env, error,
"Native reflow confirmation failed");
ebox_native_bytes_free(error);
return confirmed ? ebox_true(env) : ebox_nil(env);
}
static emacs_value
ebox_module_attach_readiness_channel(emacs_env *env, ptrdiff_t nargs,
emacs_value *args, void *data)
{
(void) nargs;
(void) data;
void *session = ebox_session_pointer(env, args[0]);
if (session == NULL ||
env->non_local_exit_check(env) != emacs_funcall_exit_return)
return ebox_nil(env);
if (env->size < (ptrdiff_t) (offsetof(emacs_env, open_channel)
+ sizeof env->open_channel) ||
env->open_channel == NULL) {
static const char message[] = "Native readiness channels are unsupported";
return ebox_signal_error(env, message, sizeof message - 1);
}
int fd = env->open_channel(env, args[1]);
if (fd < 0 || env->non_local_exit_check(env) != emacs_funcall_exit_return)
return ebox_nil(env);
if (!ebox_make_nonblocking(fd)) {
static const char message[] = "Native readiness channel setup failed";
ebox_close_readiness_fd((int32_t) fd);
return ebox_signal_error(env, message, sizeof message - 1);
}
if (ebox_native_session_attach_readiness_channel(
session, (int32_t) fd, ebox_signal_readiness_fd,
ebox_close_readiness_fd))
return ebox_true(env);
ebox_close_readiness_fd((int32_t) fd);
return ebox_nil(env);
}
static emacs_value
ebox_module_detach_readiness_channel(emacs_env *env, ptrdiff_t nargs,
emacs_value *args, void *data)
{
(void) nargs;
(void) data;
void *session = ebox_session_pointer(env, args[0]);
if (session == NULL ||
env->non_local_exit_check(env) != emacs_funcall_exit_return)
return ebox_nil(env);
ebox_native_session_detach_readiness_channel(session);
return ebox_true(env);
}
static emacs_value
ebox_module_cancel(emacs_env *env, ptrdiff_t nargs, emacs_value *args,
void *data)
{
(void) nargs;
(void) data;
void *session = ebox_session_pointer(env, args[0]);
uint64_t generation = (uint64_t) env->extract_integer(env, args[1]);
if (session == NULL ||
env->non_local_exit_check(env) != emacs_funcall_exit_return)
return ebox_nil(env);
ebox_native_session_cancel(session, generation);
return ebox_true(env);
}
static emacs_value
ebox_module_stats(emacs_env *env, ptrdiff_t nargs, emacs_value *args,
void *data)
{
(void) nargs;
(void) data;
void *session = ebox_session_pointer(env, args[0]);
if (session == NULL)
return ebox_nil(env);
ebox_native_bytes bytes = ebox_native_session_stats(session);
if (bytes.data == NULL) {
static const char message[] = "Native reflow stats failed";
return ebox_signal_error(env, message, sizeof message - 1);
}
emacs_value result = env->make_string(
env, (const char *) bytes.data, (ptrdiff_t) bytes.len);
ebox_native_bytes_free(bytes);
return result;
}
static emacs_value
ebox_module_release(emacs_env *env, ptrdiff_t nargs, emacs_value *args,
void *data)
{
(void) nargs;
(void) data;
void *session = env->get_user_ptr(env, args[0]);
if (session != NULL) {
env->set_user_ptr(env, args[0], NULL);
ebox_native_session_release(session);
}
return ebox_true(env);
}
static int
ebox_bind_function(emacs_env *env, const char *name, ptrdiff_t min_arity,
ptrdiff_t max_arity, emacs_function function,
const char *docstring)
{
emacs_value symbol = env->intern(env, name);
emacs_value callable = env->make_function(
env, min_arity, max_arity, function, docstring, NULL);
emacs_value arguments[] = {symbol, callable};
env->funcall(env, env->intern(env, "defalias"), 2, arguments);
return env->non_local_exit_check(env) == emacs_funcall_exit_return ? 0 : 1;
}
int
ebox_module_init_impl(struct emacs_runtime *runtime)
{
if (runtime == NULL || runtime->get_environment == NULL)
return 1;
emacs_env *env = runtime->get_environment(runtime);
if (env == NULL ||
env->size < (ptrdiff_t) (offsetof(emacs_env, make_unibyte_string)
+ sizeof env->make_unibyte_string))
return 2;
if (ebox_bind_function(env, "ebox-native--module-version", 0, 0,
ebox_module_version,
"Return the private Ebox native ABI version.") ||
ebox_bind_function(env, "ebox-native--module-layout-ready-p", 0, 0,
ebox_module_layout_ready,
"Return non-nil when native layout is implemented.") ||
ebox_bind_function(env, "ebox-native--module-create-session", 4, 4,
ebox_module_create_session,
"Create one bounded private native session.") ||
ebox_bind_function(env, "ebox-native--module-submit", 3, 3,
ebox_module_submit,
"Submit one private native control batch.") ||
ebox_bind_function(env, "ebox-native--module-ready-p", 3, 3,
ebox_module_ready,
"Return non-nil when a private result is ready.") ||
ebox_bind_function(env, "ebox-native--module-take", 3, 3,
ebox_module_take,
"Take one private native result as bytes.") ||
ebox_bind_function(env, "ebox-native--module-render-proof", 1, 1,
ebox_module_render_proof,
"Render one private native proof synchronously.") ||
ebox_bind_function(env, "ebox-native--module-flex-size-lines", 3, 3,
ebox_module_flex_size_lines,
"Calculate pure native Flex geometry lines.") ||
ebox_bind_function(env, "ebox-native--module-confirm-frame", 4, 4,
ebox_module_confirm_frame,
"Confirm one published native layout frame.") ||
ebox_bind_function(env, "ebox-native--module-attach-readiness-channel",
2, 2, ebox_module_attach_readiness_channel,
"Attach one native readiness pipe process.") ||
ebox_bind_function(env, "ebox-native--module-detach-readiness-channel",
1, 1, ebox_module_detach_readiness_channel,
"Detach one native readiness pipe process.") ||
ebox_bind_function(env, "ebox-native--module-cancel", 2, 2,
ebox_module_cancel,
"Cancel one private native generation.") ||
ebox_bind_function(env, "ebox-native--module-stats", 1, 1,
ebox_module_stats,
"Return private native session stats as JSON.") ||
ebox_bind_function(env, "ebox-native--module-release", 1, 1,
ebox_module_release,
"Release one private native session."))
return 3;
emacs_value feature = env->intern(env, "ebox-native-module");
env->funcall(env, env->intern(env, "provide"), 1, &feature);
return env->non_local_exit_check(env) == emacs_funcall_exit_return ? 0 : 4;
}