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
23 changes: 23 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ They are still available on rubygems.org and can be installed with
* error_highlight 0.7.2
* io-console 0.9.2
* 0.8.2 to [v0.9.0][io-console-v0.9.0], [v0.9.1][io-console-v0.9.1], [v0.9.2][io-console-v0.9.2]
* io-wait 999.999.999
* ipaddr 1.2.9
* 1.2.8 to [v1.2.9][ipaddr-v1.2.9]
* json 3.0.2
Expand Down Expand Up @@ -387,6 +388,28 @@ A lot of work has gone into making Ractors more stable, performant, and usable.
* `ObjectSpace.define_finalizer` on another Ractor's object raises
`Ractor::IsolationError`.

* `Ractor#monitor` now sends an Array naming the Ractor and what happened to
it, `[ractor, :exited]` or `[ractor, :aborted]`, where it used to send the
bare Symbol `:exited` or `:aborted`. Several Ractors can then report to one
port and the receiver still knows which one finished. The Array is built for
the receiving Ractor, so watching many Ractors leaves no shareable objects
behind.

r = Ractor.new { :ok }
r.monitor(port = Ractor::Port.new)
port.receive #=> [r, :exited]

One port can therefore watch a whole group, which is all a supervisor
needs:

workers.each { |r| r.monitor port }

until workers.empty?
r, status = port.receive
workers.delete(r)
workers << restart(r) if status == :aborted
end

### M:N thread scheduler

* The scheduler scales with the number of waiters and of Ractors, where it
Expand Down
16 changes: 8 additions & 8 deletions bootstraptest/test_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2710,7 +2710,7 @@ def initialize(a)

## Ractor#monitor

# monitor port returns `:exited` when the monitering Ractor terminated.
# monitor port returns [ractor, :exited] when the monitering Ractor terminated.
assert_equal 'true', %q{
r = Ractor.new do
Ractor.main << :ok1
Expand All @@ -2719,10 +2719,10 @@ def initialize(a)

r.monitor port = Ractor::Port.new
Ractor.receive # :ok1
port.receive == :exited
port.receive == [r, :exited]
}

# monitor port returns `:exited` even if the monitoring Ractor was terminated.
# monitor port returns [ractor, :exited] even if the monitoring Ractor was terminated.
assert_equal 'true', %q{
r = Ractor.new do
:ok
Expand All @@ -2731,7 +2731,7 @@ def initialize(a)
r.join # wait for r's terminateion

r.monitor port = Ractor::Port.new
port.receive == :exited
port.receive == [r, :exited]
}

# monitor returns false if the monitoring Ractor was terminated.
Expand All @@ -2745,7 +2745,7 @@ def initialize(a)
r.monitor Ractor::Port.new
}

# monitor port returns `:aborted` when the monitering Ractor is aborted.
# monitor port returns [ractor, :aborted] when the monitering Ractor is aborted.
assert_equal 'true', %q{
r = Ractor.new do
Ractor.main << :ok1
Expand All @@ -2754,10 +2754,10 @@ def initialize(a)

r.monitor port = Ractor::Port.new
Ractor.receive # :ok1
port.receive == :aborted
port.receive == [r, :aborted]
}

# monitor port returns `:aborted` even if the monitoring Ractor was aborted.
# monitor port returns [ractor, :aborted] even if the monitoring Ractor was aborted.
assert_equal 'true', %q{
r = Ractor.new do
raise 'ok'
Expand All @@ -2770,7 +2770,7 @@ def initialize(a)
end

r.monitor port = Ractor::Port.new
port.receive == :aborted
port.receive == [r, :aborted]
}

assert_equal 'ok', %q{
Expand Down
126 changes: 106 additions & 20 deletions ext/erb/escape/escape.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,42 +172,128 @@ find_next_match_neon(search_state *search)
// uint64_t >>= 64 is undefined behaviour
RUBY_ASSERT(trailing_zeros < 64);
search->matches_bitmap >>= trailing_zeros;
search->cstr += trailing_zeros / 4;
search->cstr += trailing_zeros;
RUBY_ASSERT(search->cstr <= search->end);
return true;
}

