Skip to content

Commit

Permalink
Replace fbuffer by stack buffers or RB_ALLOCV in parser.c
Browse files Browse the repository at this point in the history
We only use that buffer for parsing integer and floats, these
are unlikely to be very big, and if so we can just use RB_ALLOCV as it will
almost always end in a small `alloca`.

This allow to no longer need `rb_protect` around the parser.
  • Loading branch information
byroot committed Jan 16, 2025
1 parent 591056a commit 9948599
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 42 deletions.
11 changes: 1 addition & 10 deletions ext/json/ext/fbuffer/fbuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,11 @@ typedef struct FBufferStruct {
#define FBUFFER_PAIR(fb) FBUFFER_PTR(fb), FBUFFER_LEN(fb)

static void fbuffer_free(FBuffer *fb);
#ifndef JSON_GENERATOR
static void fbuffer_clear(FBuffer *fb);
#endif
static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len);
#ifdef JSON_GENERATOR
static void fbuffer_append_long(FBuffer *fb, long number);
#endif
static inline void fbuffer_append_char(FBuffer *fb, char newchr);
#ifdef JSON_GENERATOR
static VALUE fbuffer_finalize(FBuffer *fb);
#endif

static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char *stack_buffer, long stack_buffer_size)
{
Expand Down Expand Up @@ -156,7 +150,6 @@ static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len)
}
}

#ifdef JSON_GENERATOR
static void fbuffer_append_str(FBuffer *fb, VALUE str)
{
const char *newstr = StringValuePtr(str);
Expand All @@ -166,7 +159,6 @@ static void fbuffer_append_str(FBuffer *fb, VALUE str)

fbuffer_append(fb, newstr, len);
}
#endif

static inline void fbuffer_append_char(FBuffer *fb, char newchr)
{
Expand All @@ -175,7 +167,6 @@ static inline void fbuffer_append_char(FBuffer *fb, char newchr)
fb->len++;
}

#ifdef JSON_GENERATOR
static long fltoa(long number, char *buf)
{
static const char digits[] = "0123456789";
Expand Down Expand Up @@ -210,5 +201,5 @@ static VALUE fbuffer_finalize(FBuffer *fb)
return result;
}
}
#endif

#endif
98 changes: 66 additions & 32 deletions ext/json/ext/parser/parser.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
#include "ruby.h"
#include "../fbuffer/fbuffer.h"
#include "ruby/encoding.h"

/* shims */
/* This is the fallback definition from Ruby 3.4 */

#ifndef RBIMPL_STDBOOL_H
#if defined(__cplusplus)
# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L)
# include <cstdbool>
# endif
#elif defined(HAVE_STDBOOL_H)
# include <stdbool.h>
#elif !defined(HAVE__BOOL)
typedef unsigned char _Bool;
# define bool _Bool
# define true ((_Bool)+1)
# define false ((_Bool)+0)
# define __bool_true_false_are_defined
#endif
#endif

#ifndef RB_UNLIKELY
#define RB_UNLIKELY(expr) expr
#endif

#ifndef RB_LIKELY
#define RB_LIKELY(expr) expr
#endif

static VALUE mJSON, eNestingError, Encoding_UTF_8;
static VALUE CNaN, CInfinity, CMinusInfinity;
Expand Down Expand Up @@ -401,7 +428,6 @@ typedef struct JSON_ParserStateStruct {
VALUE stack_handle;
const char *cursor;
const char *end;
FBuffer fbuffer;
rvalue_stack *stack;
rvalue_cache name_cache;
int in_array;
Expand Down Expand Up @@ -690,26 +716,44 @@ static inline VALUE fast_decode_integer(const char *p, const char *pe)
return LL2NUM(memo);
}

static VALUE
static VALUE json_decode_large_integer(const char *start, long len)
{
VALUE buffer_v;
char *buffer = RB_ALLOCV_N(char, buffer_v, len + 1);
MEMCPY(buffer, start, char, len);
buffer[len] = '\0';
VALUE number = rb_cstr2inum(buffer, 10);
RB_ALLOCV_END(buffer_v);
return number;
}

static inline VALUE
json_decode_integer(JSON_ParserState *state, const char *start, const char *end)
{
long len = end - start;
if (RB_LIKELY(len < MAX_FAST_INTEGER_SIZE)) {
return fast_decode_integer(start, end);
}

fbuffer_clear(&state->fbuffer);
fbuffer_append(&state->fbuffer, start, len);
fbuffer_append_char(&state->fbuffer, '\0');
return rb_cstr2inum(FBUFFER_PTR(&state->fbuffer), 10);
return json_decode_large_integer(start, len);
}

static VALUE json_decode_large_float(const char *start, long len)
{
VALUE buffer_v;
char *buffer = RB_ALLOCV_N(char, buffer_v, len + 1);
MEMCPY(buffer, start, char, len);
buffer[len] = '\0';
VALUE number = DBL2NUM(rb_cstr_to_dbl(buffer, 1));
RB_ALLOCV_END(buffer_v);
return number;
}

static VALUE json_decode_float(JSON_ParserState *state, const char *start, const char *end)
{
VALUE mod = Qnil;
ID method_id = 0;
JSON_ParserConfig *config = state->config;
if (config->decimal_class) {
if (RB_UNLIKELY(config->decimal_class)) {
// TODO: we should move this to the constructor
if (rb_respond_to(config->decimal_class, i_try_convert)) {
mod = config->decimal_class;
Expand Down Expand Up @@ -739,15 +783,17 @@ static VALUE json_decode_float(JSON_ParserState *state, const char *start, const
}

long len = end - start;
fbuffer_clear(&state->fbuffer);
fbuffer_append(&state->fbuffer, start, len);
fbuffer_append_char(&state->fbuffer, '\0');

if (method_id) {
VALUE text = rb_str_new2(FBUFFER_PTR(&state->fbuffer));
if (RB_UNLIKELY(method_id)) {
VALUE text = rb_str_new(start, len);
return rb_funcallv(mod, method_id, 1, &text);
} else if (RB_LIKELY(len < 64)) {
char buffer[64];
MEMCPY(buffer, start, char, len);
buffer[len] = '\0';
return DBL2NUM(rb_cstr_to_dbl(buffer, 1));
} else {
return DBL2NUM(rb_cstr_to_dbl(FBUFFER_PTR(&state->fbuffer), 1));
return json_decode_large_float(start, len);
}
}

Expand Down Expand Up @@ -1283,14 +1329,6 @@ static VALUE cParserConfig_initialize(VALUE self, VALUE opts)
return self;
}

static VALUE cParser_parse_safe(VALUE vstate)
{
JSON_ParserState *state = (JSON_ParserState *)vstate;
VALUE result = json_parse_any(state);
json_ensure_eof(state);
return result;
}

static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
{
Vsource = convert_encoding(StringValue(Vsource));
Expand All @@ -1311,17 +1349,13 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource)
};
JSON_ParserState *state = &_state;

char stack_buffer[FBUFFER_STACK_SIZE];
fbuffer_stack_init(&state->fbuffer, FBUFFER_INITIAL_LENGTH_DEFAULT, stack_buffer, FBUFFER_STACK_SIZE);

int interupted;
VALUE result = rb_protect(cParser_parse_safe, (VALUE)state, &interupted);
VALUE result = json_parse_any(state);

// This may be skipped in case of exception, but
// it won't cause a leak.
rvalue_stack_eagerly_release(state->stack_handle);
fbuffer_free(&state->fbuffer);
if (interupted) {
rb_jump_tag(interupted);
}

json_ensure_eof(state);

return result;
}
Expand Down

0 comments on commit 9948599

Please sign in to comment.