Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/compilers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ jobs:
- { uses: './.github/actions/compilers', name: 'clang 21', with: { tag: 'clang-21' } }
- { uses: './.github/actions/compilers', name: 'clang 20', with: { tag: 'clang-20' } }
- { uses: './.github/actions/compilers', name: 'clang 19', with: { tag: 'clang-19' } }
- { uses: './.github/actions/compilers', name: 'clang 18', with: { tag: 'clang-18' } }
# clang-18 has a bug causing ruby_current_ec to sometimes be null
# - { uses: './.github/actions/compilers', name: 'clang 18', with: { tag: 'clang-18' } }
- { uses: './.github/actions/compilers', name: 'clang 17', with: { tag: 'clang-17' } }
- { uses: './.github/actions/compilers', name: 'clang 16', with: { tag: 'clang-16' } }
- { uses: './.github/actions/compilers', name: 'clang 15', with: { tag: 'clang-15' } }
Expand Down
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -1746,6 +1746,7 @@ AS_IF([test "$GCC" = yes], [
[rb_cv_gcc_atomic_builtins=no])])
AS_IF([test "$rb_cv_gcc_atomic_builtins" = yes], [
AC_DEFINE(HAVE_GCC_ATOMIC_BUILTINS)
AC_CHECK_LIB([atomic], [__atomic_fetch_add_8])
AC_CACHE_CHECK([for 64bit __atomic builtins], [rb_cv_gcc_atomic_builtins_64], [
AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include <stdint.h>
uint64_t atomic_var;]],
Expand Down
19 changes: 14 additions & 5 deletions ext/json/generator/generator.c
Original file line number Diff line number Diff line change
Expand Up @@ -926,11 +926,6 @@ static size_t State_memsize(const void *ptr)
return sizeof(JSON_Generator_State);
}

#ifndef HAVE_RB_EXT_RACTOR_SAFE
# undef RUBY_TYPED_FROZEN_SHAREABLE
# define RUBY_TYPED_FROZEN_SHAREABLE 0
#endif

static const rb_data_type_t JSON_Generator_State_type = {
"JSON/Generator/State",
{
Expand Down Expand Up @@ -1630,6 +1625,7 @@ static VALUE string_config(VALUE config)
*/
static VALUE cState_indent_set(VALUE self, VALUE indent)
{
rb_check_frozen(self);
GET_STATE(self);
RB_OBJ_WRITE(self, &state->indent, string_config(indent));
return Qnil;
Expand All @@ -1655,6 +1651,7 @@ static VALUE cState_space(VALUE self)
*/
static VALUE cState_space_set(VALUE self, VALUE space)
{
rb_check_frozen(self);
GET_STATE(self);
RB_OBJ_WRITE(self, &state->space, string_config(space));
return Qnil;
Expand All @@ -1678,6 +1675,7 @@ static VALUE cState_space_before(VALUE self)
*/
static VALUE cState_space_before_set(VALUE self, VALUE space_before)
{
rb_check_frozen(self);
GET_STATE(self);
RB_OBJ_WRITE(self, &state->space_before, string_config(space_before));
return Qnil;
Expand All @@ -1703,6 +1701,7 @@ static VALUE cState_object_nl(VALUE self)
*/
static VALUE cState_object_nl_set(VALUE self, VALUE object_nl)
{
rb_check_frozen(self);
GET_STATE(self);
RB_OBJ_WRITE(self, &state->object_nl, string_config(object_nl));
return Qnil;
Expand All @@ -1726,6 +1725,7 @@ static VALUE cState_array_nl(VALUE self)
*/
static VALUE cState_array_nl_set(VALUE self, VALUE array_nl)
{
rb_check_frozen(self);
GET_STATE(self);
RB_OBJ_WRITE(self, &state->array_nl, string_config(array_nl));
return Qnil;
Expand All @@ -1749,6 +1749,7 @@ static VALUE cState_as_json(VALUE self)
*/
static VALUE cState_as_json_set(VALUE self, VALUE as_json)
{
rb_check_frozen(self);
GET_STATE(self);
RB_OBJ_WRITE(self, &state->as_json, rb_convert_type(as_json, T_DATA, "Proc", "to_proc"));
return Qnil;
Expand Down Expand Up @@ -1791,6 +1792,7 @@ static long long_config(VALUE num)
*/
static VALUE cState_max_nesting_set(VALUE self, VALUE depth)
{
rb_check_frozen(self);
GET_STATE(self);
state->max_nesting = long_config(depth);
return Qnil;
Expand All @@ -1816,6 +1818,7 @@ static VALUE cState_script_safe(VALUE self)
*/
static VALUE cState_script_safe_set(VALUE self, VALUE enable)
{
rb_check_frozen(self);
GET_STATE(self);
state->script_safe = RTEST(enable);
return Qnil;
Expand Down Expand Up @@ -1847,6 +1850,7 @@ static VALUE cState_strict(VALUE self)
*/
static VALUE cState_strict_set(VALUE self, VALUE enable)
{
rb_check_frozen(self);
GET_STATE(self);
state->strict = RTEST(enable);
return Qnil;
Expand All @@ -1871,6 +1875,7 @@ static VALUE cState_allow_nan_p(VALUE self)
*/
static VALUE cState_allow_nan_set(VALUE self, VALUE enable)
{
rb_check_frozen(self);
GET_STATE(self);
state->allow_nan = RTEST(enable);
return Qnil;
Expand All @@ -1895,6 +1900,7 @@ static VALUE cState_ascii_only_p(VALUE self)
*/
static VALUE cState_ascii_only_set(VALUE self, VALUE enable)
{
rb_check_frozen(self);
GET_STATE(self);
state->ascii_only = RTEST(enable);
return Qnil;
Expand Down Expand Up @@ -1932,6 +1938,7 @@ static VALUE cState_depth(VALUE self)
*/
static VALUE cState_depth_set(VALUE self, VALUE depth)
{
rb_check_frozen(self);
GET_STATE(self);
state->depth = long_config(depth);
return Qnil;
Expand Down Expand Up @@ -1965,6 +1972,7 @@ static void buffer_initial_length_set(JSON_Generator_State *state, VALUE buffer_
*/
static VALUE cState_buffer_initial_length_set(VALUE self, VALUE buffer_initial_length)
{
rb_check_frozen(self);
GET_STATE(self);
buffer_initial_length_set(state, buffer_initial_length);
return Qnil;
Expand Down Expand Up @@ -2031,6 +2039,7 @@ static void configure_state(JSON_Generator_State *state, VALUE vstate, VALUE con

static VALUE cState_configure(VALUE self, VALUE opts)
{
rb_check_frozen(self);
GET_STATE(self);
configure_state(state, self, opts);
return self;
Expand Down
5 changes: 5 additions & 0 deletions ext/json/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ typedef unsigned char _Bool;
#endif
#endif

#ifndef HAVE_RB_EXT_RACTOR_SAFE
# undef RUBY_TYPED_FROZEN_SHAREABLE
# define RUBY_TYPED_FROZEN_SHAREABLE 0
#endif

#ifndef NORETURN
#define NORETURN(x) x
#endif
Expand Down
2 changes: 1 addition & 1 deletion ext/json/lib/json/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@ def initialize(options = nil, &as_json)
options[:as_json] = as_json if as_json

@state = State.new(options).freeze
@parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options))
@parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options)).freeze
end

# call-seq:
Expand Down
3 changes: 2 additions & 1 deletion ext/json/parser/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -1466,6 +1466,7 @@ static void parser_config_init(JSON_ParserConfig *config, VALUE opts)
*/
static VALUE cParserConfig_initialize(VALUE self, VALUE opts)
{
rb_check_frozen(self);
GET_PARSER_CONFIG;

parser_config_init(config, opts);
Expand Down Expand Up @@ -1561,7 +1562,7 @@ static const rb_data_type_t JSON_ParserConfig_type = {
JSON_ParserConfig_memsize,
},
0, 0,
RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE,
};

static VALUE cJSON_parser_s_allocate(VALUE klass)
Expand Down
1 change: 1 addition & 0 deletions insns.def
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,7 @@ definedivar
()
(VALUE val)
// attr bool leaf = false;
// attr bool zjit_profile = true;
{
val = Qnil;
if (!UNDEF_P(vm_getivar(GET_SELF(), id, GET_ISEQ(), ic, NULL, FALSE, Qundef))) {
Expand Down
7 changes: 5 additions & 2 deletions ruby_atomic.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
#define INTERNAL_ATOMIC_H

#include "ruby/atomic.h"
#ifdef HAVE_STDATOMIC_H
# include <stdatomic.h>
#endif

#define RUBY_ATOMIC_VALUE_LOAD(x) rbimpl_atomic_value_load(&(x), RBIMPL_ATOMIC_SEQ_CST)

Expand Down Expand Up @@ -76,9 +79,9 @@ rbimpl_atomic_u64_fetch_add(volatile rbimpl_atomic_uint64_t *ptr, uint64_t val)
return InterlockedExchangeAdd64((volatile LONG64 *)ptr, val);
#elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx))
return atomic_add_64_nv(ptr, val) - val;
#elif defined(HAVE_STDATOMIC_H)
return atomic_fetch_add_explicit((_Atomic uint64_t *)ptr, val, memory_order_seq_cst);
#else
// TODO: stdatomic

// Fallback using mutex for platforms without 64-bit atomics
static rb_native_mutex_t lock = RB_NATIVE_MUTEX_INITIALIZER;
rb_native_mutex_lock(&lock);
Expand Down
14 changes: 14 additions & 0 deletions test/json/json_generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -901,4 +901,18 @@ def test_generate_duplicate_keys_disallowed
end
assert_equal %(detected duplicate key "foo" in #{hash.inspect}), error.message
end

def test_frozen
state = JSON::State.new.freeze
assert_raise(FrozenError) do
state.configure(max_nesting: 1)
end
setters = state.methods.grep(/\w=$/)
assert_not_empty setters
setters.each do |setter|
assert_raise(FrozenError) do
state.send(setter, 1)
end
end
end
end
8 changes: 8 additions & 0 deletions test/json/json_parser_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,14 @@ def test_parse_whitespace_after_newline
assert_equal [], JSON.parse("[\n#{' ' * (8 + 8 + 4 + 3)}]")
end

def test_frozen
parser_config = JSON::Parser::Config.new({}).freeze
omit "JRuby failure in CI" if RUBY_ENGINE == "jruby"
assert_raise FrozenError do
parser_config.send(:initialize, {})
end
end

private

def assert_equal_float(expected, actual, delta = 1e-2)
Expand Down
59 changes: 59 additions & 0 deletions test/json/ractor_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,63 @@ def test_generate
_, status = Process.waitpid2(pid)
assert_predicate status, :success?
end

def test_coder
coder = JSON::Coder.new.freeze
assert Ractor.shareable?(coder)
pid = fork do
Warning[:experimental] = false
r = Ractor.new(coder) do |coder|
json = coder.dump({
'a' => 2,
'b' => 3.141,
'c' => 'c',
'd' => [ 1, "b", 3.14 ],
'e' => { 'foo' => 'bar' },
'g' => "\"\0\037",
'h' => 1000.0,
'i' => 0.001
})
coder.load(json)
end
expected_json = JSON.parse('{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' +
'"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}')
actual_json = r.value

if expected_json == actual_json
exit 0
else
puts "Expected:"
puts expected_json
puts "Actual:"
puts actual_json
puts
exit 1
end
end
_, status = Process.waitpid2(pid)
assert_predicate status, :success?
end

class NonNative
def initialize(value)
@value = value
end
end

def test_coder_proc
block = Ractor.shareable_proc { |value| value.as_json }
coder = JSON::Coder.new(&block).freeze
assert Ractor.shareable?(coder)

pid = fork do
Warning[:experimental] = false
assert_equal [{}], Ractor.new(coder) { |coder|
coder.load('[{}]')
}.value
end

_, status = Process.waitpid2(pid)
assert_predicate status, :success?
end if Ractor.respond_to?(:shareable_proc)
end if defined?(Ractor) && Process.respond_to?(:fork)
2 changes: 1 addition & 1 deletion zjit/src/asm/arm64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ pub fn lsl(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, shift: A64Opnd) {

ShiftImm::lsl(rd.reg_no, rn.reg_no, uimm as u8, rd.num_bits).into()
},
_ => panic!("Invalid operands combination to lsl instruction")
_ => panic!("Invalid operands combination {rd:?} {rn:?} {shift:?} to lsl instruction")
};

cb.write_bytes(&bytes);
Expand Down
44 changes: 36 additions & 8 deletions zjit/src/backend/x86_64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,14 +542,23 @@ impl Assembler {
*opnds = vec![];
asm.push_insn(insn);
}
Insn::CSelZ { out, .. } |
Insn::CSelNZ { out, .. } |
Insn::CSelE { out, .. } |
Insn::CSelNE { out, .. } |
Insn::CSelL { out, .. } |
Insn::CSelLE { out, .. } |
Insn::CSelG { out, .. } |
Insn::CSelGE { out, .. } |
Insn::CSelZ { truthy: left, falsy: right, out } |
Insn::CSelNZ { truthy: left, falsy: right, out } |
Insn::CSelE { truthy: left, falsy: right, out } |
Insn::CSelNE { truthy: left, falsy: right, out } |
Insn::CSelL { truthy: left, falsy: right, out } |
Insn::CSelLE { truthy: left, falsy: right, out } |
Insn::CSelG { truthy: left, falsy: right, out } |
Insn::CSelGE { truthy: left, falsy: right, out } => {
*left = split_stack_membase(asm, *left, SCRATCH1_OPND, &stack_state);
*right = split_stack_membase(asm, *right, SCRATCH0_OPND, &stack_state);
*right = split_if_both_memory(asm, *right, *left, SCRATCH0_OPND);
let mem_out = split_memory_write(out, SCRATCH0_OPND);
asm.push_insn(insn);
if let Some(mem_out) = mem_out {
asm.store(mem_out, SCRATCH0_OPND);
}
}
Insn::Lea { out, .. } => {
let mem_out = split_memory_write(out, SCRATCH0_OPND);
asm.push_insn(insn);
Expand Down Expand Up @@ -1776,4 +1785,23 @@ mod tests {
");
assert_snapshot!(cb.hexdump(), @"49bb00100000000000004c891b");
}

#[test]
fn test_csel_split_memory_read() {
let (mut asm, mut cb) = setup_asm();

let left = Opnd::Mem(Mem { base: MemBase::Stack { stack_idx: 0, num_bits: 64 }, disp: 0, num_bits: 64 });
let right = Opnd::Mem(Mem { base: MemBase::Stack { stack_idx: 1, num_bits: 64 }, disp: 2, num_bits: 64 });
let _ = asm.csel_e(left, right);
asm.compile_with_num_regs(&mut cb, 0);

assert_disasm_snapshot!(cb.disasm(), @"
0x0: mov r10, qword ptr [rbp - 8]
0x4: mov r11, qword ptr [rbp - 0x10]
0x8: mov r11, qword ptr [r11 + 2]
0xc: cmove r11, qword ptr [r10]
0x10: mov qword ptr [rbp - 8], r11
");
assert_snapshot!(cb.hexdump(), @"4c8b55f84c8b5df04d8b5b024d0f441a4c895df8");
}
}
Loading