// This 16-byte lookup table is indexed into by using the
// low nibble of each input byte.
// Note: index 0 is intentionally set to a character that will not match
// the NULL byte.
static const uint8x16_t escape_char_by_low_nibble = {
'\'', 0, '"', 0,
0, 0, '&', '\'',
0, 0, 0, 0,
'<', 0, '>', 0,
};

static inline uint8x16_t
neon_escape_matches(const uint8x16_t bytes)
{
// An example to demonstrate how this works. The goal is to get a uint8x16_t
// with each lane to equal 0xFF if the corresponding byte in 'bytes' needs
// to be escaped, or 0x00 otherwise.
//
// To keep things very simple, I'm going to assume a vector of length 6, in
// reality, the vector would be 16 bytes wide.
//
// Assume the string is: "<br />"
// Converted to integers:
// [0x3c 0x62 0x72 0x20 0x2f 0x3e]
//
// Next, we mask off the top nibble so we are left only with the low nibble
// of each byte. We do this by AND'ing each byte with 0x0F.
//
// The result:
// [0x0c 0x02 0x02 0x00 0x0f 0x0e]
//
// Now, we use these low nibbles as indexes into the
// escape_char_by_low_nibble array and find the full byte
// value we expect to match in the input.
//
// The result:
// [0x3c 0x22 0x22 0x27 0x00 0x3e]
//
// Finally, we compare the bytes we expect with the actual input bytes.
//
// The result:
// [0xFF 0x00 0x00 0x00 0x00 0xFF]
const uint8x16_t low_nibbles = vandq_u8(bytes, vdupq_n_u8(0x0F));
const uint8x16_t looked_up = vqtbl1q_u8(escape_char_by_low_nibble, low_nibbles);
return vceqq_u8(looked_up, bytes);
}

static inline uint64_t
neon_matches_to_bitmap16(const uint8x16_t matches)
{
static const uint8x16_t bit_mask = {
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
};

uint8x16_t folded = vandq_u8(matches, bit_mask);
folded = vpaddq_u8(folded, folded);
folded = vpaddq_u8(folded, folded);
folded = vpaddq_u8(folded, folded);

return vgetq_lane_u16(vreinterpretq_u16_u8(folded), 0);
}

static inline uint64_t
neon_matches_to_bitmap64(const uint8x16_t m0, const uint8x16_t m1, const uint8x16_t m2, const uint8x16_t m3)
{
static const uint8x16_t bit_mask = {
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
};

const uint8x16_t t0 = vandq_u8(m0, bit_mask);
const uint8x16_t t1 = vandq_u8(m1, bit_mask);
const uint8x16_t t2 = vandq_u8(m2, bit_mask);
const uint8x16_t t3 = vandq_u8(m3, bit_mask);

uint8x16_t folded = vpaddq_u8(vpaddq_u8(t0, t1), vpaddq_u8(t2, t3));
folded = vpaddq_u8(folded, folded);

return vgetq_lane_u64(vreinterpretq_u64_u8(folded), 0);
}

