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
6 changes: 3 additions & 3 deletions .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['2.6', '2.7', '3.0', '3.3']
ruby-version: ['3.2', '3.3', '3.4', '4.0']

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
bundler-cache: true
- name: Run tests
run: bundle exec rake
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
### master
### 0.7.0 (August 11, 2025)
* Make it compatible with gcc 15 (by [@lemmuh](https://github.com/lemmuh))

### 0.6.0 (December 19, 2024)
* Update libxxhash to 0.8.1
* Drop old Rubies. Support only MRI 3.1+
* Modernize Ruby C API ussage, fix related warnings. (by [@casperisfine](https://github.com/casperisfine) and [@byroot](https://github.com/byroot))

### 0.5.0 (July 28, 2022)
* Mark extension as Ractor-safe (by [@kreynolds](https://github.com/kreynolds))
Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## xxHash [![Build Status](https://travis-ci.org/nashby/xxhash.png?branch=master)](https://travis-ci.org/nashby/xxhash)
## xxHash [![Ruby](https://github.com/nashby/xxhash/actions/workflows/ruby.yml/badge.svg)](https://github.com/nashby/xxhash/actions/workflows/ruby.yml)

Ruby wrapper for [xxHash](https://github.com/Cyan4973/xxHash)

Expand Down Expand Up @@ -37,14 +37,13 @@ XXH64 is also supported: you can use `xxh64`, `xxh64_stream`, `.xxh64_file`.

### Supported Ruby versions

- MRI 2.3+
- rbx-19mode
- MRI 3.1+

Note: It doesn't work on JRuby as it uses C extension.

### Versioning

Version 0.5.0 is equal to [0.6.2](https://github.com/Cyan4973/xxHash/tree/v0.6.2)
Version 0.7.0 is equal to [0.8.1](https://github.com/Cyan4973/xxHash/tree/v0.8.1)

## Contributing

Expand All @@ -56,5 +55,5 @@ Version 0.5.0 is equal to [0.6.2](https://github.com/Cyan4973/xxHash/tree/v0.6.2

### Copyright

Copyright (c) 2022 Vasiliy Ermolovich. See LICENSE.txt for
Copyright (c) 2024 Vasiliy Ermolovich. See LICENSE.txt for
further details.
80 changes: 58 additions & 22 deletions ext/xxhash/xxhash.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,30 @@ VALUE xxhash_xxh32(VALUE mod, VALUE input, VALUE seed)
return ULL2NUM(XXH32(StringValuePtr(input), (size_t)RSTRING_LEN(input), (unsigned int)NUM2ULL(seed)));
}

void xxhash32_streaming_hash_free(xxhash_xxh32_t* storage)
static void xxhash32_streaming_hash_free(void *ptr)
{
xxhash_xxh32_t* storage = (xxhash_xxh32_t*)ptr;
// Digest frees the memory.
XXH32_freeState(storage->state);
xfree(storage);
}

static size_t xxhash32_streaming_hash_memsize(const void *ptr)
{
// Ideally we'd include sizeof(XXH32_state_t) too, but the type is opaque.
return sizeof(xxhash_xxh32_t);
}

static const rb_data_type_t xxhash_xxh32_type = {
.wrap_struct_name = "xxhash/xxhash32_streaming_hash",
.function = {
.dmark = NULL,
.dfree = xxhash32_streaming_hash_free,
.dsize = xxhash32_streaming_hash_memsize,
},
.flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY
};

VALUE xxhash32_streaming_hash_new(VALUE klass, VALUE seed)
{
XXH_errorcode code;
Expand All @@ -86,14 +103,14 @@ VALUE xxhash32_streaming_hash_new(VALUE klass, VALUE seed)
rb_raise(rb_eRuntimeError, "Error during reset.");
return Qnil;
}
return Data_Wrap_Struct(klass, 0, xxhash32_streaming_hash_free, storage);
return TypedData_Wrap_Struct(klass, &xxhash_xxh32_type, storage);
}

VALUE xxhash32_streaming_hash_reset(VALUE self)
{
XXH_errorcode code;
xxhash_xxh32_t* storage;
Data_Get_Struct(self, xxhash_xxh32_t, storage);
TypedData_Get_Struct(self, xxhash_xxh32_t, &xxhash_xxh32_type, storage);

code = XXH32_reset(storage->state, storage->seed);
if(code != XXH_OK) {
Expand All @@ -107,7 +124,7 @@ VALUE xxhash32_streaming_hash_update(VALUE self, VALUE data)
{
XXH_errorcode code;
xxhash_xxh32_t* storage;
Data_Get_Struct(self, xxhash_xxh32_t, storage);
TypedData_Get_Struct(self, xxhash_xxh32_t, &xxhash_xxh32_type, storage);

code = XXH32_update(storage->state, StringValuePtr(data), (size_t)RSTRING_LEN(data));
if(code != XXH_OK) {
Expand All @@ -119,7 +136,7 @@ VALUE xxhash32_streaming_hash_update(VALUE self, VALUE data)
VALUE xxhash32_streaming_hash_digest(VALUE self)
{
xxhash_xxh32_t* storage;
Data_Get_Struct(self, xxhash_xxh32_t, storage);
TypedData_Get_Struct(self, xxhash_xxh32_t, &xxhash_xxh32_type, storage);

// Do not free memory now.
return ULL2NUM(XXH32_digest(storage->state));
Expand All @@ -130,13 +147,30 @@ VALUE xxhash_xxh64(VALUE mod, VALUE input, VALUE seed)
return ULL2NUM(XXH64(StringValuePtr(input), (size_t)RSTRING_LEN(input), (unsigned int)NUM2ULL(seed)));
}

void xxhash64_streaming_hash_free(xxhash_xxh64_t* storage)
static void xxhash64_streaming_hash_free(void *ptr)
{
xxhash_xxh64_t* storage = (xxhash_xxh64_t*)ptr;
// Digest frees the memory.
XXH64_freeState(storage->state);
xfree(storage);
}

static size_t xxhash64_streaming_hash_memsize(const void *ptr)
{
// Ideally we'd include sizeof(XXH64_state_t) too, but the type is opaque.
return sizeof(xxhash_xxh64_t);
}

static const rb_data_type_t xxhash_xxh64_type = {
.wrap_struct_name = "xxhash/xxhash64_streaming_hash",
.function = {
.dmark = NULL,
.dfree = xxhash64_streaming_hash_free,
.dsize = xxhash64_streaming_hash_memsize,
},
.flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY
};

VALUE xxhash64_streaming_hash_new(VALUE klass, VALUE seed)
{
XXH_errorcode code;
Expand All @@ -150,14 +184,14 @@ VALUE xxhash64_streaming_hash_new(VALUE klass, VALUE seed)
rb_raise(rb_eRuntimeError, "Error during reset.");
return Qnil;
}
return Data_Wrap_Struct(klass, 0, xxhash64_streaming_hash_free, storage);
return TypedData_Wrap_Struct(klass, &xxhash_xxh64_type, storage);
}

VALUE xxhash64_streaming_hash_reset(VALUE self)
{
XXH_errorcode code;
xxhash_xxh64_t* storage;
Data_Get_Struct(self, xxhash_xxh64_t, storage);
TypedData_Get_Struct(self, xxhash_xxh64_t, &xxhash_xxh64_type, storage);

code = XXH64_reset(storage->state, storage->seed);
if(code != XXH_OK) {
Expand All @@ -170,7 +204,7 @@ VALUE xxhash64_streaming_hash_update(VALUE self, VALUE data)
{
XXH_errorcode code;
xxhash_xxh64_t* storage;
Data_Get_Struct(self, xxhash_xxh64_t, storage);
TypedData_Get_Struct(self, xxhash_xxh64_t, &xxhash_xxh64_type, storage);

code = XXH64_update(storage->state, StringValuePtr(data), (size_t)RSTRING_LEN(data));
if(code != XXH_OK) {
Expand All @@ -182,7 +216,7 @@ VALUE xxhash64_streaming_hash_update(VALUE self, VALUE data)
VALUE xxhash64_streaming_hash_digest(VALUE self)
{
xxhash_xxh64_t* storage;
Data_Get_Struct(self, xxhash_xxh64_t, storage);
TypedData_Get_Struct(self, xxhash_xxh64_t, &xxhash_xxh64_type, storage);

// Do not free memory now.
return ULL2NUM(XXH64_digest(storage->state));
Expand All @@ -203,22 +237,24 @@ void Init_xxhash(void)
mXXhash = rb_define_module("XXhash");
mInternal = rb_define_module_under(mXXhash, "XXhashInternal");

rb_define_singleton_method(mInternal, "xxh32", (ruby_method*) &xxhash_xxh32, 2);
rb_define_singleton_method(mInternal, "xxh32_file", (ruby_method*) &xxhash_xxh32_file, 2);
rb_define_singleton_method(mInternal, "xxh64", (ruby_method*) &xxhash_xxh64, 2);
rb_define_singleton_method(mInternal, "xxh64_file", (ruby_method*) &xxhash_xxh64_file, 2);
rb_define_singleton_method(mInternal, "xxh32", xxhash_xxh32, 2);
rb_define_singleton_method(mInternal, "xxh32_file", xxhash_xxh32_file, 2);
rb_define_singleton_method(mInternal, "xxh64", xxhash_xxh64, 2);
rb_define_singleton_method(mInternal, "xxh64_file", xxhash_xxh64_file, 2);

cStreamingHash = rb_define_class_under(mInternal, "StreamingHash32", rb_cObject);
rb_undef_alloc_func(cStreamingHash);

rb_define_singleton_method(cStreamingHash, "new", (ruby_method*) &xxhash32_streaming_hash_new, 1);
rb_define_method(cStreamingHash, "update", (ruby_method*) &xxhash32_streaming_hash_update, 1);
rb_define_method(cStreamingHash, "digest", (ruby_method*) &xxhash32_streaming_hash_digest, 0);
rb_define_method(cStreamingHash, "reset", (ruby_method*) &xxhash32_streaming_hash_reset, 0);
rb_define_singleton_method(cStreamingHash, "new", xxhash32_streaming_hash_new, 1);
rb_define_method(cStreamingHash, "update", xxhash32_streaming_hash_update, 1);
rb_define_method(cStreamingHash, "digest", xxhash32_streaming_hash_digest, 0);
rb_define_method(cStreamingHash, "reset", xxhash32_streaming_hash_reset, 0);

cStreamingHash64 = rb_define_class_under(mInternal, "StreamingHash64", rb_cObject);
rb_undef_alloc_func(cStreamingHash64);

rb_define_singleton_method(cStreamingHash64, "new", (ruby_method*) &xxhash64_streaming_hash_new, 1);
rb_define_method(cStreamingHash64, "update", (ruby_method*) &xxhash64_streaming_hash_update, 1);
rb_define_method(cStreamingHash64, "digest", (ruby_method*) &xxhash64_streaming_hash_digest, 0);
rb_define_method(cStreamingHash64, "reset", (ruby_method*) &xxhash64_streaming_hash_reset, 0);
rb_define_singleton_method(cStreamingHash64, "new", xxhash64_streaming_hash_new, 1);
rb_define_method(cStreamingHash64, "update", xxhash64_streaming_hash_update, 1);
rb_define_method(cStreamingHash64, "digest", xxhash64_streaming_hash_digest, 0);
rb_define_method(cStreamingHash64, "reset", xxhash64_streaming_hash_reset, 0);
}
2 changes: 0 additions & 2 deletions ext/xxhash/xxhash.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@ typedef VALUE (ruby_method)();

VALUE xxhash_xxh32(VALUE mod, VALUE input, VALUE seed);
VALUE xxhash_xxh32_file(VALUE mod, VALUE filename, VALUE seed);
void xxhash32_streaming_hash_free(xxhash_xxh32_t* state);
VALUE xxhash32_streaming_hash_new(VALUE klass, VALUE seed);
VALUE xxhash32_streaming_hash_update(VALUE self, VALUE data);
VALUE xxhash32_streaming_hash_reset(VALUE self);
VALUE xxhash32_streaming_hash_digest(VALUE self);
VALUE xxhash_xxh64(VALUE mod, VALUE input, VALUE seed);
VALUE xxhash_xxh64_file(VALUE mod, VALUE filename, VALUE seed);
void xxhash64_streaming_hash_free(xxhash_xxh64_t* state);
VALUE xxhash64_streaming_hash_new(VALUE klass, VALUE seed);
VALUE xxhash64_streaming_hash_update(VALUE self, VALUE data);
VALUE xxhash64_streaming_hash_reset(VALUE self);
Expand Down
2 changes: 1 addition & 1 deletion lib/xxhash/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module XXhash
VERSION = "0.5.0"
VERSION = "0.7.0"
end
24 changes: 18 additions & 6 deletions test/xxhash_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,26 @@

describe XXhash do
it 'is marked as ractor-safe' do
skip("Ractorrs are not supported in this version of ruby(#{RUBY_VERSION})") unless defined?(Ractor)
skip("Ractors are not supported in this version of ruby(#{RUBY_VERSION})") unless defined?(Ractor)

ractor = Ractor.new do
Ractor.yield XXhash.xxh32(Ractor.receive)
end
if defined?(Ractor::Port)
ractor = Ractor.new do
msg, reply_port = Ractor.receive
reply_port << XXhash.xxh32(msg)
end

reply = Ractor::Port.new
ractor << ["test", reply]

ractor.send('test')
assert_equal ractor.take, XXhash.xxh32('test')
assert_equal reply.receive, XXhash.xxh32("test")
else
ractor = Ractor.new do
Ractor.yield XXhash.xxh32(Ractor.receive)
end

ractor.send('test')
assert_equal ractor.take, XXhash.xxh32('test')
end
end

it 'returns 32-bit hash' do
Expand Down