static inline bool
find_next_neon(search_state *search)
{
if (search->matches_bitmap) {
return find_next_match_neon(search);
}

const uint8x16_t single_quote = vdupq_n_u8('\'');
const uint8x16_t double_quote = vdupq_n_u8('"');
const uint8x16_t ampersand = vdupq_n_u8('&');
const uint8x16_t lt = vdupq_n_u8('<');
const uint8x16_t gt = vdupq_n_u8('>');
while ((size_t)(search->end - search->cstr) >= sizeof(uint8x16x4_t)) {
const uint8x16_t bytes0 = vld1q_u8(search->cstr + 0);
const uint8x16_t bytes1 = vld1q_u8(search->cstr + 16);
const uint8x16_t bytes2 = vld1q_u8(search->cstr + 32);
const uint8x16_t bytes3 = vld1q_u8(search->cstr + 48);

while ((size_t)(search->end - search->cstr) >= sizeof(uint8x16_t)) {
const uint8x16_t bytes = vld1q_u8(search->cstr);
const uint8x16_t match1 = vceqq_u8(bytes, single_quote);
const uint8x16_t match2 = vceqq_u8(bytes, double_quote);
const uint8x16_t match3 = vceqq_u8(bytes, ampersand);
const uint8x16_t match4 = vceqq_u8(bytes, lt);
const uint8x16_t match5 = vceqq_u8(bytes, gt);
const uint8x16_t m0 = neon_escape_matches(bytes0);
const uint8x16_t m1 = neon_escape_matches(bytes1);
const uint8x16_t m2 = neon_escape_matches(bytes2);
const uint8x16_t m3 = neon_escape_matches(bytes3);

const uint8x16_t mask1 = vorrq_u8(match1, match2);
const uint8x16_t mask2 = vorrq_u8(match3, match4);
const uint8x16_t mask3 = vorrq_u8(mask1, match5);
const uint8x16_t matches = vorrq_u8(mask2, mask3);
const uint64_t bitmap = neon_matches_to_bitmap64(m0, m1, m2, m3);

const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(matches), 4);
const uint64_t bitmap = vget_lane_u64(vreinterpret_u64_u8(res), 0);
if (bitmap) {
search->matches_bitmap = bitmap;
return find_next_match_neon(search);
}

search->cstr += 64;
}

while ((size_t)(search->end - search->cstr) >= sizeof(uint8x16_t)) {
const uint8x16_t bytes = vld1q_u8(search->cstr);
const uint8x16_t matches = neon_escape_matches(bytes);
const uint64_t bitmap = neon_matches_to_bitmap16(matches);

if (bitmap) {
search->matches_bitmap = bitmap & 0x8888888888888888ull;
search->matches_bitmap = bitmap;
return find_next_match_neon(search);
}
search->cstr += sizeof(uint8x16_t);
Expand Down
3 changes: 0 additions & 3 deletions ext/io/wait/depend

This file was deleted.

4 changes: 0 additions & 4 deletions ext/io/wait/extconf.rb

This file was deleted.

20 changes: 5 additions & 15 deletions ext/io/wait/io-wait.gemspec
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
_VERSION = "0.4.0"
_VERSION = "999.999.999"

Gem::Specification.new do |spec|
spec.name = "io-wait"
spec.version = _VERSION
spec.authors = ["Nobu Nakada", "Charles Oliver Nutter"]
spec.email = ["nobu@ruby-lang.org", "headius@headius.com"]

spec.summary = %q{Waits until IO is readable or writable without blocking.}
spec.description = %q{Waits until IO is readable or writable without blocking.}
spec.summary = %q{Deprecated: All functionality ships with Ruby 3.2 and higher.}
spec.description = %q{Deprecated: All functionality ships with Ruby 3.2 and higher.}
spec.homepage = "https://github.com/ruby/io-wait"
spec.licenses = ["Ruby", "BSD-2-Clause"]
spec.required_ruby_version = Gem::Requirement.new(">= 3.2")
spec.required_ruby_version = Gem::Requirement.new(">= 4.1")

spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage

jruby = true if Gem::Platform.new('java') =~ spec.platform or RUBY_ENGINE == 'jruby'
dir, gemspec = File.split(__FILE__)
excludes = [
*%w[:^/.git* :^/Gemfile* :^/Rakefile* :^/bin/ :^/test/ :^/rakelib/ :^*.java],
*(jruby ? %w[:^/ext/io] : %w[:^/ext/java]),
*%w[:^/.git* :^/Gemfile* :^/Rakefile* :^/bin/ :^/test/ :^/rakelib/],
":(exclude,literal,top)#{gemspec}"
]
files = IO.popen(%w[git ls-files -z --] + excludes, chdir: dir, &:read).split("\x0")
Expand All @@ -28,12 +26,4 @@ Gem::Specification.new do |spec|
spec.bindir = "exe"
spec.executables = []
spec.require_paths = ["lib"]

if jruby
spec.platform = 'java'
spec.files << "lib/io/wait.jar"
spec.require_paths += ["ext/java/lib"]
else
spec.extensions = %w[ext/io/wait/extconf.rb]
end
end
23 changes: 0 additions & 23 deletions ext/io/wait/wait.c

This file was deleted.

Loading