diff --git a/cont.c b/cont.c
index c4d57d92ba1a0d..9c29d509290f1c 100644
--- a/cont.c
+++ b/cont.c
@@ -175,6 +175,12 @@ struct fiber_pool_allocation {
struct fiber_pool_allocation * next;
};
+#if VM_CHECK_MODE > 0 && defined(HAVE_PTHREAD_H)
+#define ASSERT_FIBER_POOL_LOCK_OWNER_P (true)
+#else
+#define ASSERT_FIBER_POOL_LOCK_OWNER_P (false)
+#endif
+
// A fiber pool manages vacant stacks to reduce the overhead of creating fibers.
struct fiber_pool {
// A singly-linked list of allocations which contain 1 or more stacks each.
@@ -206,6 +212,16 @@ struct fiber_pool {
// The amount to allocate for the vm_stack.
size_t vm_stack_size;
+
+ // Pools are independent so each one has its own lock.
+ rb_nativethread_lock_t lock;
+
+#if ASSERT_FIBER_POOL_LOCK_OWNER_P
+ pthread_t lock_owner;
+#endif
+
+ // Links all live pools together so fork can reinitialize their locks.
+ struct ccan_list_node list_node;
};
// Continuation contexts used by JITs
@@ -287,13 +303,118 @@ rb_free_shared_fiber_pool(void)
struct fiber_pool_allocation *allocations = shared_fiber_pool.allocations;
while (allocations) {
struct fiber_pool_allocation *next = allocations->next;
- SIZED_FREE(allocations);
+ ruby_mimfree(allocations);
allocations = next;
}
}
static ID fiber_initialize_keywords[3] = {0};
+static CCAN_LIST_HEAD(fiber_pool_list);
+static rb_nativethread_lock_t fiber_pool_list_lock;
+
+#if ASSERT_FIBER_POOL_LOCK_OWNER_P
+static inline bool
+fiber_pool_locked_p(const struct fiber_pool * fiber_pool)
+{
+ return pthread_equal(pthread_self(), fiber_pool->lock_owner);
+}
+
+static inline void
+ASSERT_fiber_pool_locked(const struct fiber_pool * fiber_pool)
+{
+#if VM_CHECK_MODE == 0
+ if (!rb_multi_ractor_p()) {
+ return;
+ }
+#endif
+ VM_ASSERT(fiber_pool_locked_p(fiber_pool));
+}
+
+static inline void
+ASSERT_fiber_pool_unlocked(const struct fiber_pool * fiber_pool)
+{
+#if VM_CHECK_MODE == 0
+ if (!rb_multi_ractor_p()) {
+ return;
+ }
+#endif
+ VM_ASSERT(!fiber_pool_locked_p(fiber_pool));
+}
+#else
+#define ASSERT_fiber_pool_locked(fiber_pool) (void)0
+#define ASSERT_fiber_pool_unlocked(fiber_pool) (void)0
+#endif
+
+static inline void
+fiber_pool_lock(struct fiber_pool * fiber_pool)
+{
+#if VM_CHECK_MODE == 0
+ // Locking isn't necessary when there's only 1 Ractor
+ if (!rb_multi_ractor_p()) return;
+#endif
+ ASSERT_fiber_pool_unlocked(fiber_pool);
+ rb_native_mutex_lock(&fiber_pool->lock);
+#if ASSERT_FIBER_POOL_LOCK_OWNER_P
+ fiber_pool->lock_owner = pthread_self();
+#endif
+}
+
+static inline void
+fiber_pool_unlock(struct fiber_pool * fiber_pool)
+{
+#if VM_CHECK_MODE == 0
+ if (!rb_multi_ractor_p()) return;
+#endif
+ ASSERT_fiber_pool_locked(fiber_pool);
+#if ASSERT_FIBER_POOL_LOCK_OWNER_P
+ fiber_pool->lock_owner = 0;
+#endif
+ rb_native_mutex_unlock(&fiber_pool->lock);
+}
+
+static void
+fiber_pool_lock_initialize(struct fiber_pool * fiber_pool)
+{
+ rb_native_mutex_initialize(&fiber_pool->lock);
+#if ASSERT_FIBER_POOL_LOCK_OWNER_P
+ fiber_pool->lock_owner = 0;
+#endif
+}
+
+static void
+fiber_pool_list_add(struct fiber_pool * fiber_pool)
+{
+ rb_native_mutex_lock(&fiber_pool_list_lock);
+ {
+ ccan_list_add(&fiber_pool_list, &fiber_pool->list_node);
+ }
+ rb_native_mutex_unlock(&fiber_pool_list_lock);
+}
+
+#ifdef RB_EXPERIMENTAL_FIBER_POOL
+static void
+fiber_pool_list_remove(struct fiber_pool * fiber_pool)
+{
+ rb_native_mutex_lock(&fiber_pool_list_lock);
+ {
+ ccan_list_del(&fiber_pool->list_node);
+ }
+ rb_native_mutex_unlock(&fiber_pool_list_lock);
+}
+#endif
+
+void
+rb_fiber_pool_lock_atfork(void)
+{
+ rb_native_mutex_initialize(&fiber_pool_list_lock);
+
+ struct fiber_pool *fiber_pool = NULL;
+ ccan_list_for_each(&fiber_pool_list, fiber_pool, list_node) {
+ fiber_pool_lock_initialize(fiber_pool);
+ }
+}
+
/*
* FreeBSD require a first (i.e. addr) argument of mmap(2) is not NULL
* if MAP_STACK is passed.
@@ -397,6 +518,7 @@ fiber_pool_vacancy_reset(struct fiber_pool_vacancy * vacancy)
inline static struct fiber_pool_vacancy *
fiber_pool_vacancy_push(struct fiber_pool_vacancy * vacancy, struct fiber_pool_vacancy * head)
{
+ ASSERT_fiber_pool_locked(vacancy->stack.pool);
vacancy->next = head;
#ifdef FIBER_POOL_ALLOCATION_FREE
@@ -429,6 +551,7 @@ fiber_pool_vacancy_remove(struct fiber_pool_vacancy * vacancy)
inline static struct fiber_pool_vacancy *
fiber_pool_vacancy_pop(struct fiber_pool * pool)
{
+ ASSERT_fiber_pool_locked(pool);
struct fiber_pool_vacancy * vacancy = pool->vacancies;
if (vacancy) {
@@ -441,6 +564,7 @@ fiber_pool_vacancy_pop(struct fiber_pool * pool)
inline static struct fiber_pool_vacancy *
fiber_pool_vacancy_pop(struct fiber_pool * pool)
{
+ ASSERT_fiber_pool_locked(pool);
struct fiber_pool_vacancy * vacancy = pool->vacancies;
if (vacancy) {
@@ -516,20 +640,26 @@ fiber_pool_allocate_memory(size_t * count, size_t stride)
// Given an existing fiber pool, expand it by the specified number of stacks.
//
// @param count the maximum number of stacks to allocate.
+// @param needs_lock whether this function should acquire the fiber pool lock
+// @param vacancy_out the out param for the next vacancy, if set to non-NULL
// @return the new allocation on success, or NULL on failure with errno set.
-// @raise NoMemoryError if the struct or memory allocation fails.
//
-// Call from fiber_pool_stack_acquire_expand with VM lock held, or from
-// fiber_pool_initialize before the pool is shared across threads.
// @sa fiber_pool_allocation_free
static struct fiber_pool_allocation *
-fiber_pool_expand(struct fiber_pool * fiber_pool, size_t count)
+fiber_pool_expand(struct fiber_pool * fiber_pool, size_t count, bool needs_lock, struct fiber_pool_vacancy **vacancy_out)
{
if (count == 0) {
errno = EAGAIN;
return NULL;
}
+ struct fiber_pool_allocation * allocation = ruby_mimmalloc(sizeof(struct fiber_pool_allocation));
+ if (RB_UNLIKELY(!allocation)) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ if (needs_lock) fiber_pool_lock(fiber_pool);
+
STACK_GROW_DIR_DETECTION;
size_t size = fiber_pool->size;
@@ -538,6 +668,8 @@ fiber_pool_expand(struct fiber_pool * fiber_pool, size_t count)
// If the maximum number of stacks is set, and we have reached it, return NULL.
if (fiber_pool->maximum_count > 0) {
if (fiber_pool->count >= fiber_pool->maximum_count) {
+ if (needs_lock) fiber_pool_unlock(fiber_pool);
+ ruby_mimfree(allocation);
errno = EAGAIN;
return NULL;
}
@@ -547,16 +679,15 @@ fiber_pool_expand(struct fiber_pool * fiber_pool, size_t count)
}
}
- // Allocate metadata before mmap: ruby_xmalloc (RB_ALLOC) raises on failure and
- // must not run after base is mapped, or the region would leak.
- struct fiber_pool_allocation * allocation = RB_ALLOC(struct fiber_pool_allocation);
-
// Allocate the memory required for the stacks:
void * base = fiber_pool_allocate_memory(&count, stride);
if (base == NULL) {
- if (!errno) errno = ENOMEM;
- ruby_xfree(allocation);
+ int saved_errno = errno;
+ if (!saved_errno) saved_errno = ENOMEM;
+ if (needs_lock) fiber_pool_unlock(fiber_pool);
+ ruby_mimfree(allocation);
+ errno = saved_errno;
return NULL;
}
@@ -586,8 +717,9 @@ fiber_pool_expand(struct fiber_pool * fiber_pool, size_t count)
if (!VirtualProtect(page, RB_PAGE_SIZE, PAGE_READWRITE | PAGE_GUARD, &old_protect)) {
int error = rb_w32_map_errno(GetLastError());
+ if (needs_lock) fiber_pool_unlock(fiber_pool);
VirtualFree(allocation->base, 0, MEM_RELEASE);
- ruby_xfree(allocation);
+ ruby_mimfree(allocation);
errno = error;
return NULL;
}
@@ -597,9 +729,10 @@ fiber_pool_expand(struct fiber_pool * fiber_pool, size_t count)
#else
if (mprotect(page, RB_PAGE_SIZE, PROT_NONE) < 0) {
int error = errno;
+ if (needs_lock) fiber_pool_unlock(fiber_pool);
if (!error) error = ENOMEM;
munmap(allocation->base, count*stride);
- ruby_xfree(allocation);
+ ruby_mimfree(allocation);
errno = error;
return NULL;
}
@@ -631,9 +764,29 @@ fiber_pool_expand(struct fiber_pool * fiber_pool, size_t count)
fiber_pool->vacancies = vacancies;
fiber_pool->count += count;
+ if (vacancy_out) {
+ *vacancy_out = fiber_pool_vacancy_pop(fiber_pool);
+ }
+ if (needs_lock) fiber_pool_unlock(fiber_pool);
+
return allocation;
}
+static struct fiber_pool_vacancy *
+fiber_pool_expand_and_pop(struct fiber_pool * fiber_pool, size_t count)
+{
+ ASSERT_fiber_pool_locked(fiber_pool);
+ struct fiber_pool_vacancy *vacancy_out = NULL;
+ struct fiber_pool_allocation *allocation = fiber_pool_expand(fiber_pool, count, false, &vacancy_out);
+ if (allocation) {
+ VM_ASSERT(vacancy_out);
+ return vacancy_out;
+ }
+ else {
+ return NULL;
+ }
+}
+
// Initialize the specified fiber pool with the given number of stacks.
// @param vm_stack_size The size of the vm stack to allocate.
static void
@@ -651,8 +804,11 @@ fiber_pool_initialize(struct fiber_pool * fiber_pool, size_t size, size_t minimu
fiber_pool->used = 0;
fiber_pool->vm_stack_size = vm_stack_size;
+ fiber_pool_lock_initialize(fiber_pool);
+ fiber_pool_list_add(fiber_pool);
+
if (fiber_pool->minimum_count > 0) {
- if (RB_UNLIKELY(!fiber_pool_expand(fiber_pool, fiber_pool->minimum_count))) {
+ if (RB_UNLIKELY(!fiber_pool_expand(fiber_pool, fiber_pool->minimum_count, true, NULL))) {
rb_raise(rb_eFiberError, "can't allocate initial fiber stacks (%"PRIuSIZE" x %"PRIuSIZE" bytes): %s", fiber_pool->minimum_count, fiber_pool->size, strerror(errno));
}
}
@@ -699,7 +855,7 @@ fiber_pool_allocation_free(struct fiber_pool_allocation * allocation)
allocation->pool->count -= allocation->count;
- SIZED_FREE(allocation);
+ ruby_mimfree(allocation);
}
#endif
@@ -707,6 +863,7 @@ fiber_pool_allocation_free(struct fiber_pool_allocation * allocation)
static size_t
fiber_pool_stack_expand_count(const struct fiber_pool *pool)
{
+ ASSERT_fiber_pool_locked(pool);
const size_t maximum_allocations = FIBER_POOL_MAXIMUM_ALLOCATIONS;
const size_t minimum_count = FIBER_POOL_MINIMUM_COUNT;
@@ -732,24 +889,29 @@ fiber_pool_stack_expand_count(const struct fiber_pool *pool)
return count;
}
-// When the vacancy list is empty, grow the pool (and run GC only if mmap fails). Caller holds the VM lock.
+// When the vacancy list is empty, grow the pool (and run GC only if mmap fails).
// Returns NULL if expansion failed after GC + retry; errno is set. Otherwise returns a vacancy.
static struct fiber_pool_vacancy *
fiber_pool_stack_acquire_expand(struct fiber_pool *fiber_pool)
{
+ ASSERT_fiber_pool_locked(fiber_pool);
size_t count = fiber_pool_stack_expand_count(fiber_pool);
if (DEBUG_ACQUIRE) fprintf(stderr, "fiber_pool_stack_acquire: expanding fiber pool by %"PRIuSIZE" stacks\n", count);
struct fiber_pool_vacancy *vacancy = NULL;
- if (RB_LIKELY(fiber_pool_expand(fiber_pool, count))) {
- return fiber_pool_vacancy_pop(fiber_pool);
+ if (RB_LIKELY((vacancy = fiber_pool_expand_and_pop(fiber_pool, count)))) {
+ return vacancy;
}
else {
if (DEBUG_ACQUIRE) fprintf(stderr, "fiber_pool_stack_acquire: expand failed (%s), collecting garbage\n", strerror(errno));
- rb_gc();
+ fiber_pool_unlock(fiber_pool);
+ {
+ rb_gc();
+ }
+ fiber_pool_lock(fiber_pool);
// After running GC, the vacancy list may have some stacks:
vacancy = fiber_pool_vacancy_pop(fiber_pool);
@@ -761,11 +923,11 @@ fiber_pool_stack_acquire_expand(struct fiber_pool *fiber_pool)
count = fiber_pool_stack_expand_count(fiber_pool);
// Try to expand the fiber pool again:
- if (RB_LIKELY(fiber_pool_expand(fiber_pool, count))) {
- return fiber_pool_vacancy_pop(fiber_pool);
+ if (RB_LIKELY((vacancy = fiber_pool_expand_and_pop(fiber_pool, count)))) {
+ return vacancy;
}
else {
- // Okay, we really failed to acquire a stack. Give up and return NULL with errno set:
+ // Okay, we really failed to acquire a stack. Give up and return NULL with errno set
return NULL;
}
}
@@ -777,8 +939,7 @@ fiber_pool_stack_acquire(struct fiber_pool * fiber_pool)
{
struct fiber_pool_vacancy * vacancy;
- unsigned int lev;
- RB_VM_LOCK_ENTER_LEV(&lev);
+ fiber_pool_lock(fiber_pool);
{
// Fast path: try to acquire a stack from the vacancy list:
vacancy = fiber_pool_vacancy_pop(fiber_pool);
@@ -791,7 +952,7 @@ fiber_pool_stack_acquire(struct fiber_pool * fiber_pool)
// If expansion failed, raise an error:
if (RB_UNLIKELY(!vacancy)) {
- RB_VM_LOCK_LEAVE_LEV(&lev);
+ fiber_pool_unlock(fiber_pool);
rb_raise(rb_eFiberError, "can't allocate fiber stack: %s", strerror(errno));
}
}
@@ -812,7 +973,7 @@ fiber_pool_stack_acquire(struct fiber_pool * fiber_pool)
fiber_pool_stack_reset(&vacancy->stack);
}
- RB_VM_LOCK_LEAVE_LEV(&lev);
+ fiber_pool_unlock(fiber_pool);
return vacancy->stack;
}
@@ -852,24 +1013,12 @@ fiber_pool_stack_free(struct fiber_pool_stack * stack)
static void
fiber_pool_stack_release(struct fiber_pool_stack * stack)
{
+ ASSERT_fiber_pool_locked(stack->pool);
struct fiber_pool * pool = stack->pool;
struct fiber_pool_vacancy * vacancy = fiber_pool_vacancy_pointer(stack->base, stack->size);
if (DEBUG) fprintf(stderr, "fiber_pool_stack_release: %p used=%"PRIuSIZE"\n", stack->base, stack->pool->used);
- /* Serialize pool access against other Ractors' acquires: a per-Ractor GC sweep can
- * free a fiber without the VM lock. Releases are rare, so take it NO_BARRIER,
- * never joining a forming global barrier.
- *
- * Two callers must not take it. VM destruct's free-at-exit walk is single-threaded
- * and its thread structs are already freed, so looking the current Ractor up would
- * read freed memory. A single objspace impl (mmtk) frees on its own GC thread,
- * which has no execution context to look one up from at all -- and it stops the
- * world, so nothing races us there. */
- unsigned int lev = 0;
- const bool lock_here = !ruby_vm_during_cleanup && rb_current_execution_context(false) != NULL;
- if (lock_here) RB_VM_LOCK_ENTER_LEV_NB(&lev);
-
// Copy the stack details into the vacancy area:
vacancy->stack = *stack;
// After this point, be careful about updating/using state in stack, since it's copied to the vacancy area.
@@ -900,8 +1049,6 @@ fiber_pool_stack_release(struct fiber_pool_stack * stack)
fiber_pool_stack_free(&vacancy->stack);
}
#endif
-
- if (lock_here) RB_VM_LOCK_LEAVE_LEV_NB(&lev);
}
static inline void
@@ -1006,7 +1153,14 @@ fiber_stack_release(rb_fiber_t * fiber)
// Return the stack back to the fiber pool if it wasn't already:
if (fiber->stack.base) {
- fiber_pool_stack_release(&fiber->stack);
+ struct fiber_pool * fiber_pool = fiber->stack.pool;
+
+ fiber_pool_lock(fiber_pool);
+ {
+ fiber_pool_stack_release(&fiber->stack);
+ }
+ fiber_pool_unlock(fiber_pool);
+
fiber->stack.base = NULL;
}
@@ -1014,15 +1168,6 @@ fiber_stack_release(rb_fiber_t * fiber)
rb_ec_clear_vm_stack(ec);
}
-static void
-fiber_stack_release_locked(rb_fiber_t *fiber)
-{
- /* Called from GC finalization. With per-Ractor objspaces the sweep runs with
- * no barrier and no VM lock, so the side that returns stacks to the pool
- * (fiber_pool_stack_release) takes the lock. Do not assert the VM lock here. */
- fiber_stack_release(fiber);
-}
-
static const char *
fiber_status_name(enum fiber_status s)
{
@@ -1185,7 +1330,7 @@ cont_free(void *ptr)
else {
rb_fiber_t *fiber = (rb_fiber_t*)cont;
coroutine_destroy(&fiber->context);
- fiber_stack_release_locked(fiber);
+ fiber_stack_release(fiber);
}
SIZED_FREE_N(cont->saved_vm_stack.ptr, cont->saved_vm_stack.size);
@@ -1369,7 +1514,7 @@ cont_handle_weak_references(void *ptr)
static const rb_data_type_t rb_cont_data_type = {
"continuation",
{cont_mark, cont_free, cont_memsize, cont_compact, cont_handle_weak_references},
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
+ 0, 0, RUBY_TYPED_THREAD_SAFE_FREE
};
static inline void
@@ -2130,7 +2275,7 @@ fiber_handle_weak_references(void *ptr)
static const rb_data_type_t rb_fiber_data_type = {
"fiber",
{fiber_mark, fiber_free, fiber_memsize, fiber_compact, fiber_handle_weak_references},
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
+ 0, 0, RUBY_TYPED_THREAD_SAFE_FREE
};
static VALUE fiber_alloc_in(VALUE klass, void *objspace);
@@ -2908,9 +3053,7 @@ fiber_switch(rb_fiber_t *fiber, int argc, const VALUE *argv, int kw_splat, rb_fi
// We cannot free the stack until the pthread is joined:
#ifndef COROUTINE_PTHREAD_CONTEXT
if (FIBER_TERMINATED_P(fiber)) {
- RB_VM_LOCKING() {
- fiber_stack_release(fiber);
- }
+ fiber_stack_release(fiber);
}
#endif
RB_GC_GUARD(fiber_value);
@@ -3537,7 +3680,9 @@ fiber_pool_free(void *ptr)
struct fiber_pool * fiber_pool = ptr;
RUBY_FREE_ENTER("fiber_pool");
+ fiber_pool_list_remove(fiber_pool);
fiber_pool_allocation_free(fiber_pool->allocations);
+ rb_native_mutex_destroy(&fiber_pool->lock);
SIZED_FREE(fiber_pool);
RUBY_FREE_LEAVE("fiber_pool");
@@ -3557,7 +3702,7 @@ fiber_pool_memsize(const void *ptr)
static const rb_data_type_t FiberPoolDataType = {
"fiber_pool",
{NULL, fiber_pool_free, fiber_pool_memsize,},
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
+ 0, 0, RUBY_TYPED_THREAD_SAFE_FREE
};
static VALUE
@@ -3670,6 +3815,8 @@ Init_Cont(void)
rb_eFiberError = rb_define_class("FiberError", rb_eStandardError);
+ rb_native_mutex_initialize(&fiber_pool_list_lock);
+
size_t minimum_count = shared_fiber_pool_minimum_count();
size_t maximum_count = shared_fiber_pool_maximum_count();
fiber_pool_initialize(&shared_fiber_pool, stack_size, minimum_count, maximum_count, vm_stack_size);
diff --git a/file.c b/file.c
index e12f26e2a918f7..2c10e223b8c9b3 100644
--- a/file.c
+++ b/file.c
@@ -931,12 +931,32 @@ rb_stat_rdev_minor(VALUE self)
}
/*
+ * :markup: markdown
+ *
* call-seq:
- * stat.size -> integer
+ * size -> integer
*
- * Returns the size of stat in bytes.
+ * Returns the size of `self` in bytes:
+ *
+ * ```ruby
+ * File.stat('doc/maintainers.md').size # => 14900 # Regular file.
+ * File.stat('doc/syntax/').size # => 4096 # Directory.
+ * # When the file size changes.
+ * path = '/tmp/t.tmp'
+ * file = File.new(path, 'w+')
+ * file.write('foo')
+ * stat = File.stat(path) # Take snapshot.
+ * stat.size # => 3
+ * file.write('bar') # Change file size.
+ * file.size # => 6
+ * stat.size # => 3 # Snapshot unchanged.
+ * stat = File.stat(path) # Fresh snapshot.
+ * stat.size # => 6 # Shapshot different.
+ * # Clean up.
+ * file.close
+ * File.delete(path)
+ * ```
*
- * File.stat("testfile").size #=> 66
*/
static VALUE
@@ -2493,12 +2513,28 @@ check3rdbyte(VALUE fname, int mode)
#endif
/*
+ * :markup: markdown
+ *
* call-seq:
- * File.setuid?(file_name) -> true or false
+ * File.setuid?(object) -> true or false
*
- * Returns +true+ if the named file has the setuid bit set.
+ * Returns whether the setuid bit is set
+ * in the [special bits](rdoc-ref:file/filesystem_modes.md@Special+Bits)
+ * for the given `object`, which may be a path or an IO object:
*
- * _file_name_ can be an IO object.
+ * ```ruby
+ * path = '/tmp/t.tmp'
+ * File.write(path, 'foo')
+ * mode = File.stat(path).mode.to_s(8) # => "100664"
+ * File.setuid?(path) # => false
+ * File.chmod(0o4644, path) # Set the bit.
+ * mode = File.stat(path).mode.to_s(8) # => "104644"
+ * File.setuid?(path) # => true
+ * File.delete(path) # Clean up.
+ * File.setuid?($stdin) # => false
+ * ```
+ *
+ * On Windows, the bit is never set; the method always returns `false`.
*/
static VALUE
@@ -2512,12 +2548,28 @@ rb_file_suid_p(VALUE obj, VALUE fname)
}
/*
+ * :markup: markdown
+ *
* call-seq:
- * File.setgid?(file_name) -> true or false
+ * File.setgid?(object) -> true or false
*
- * Returns +true+ if the named file has the setgid bit set.
+ * Returns whether the setgid bit is set
+ * in the [special bits](rdoc-ref:file/filesystem_modes.md@Special+Bits)
+ * for the given `object`, which may be a path or an IO object:
*
- * _file_name_ can be an IO object.
+ * ```ruby
+ * path = '/tmp/t.tmp'
+ * File.write(path, 'foo')
+ * mode = File.stat(path).mode.to_s(8) # => "100664"
+ * File.setgid?(path) # => false
+ * File.chmod(0o2644, path) # Set the bit.
+ * mode = File.stat(path).mode.to_s(8) # => "102644"
+ * File.setgid?(path) # => true
+ * File.delete(path) # Clean up.
+ * File.setgid?($stdin) # => false
+ * ```
+ *
+ * On Windows, the bit is never set; the method always returns `false`.
*/
static VALUE
@@ -2594,12 +2646,20 @@ rb_file_identical_p(VALUE obj, VALUE fname1, VALUE fname2)
}
/*
+ * :markup: markdown
+ *
* call-seq:
- * File.size(file_name) -> integer
+ * File.size(object) -> integer
*
- * Returns the size of file_name.
+ * Returns the size in bytes of the given `object`,
+ * which may be a path or an IO object:
+ *
+ * ```ruby
+ * File.size('doc/maintainers.md') # => 14900 # Regular file.
+ * File.size('doc/syntax/') # => 4096 # Directory.
+ * File.size($stdin) # => 0 # IO object.
+ * ```
*
- * _file_name_ can be an IO object.
*/
static VALUE
@@ -3047,12 +3107,17 @@ rb_file_size(VALUE file)
}
/*
+ * :markup: markdown
+ *
* call-seq:
- * file.size -> integer
+ * size -> integer
*
- * Returns the size of file in bytes.
+ * Returns the size of `self` in bytes:
*
- * File.new("testfile").size #=> 66
+ * ```ruby
+ * File.new('doc/maintainers.md').size # => 14900 # Regular file.
+ * File.new('doc/syntax/').size # => 4096 # Directory.
+ * ```
*
*/
@@ -7373,14 +7438,31 @@ rb_stat_s(VALUE obj)
}
/*
+ * :markup: markdown
+ *
* call-seq:
- * stat.setuid? -> true or false
+ * setuid? -> true or false
*
- * Returns +true+ if stat has the set-user-id permission bit set,
- * +false+ if it doesn't or if the operating system doesn't support this
- * feature.
+ * Returns whether the setuid bit is set
+ * in the [special bits](rdoc-ref:file/filesystem_modes.md@Special+Bits)
+ * for the entry represented in `self`:
+ *
+ * ```ruby
+ * path = '/tmp/t.tmp'
+ * File.write(path, 'foo')
+ * stat = File.stat(path) # Take snapshot; bit not set.
+ * stat.setuid? # => false
+ * stat.mode.to_s(8) # => "100664"
+ * File.chmod(0o4644, path) # Set the bit; snapshot not updated.
+ * stat.setuid? # => false
+ * stat.mode.to_s(8) # => "100664"
+ * stat = File.stat(path) # Fresh snapshot.
+ * stat.setuid? # => true
+ * stat.mode.to_s(8) # => "104644"
+ * File.delete(path) # Clean up.
+ * ```
*
- * File.stat("/bin/su").setuid? #=> true
+ * On Windows, the bit is never set; the method always returns `false`.
*/
static VALUE
@@ -7393,15 +7475,31 @@ rb_stat_suid(VALUE obj)
}
/*
+ * :markup: markdown
+ *
* call-seq:
- * stat.setgid? -> true or false
+ * setgid? -> true or false
*
- * Returns +true+ if stat has the set-group-id permission bit set,
- * +false+ if it doesn't or if the operating system doesn't support this
- * feature.
+ * Returns whether the setgid bit is set
+ * in the [special bits](rdoc-ref:file/filesystem_modes.md@Special+Bits)
+ * for the entry represented in `self`:
*
- * File.stat("/usr/sbin/lpc").setgid? #=> true
+ * ```ruby
+ * path = '/tmp/t.tmp'
+ * File.write(path, 'foo')
+ * stat = File.stat(path) # Take a snapshot.
+ * stat.setgid? # => false
+ * stat.mode.to_s(8) # => "100664"
+ * File.chmod(0o2644, path) # Set the bit; stat snapshot unchanged.
+ * stat.setgid? # => false
+ * stat.mode.to_s(8) # => "100664"
+ * stat = File.stat(path) # Fresh stat; snapshot changed.
+ * stat.setgid? # => true
+ * stat.mode.to_s(8) # => "102644"
+ * File.delete(path) # Clean up.
+ * ```
*
+ * On Windows, the bit is never set; the method always returns `false`.
*/
static VALUE
diff --git a/internal/cont.h b/internal/cont.h
index f9f200e420379b..9878db639ac46f 100644
--- a/internal/cont.h
+++ b/internal/cont.h
@@ -20,6 +20,8 @@ void ruby_register_rollback_func_for_ensure(VALUE (*ensure_func)(VALUE), VALUE (
/* vm.c */
void rb_free_shared_fiber_pool(void);
+/* thread.c */
+void rb_fiber_pool_lock_atfork(void);
// Copy locals from the current execution to the specified fiber.
VALUE rb_fiber_inherit_storage(struct rb_execution_context_struct *ec, struct rb_fiber_struct *fiber);
diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb
index a9442bd26e9282..192b7cb52e55dd 100644
--- a/lib/prism/ffi.rb
+++ b/lib/prism/ffi.rb
@@ -54,16 +54,18 @@ def self.resolve_type(type, callbacks)
def self.load_exported_functions_from(header, *functions, callbacks)
File.foreach("#{INCLUDE_DIR}/#{header}") do |line|
# We only want to attempt to load exported functions.
- next unless line.start_with?("PRISM_EXPORTED_FUNCTION ")
+ next unless line.include?("PRISM_EXPORTED_FUNCTION ")
# We only want to load the functions that we are interested in.
next unless functions.any? { |function| line.include?(function) }
+ # Strip leading attributes (PRISM_EXPORTED_FUNCTION, PRISM_NODISCARD, etc.)
+ line = line.sub(/\A(PRISM_\w+(?:\([^)]*\))?)+/, "")
# Strip trailing attributes (PRISM_NODISCARD, PRISM_NONNULL(...), etc.)
line = line.sub(/\)(\s+PRISM_\w+(?:\([^)]*\))?)+\s*;/, ");")
# Parse the function declaration.
- unless /^PRISM_EXPORTED_FUNCTION (?.+) (?\w+)\((?.+)\);$/ =~ line
+ unless /^(?.+) (?\w+)\((?.+)\);$/ =~ line
raise "Could not parse #{line}"
end
diff --git a/pathname_builtin.rb b/pathname_builtin.rb
index ad1700b8dc5cd1..0fff9c508e2995 100644
--- a/pathname_builtin.rb
+++ b/pathname_builtin.rb
@@ -2596,17 +2596,16 @@ def readable_real?() FileTest.readable_real?(@path) end
# call-seq:
# setuid? -> true or false
#
- # Returns whether the [setuid bit](https://en.wikipedia.org/wiki/Setuid) is set
- # in the permissions for the entry at the path in `self`:
+ # Returns whether the setuid bit is set
+ # in the [special bits](rdoc-ref:file/filesystem_modes.md@Special+Bits)
+ # for the entry at the path in `self`:
#
# ```ruby
- # # Create a file and get its permissions and setuid? setting.
# pn = Pathname('doc/t.tmp')
# pn.write('foo')
# mode = pn.stat.mode.to_s(8) # => "100664"
# pn.setuid? # => false
- # # Set the bit.
- # pn.chmod(0o4644)
+ # pn.chmod(0o4644) # Set the bit.
# mode = pn.stat.mode.to_s(8) # => "104644"
# pn.setuid? # => true
# pn.delete # Clean up.
@@ -2620,17 +2619,16 @@ def setuid?() FileTest.setuid?(@path) end
# call-seq:
# setgid? -> true or false
#
- # Returns whether the [setgid bit](https://en.wikipedia.org/wiki/Setuid) is set
- # in the permissions for the entry at the path in `self`:
+ # Returns whether the setgid bit is set
+ # in the [special bits](rdoc-ref:file/filesystem_modes.md@Special+Bits)
+ # for the entry at the path in `self`:
#
# ```ruby
- # # Create a file and get its permissions and setgid? setting.
# pn = Pathname('doc/t.tmp')
# pn.write('foo')
# mode = pn.stat.mode.to_s(8) # => "100664"
# pn.setgid? # => false
- # # Set the bit.
- # pn.chmod(0o2644)
+ # pn.chmod(0o2644) # Set the bit.
# mode = pn.stat.mode.to_s(8) # => "102644"
# pn.setgid? # => true
# pn.delete # Clean up.
diff --git a/prism/arena.h b/prism/arena.h
index e1fa8fc6ad2c52..ecd84b9b8f9726 100644
--- a/prism/arena.h
+++ b/prism/arena.h
@@ -25,7 +25,7 @@ typedef struct pm_arena_t pm_arena_t;
* the caller to free the arena using pm_arena_free when it is no longer
* needed.
*/
-PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_arena_t * pm_arena_new(void);
+PRISM_NODISCARD PRISM_EXPORTED_FUNCTION pm_arena_t * pm_arena_new(void);
/**
* Frees both the held memory and the arena itself.
diff --git a/prism/buffer.h b/prism/buffer.h
index 24b572d2c3f555..b54e5ac0d2258c 100644
--- a/prism/buffer.h
+++ b/prism/buffer.h
@@ -24,7 +24,7 @@ typedef struct pm_buffer_t pm_buffer_t;
* @returns A pointer to the initialized buffer. The caller is responsible for
* freeing the buffer with pm_buffer_free.
*/
-PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_buffer_t * pm_buffer_new(void);
+PRISM_NODISCARD PRISM_EXPORTED_FUNCTION pm_buffer_t * pm_buffer_new(void);
/**
* Free both the memory held by the buffer and the buffer itself.
diff --git a/prism/options.h b/prism/options.h
index 71e606bb5d66d1..cf8a430bbcbef6 100644
--- a/prism/options.h
+++ b/prism/options.h
@@ -114,7 +114,7 @@ static const uint8_t PM_OPTIONS_COMMAND_LINE_X = 0x20;
* @returns A new options struct with default values. It is the responsibility
* of the caller to free this struct using pm_options_free().
*/
-PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_options_t * pm_options_new(void);
+PRISM_NODISCARD PRISM_EXPORTED_FUNCTION pm_options_t * pm_options_new(void);
/**
* Free both the held memory of the given options struct and the struct itself.
diff --git a/prism/parser.h b/prism/parser.h
index 2c8c4b3a7acdc0..2c843ecf5ea360 100644
--- a/prism/parser.h
+++ b/prism/parser.h
@@ -33,7 +33,7 @@ typedef struct pm_parser_t pm_parser_t;
* @returns The initialized parser. It is the responsibility of the caller to
* free the parser with `pm_parser_free()`.
*/
-PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_parser_t * pm_parser_new(pm_arena_t *arena, const uint8_t *source, size_t size, const pm_options_t *options) PRISM_NONNULL(1);
+PRISM_NODISCARD PRISM_EXPORTED_FUNCTION pm_parser_t * pm_parser_new(pm_arena_t *arena, const uint8_t *source, size_t size, const pm_options_t *options) PRISM_NONNULL(1);
/**
* Free both the memory held by the given parser and the parser itself.
diff --git a/prism/source.h b/prism/source.h
index 115914392f8ec3..741350bfc7bebe 100644
--- a/prism/source.h
+++ b/prism/source.h
@@ -73,7 +73,7 @@ typedef enum {
* @param length The length of the source data in bytes.
* @returns A new source. Aborts on allocation failure.
*/
-PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_constant_new(const uint8_t *data, size_t length);
+PRISM_NODISCARD PRISM_EXPORTED_FUNCTION pm_source_t * pm_source_constant_new(const uint8_t *data, size_t length);
/**
* Create a new source that wraps existing shared memory. The memory is not
@@ -83,7 +83,7 @@ PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_constant_new(con
* @param length The length of the source data in bytes.
* @returns A new source. Aborts on allocation failure.
*/
-PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_shared_new(const uint8_t *data, size_t length);
+PRISM_NODISCARD PRISM_EXPORTED_FUNCTION pm_source_t * pm_source_shared_new(const uint8_t *data, size_t length);
/**
* Create a new source that owns its memory. The memory will be freed with
@@ -93,7 +93,7 @@ PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_shared_new(const
* @param length The length of the source data in bytes.
* @returns A new source. Aborts on allocation failure.
*/
-PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_owned_new(uint8_t *data, size_t length);
+PRISM_NODISCARD PRISM_EXPORTED_FUNCTION pm_source_t * pm_source_owned_new(uint8_t *data, size_t length);
/**
* Create a new source by reading a file into a heap-allocated buffer.
@@ -102,7 +102,7 @@ PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_owned_new(uint8_
* @param result Out parameter for the result of the initialization.
* @returns A new source, or NULL on error (with result written to out param).
*/
-PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_file_new(const char *filepath, pm_source_init_result_t *result) PRISM_NONNULL(1, 2);
+PRISM_NODISCARD PRISM_EXPORTED_FUNCTION pm_source_t * pm_source_file_new(const char *filepath, pm_source_init_result_t *result) PRISM_NONNULL(1, 2);
/**
* Create a new source by memory-mapping a file. Falls back to file reading on
@@ -117,7 +117,7 @@ PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_file_new(const c
* @param result Out parameter for the result of the initialization.
* @returns A new source, or NULL on error (with result written to out param).
*/
-PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_mapped_new(const char *filepath, int open_flags, pm_source_init_result_t *result) PRISM_NONNULL(1, 3);
+PRISM_NODISCARD PRISM_EXPORTED_FUNCTION pm_source_t * pm_source_mapped_new(const char *filepath, int open_flags, pm_source_init_result_t *result) PRISM_NONNULL(1, 3);
/**
* Create a new source by reading from a stream using the provided callbacks.
@@ -127,7 +127,7 @@ PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_mapped_new(const
* @param feof The function to use to check if the stream is at EOF.
* @returns A new source. Aborts on allocation failure.
*/
-PRISM_EXPORTED_FUNCTION PRISM_NODISCARD pm_source_t * pm_source_stream_new(void *stream, pm_source_stream_fgets_t *fgets, pm_source_stream_feof_t *feof);
+PRISM_NODISCARD PRISM_EXPORTED_FUNCTION pm_source_t * pm_source_stream_new(void *stream, pm_source_stream_fgets_t *fgets, pm_source_stream_feof_t *feof);
/**
* Free the given source and any memory it owns.
diff --git a/struct.c b/struct.c
index 8bc1694700bdc5..1afd1a4ba4cc04 100644
--- a/struct.c
+++ b/struct.c
@@ -711,7 +711,7 @@ num_members(VALUE klass)
VALUE members;
members = struct_ivar_get(klass, id_members);
if (!RB_TYPE_P(members, T_ARRAY)) {
- rb_raise(rb_eTypeError, "broken members");
+ rb_bug("broken members"); /* should never happen */
}
return RARRAY_LEN(members);
}
@@ -859,6 +859,13 @@ struct_alloc(VALUE klass)
}
}
+// Whether `klass` allocates its instances with struct_alloc.
+bool
+rb_zjit_class_has_struct_allocator(VALUE klass)
+{
+ return rb_get_alloc_func(klass) == struct_alloc;
+}
+
VALUE
rb_struct_alloc(VALUE klass, VALUE values)
{
diff --git a/test/fileutils/test_fileutils.rb b/test/fileutils/test_fileutils.rb
index 27511c2f50d0ea..dd2ba5db3ab41b 100644
--- a/test/fileutils/test_fileutils.rb
+++ b/test/fileutils/test_fileutils.rb
@@ -309,6 +309,15 @@ def test_cmp
TARGETS.each do |fname|
assert cmp(fname, fname), 'not same?'
end
+
+ File.write('tmp/same', "contents\n")
+ File.write('tmp/copy', "contents\n")
+ File.write('tmp/different', "content!\n")
+ File.write('tmp/shorter', "content")
+ assert(cmp('tmp/same', 'tmp/copy'))
+ assert_equal(false, cmp('tmp/same', 'tmp/different'))
+ assert_equal(false, cmp('tmp/same', 'tmp/shorter'))
+
assert_raise(ArgumentError) {
cmp TARGETS[0], TARGETS[0], :undefinedoption => true
}
@@ -582,6 +591,11 @@ def test_cp_lr
def test_mv
check_singleton :mv
+ File.write('tmp/source', "contents\n")
+ assert_equal(0, FileUtils.mv('tmp/source', 'tmp/destination'))
+ assert_file_not_exist('tmp/source')
+ assert_equal("contents\n", File.read('tmp/destination'))
+
mkdir 'tmp/dest'
TARGETS.each do |fname|
cp fname, 'tmp/mvsrc'
@@ -676,6 +690,11 @@ def test_rm
def test_rm_f
check_singleton :rm_f
+ File.write('tmp/file', "contents\n")
+ assert_equal(['tmp/file'], FileUtils.rm_f('tmp/file'))
+ assert_file_not_exist('tmp/file')
+ assert_equal(['tmp/missing'], FileUtils.rm_f('tmp/missing'))
+
TARGETS.each do |fname|
cp fname, 'tmp/rmsrc'
rm_f 'tmp/rmsrc'
@@ -928,6 +947,8 @@ def test_ln
TARGETS.each do |fname|
ln fname, 'tmp/lndest'
assert_same_file fname, 'tmp/lndest'
+ assert_same_entry fname, 'tmp/lndest'
+ assert_file.identical?(fname, 'tmp/lndest')
File.unlink 'tmp/lndest'
end
@@ -981,9 +1002,24 @@ def test_ln_pathname
def test_ln_s
check_singleton :ln_s
- ln_s TARGETS, 'tmp'
- each_srcdest do |fname, lnfname|
+ TARGETS.each do |fname|
+ fname = "../#{fname}"
+ lnfname = 'tmp/lnsdest'
+ assert_equal(0, ln_s(fname, lnfname))
+ assert_file.symlink?(lnfname)
assert_equal fname, File.readlink(lnfname)
+ assert_file.exist?(lnfname)
+ ensure
+ rm_f lnfname
+ end
+ end if have_symlink?
+
+ def test_ln_s_multiple
+ ln_s TARGETS.map {|fname| "../#{fname}" }, 'tmp'
+ each_srcdest do |fname, lnfname|
+ assert_file.symlink?(lnfname)
+ assert_equal "../#{fname}", File.readlink(lnfname)
+ assert_file.exist?(lnfname)
ensure
rm_f lnfname
end
@@ -993,17 +1029,7 @@ def test_ln_s
ln_s TARGETS, lnfname
}
assert_file.not_exist?(lnfname)
-
- TARGETS.each do |fname|
- fname = "../#{fname}"
- lnfname = 'tmp/lnsdest'
- ln_s fname, lnfname
- assert_file.symlink?(lnfname)
- assert_equal fname, File.readlink(lnfname)
- ensure
- rm_f lnfname
- end
- end if have_symlink? and !no_broken_symlink?
+ end if have_symlink?
def test_ln_s_relative_to_symlinked_directory
mkdir_p 'tmp/symlink_dir/.dotfiles/zsh'
@@ -1040,9 +1066,11 @@ def test_ln_s_relative_to_symlinked_directory
def test_ln_s_broken_symlink
assert_nothing_raised {
- ln_s 'symlink', 'tmp/symlink'
+ ln_s 'missing', 'tmp/symlink'
}
assert_symlink 'tmp/symlink'
+ assert_equal 'missing', File.readlink('tmp/symlink')
+ assert_file.not_exist?('tmp/symlink')
end if have_symlink? and !no_broken_symlink?
def test_ln_s_pathname
@@ -1229,7 +1257,7 @@ def test_mkdir_p
)
my_rm_rf 'tmpdir'
dirs.each do |d|
- mkdir_p d
+ assert_equal([d], mkdir_p(d))
assert_directory d
assert_file_not_exist "#{d}/a"
assert_file_not_exist "#{d}/b"
@@ -1885,12 +1913,15 @@ def test_remove_dir_with_file
def test_compare_file
check_singleton :compare_file
- # FIXME
+ assert_equal(FileUtils.method(:cmp), FileUtils.method(:compare_file))
end
def test_compare_stream
check_singleton :compare_stream
- # FIXME
+
+ assert(FileUtils.compare_stream(StringIO.new("contents\n"), StringIO.new("contents\n")))
+ assert_not_equal(true, FileUtils.compare_stream(StringIO.new("contents\n"), StringIO.new("content!\n")))
+ assert_not_equal(true, FileUtils.compare_stream(StringIO.new("contents\n"), StringIO.new("content")))
end
class Stream
@@ -1934,30 +1965,44 @@ def test_uptodate?
def test_cd
check_singleton :cd
+
+ original = FileUtils.pwd
+ FileUtils.cd('tmp')
+ assert_equal(File.join(original, 'tmp'), FileUtils.pwd)
+ ensure
+ FileUtils.cd(original) if original
end
def test_cd_result
assert_equal 42, cd('.') { 42 }
+
+ original = FileUtils.pwd
+ assert_equal('tmp', FileUtils.cd('tmp') {|dir|
+ assert_equal(File.join(original, 'tmp'), Dir.pwd)
+ dir
+ })
+ assert_equal(original, FileUtils.pwd)
end
def test_chdir
check_singleton :chdir
+ assert_equal(FileUtils.method(:cd), FileUtils.method(:chdir))
end
- def test_chdir_verbose
+ def test_cd_verbose
assert_output_lines(["cd .", "cd -"], FileUtils) do
- FileUtils.chdir('.', verbose: true){}
+ FileUtils.cd('.', verbose: true){}
end
end
- def test_chdir_verbose_frozen
+ def test_cd_verbose_frozen
o = Object.new
o.extend(FileUtils)
- o.singleton_class.send(:public, :chdir)
+ o.singleton_class.send(:public, :cd)
o.freeze
orig_stdout = $stdout
$stdout = StringIO.new
- o.chdir('.', verbose: true){}
+ o.cd('.', verbose: true){}
$stdout.rewind
assert_equal(<<-END, $stdout.read)
cd .
@@ -1969,31 +2014,43 @@ def test_chdir_verbose_frozen
def test_getwd
check_singleton :getwd
+ assert_equal(FileUtils.method(:pwd), FileUtils.method(:getwd))
end
def test_identical?
check_singleton :identical?
+ assert_equal(FileUtils.method(:cmp), FileUtils.method(:identical?))
end
def test_link
check_singleton :link
+ assert_equal(FileUtils.method(:ln), FileUtils.method(:link))
end
def test_makedirs
check_singleton :makedirs
+ assert_equal(FileUtils.method(:mkdir_p), FileUtils.method(:makedirs))
end
def test_mkpath
check_singleton :mkpath
+ assert_equal(FileUtils.method(:mkdir_p), FileUtils.method(:mkpath))
end
def test_move
check_singleton :move
+ assert_equal(FileUtils.method(:mv), FileUtils.method(:move))
end
def test_rm_rf
check_singleton :rm_rf
+ FileUtils.mkdir_p('tmp/tree/child')
+ File.write('tmp/tree/child/file', "contents\n")
+ assert_equal(['tmp/tree'], FileUtils.rm_rf('tmp/tree'))
+ assert_file_not_exist('tmp/tree')
+ assert_equal(['tmp/missing'], FileUtils.rm_rf('tmp/missing'))
+
return if /mswin|mingw/ =~ RUBY_PLATFORM
mkdir 'tmpdatadir'
@@ -2045,14 +2102,17 @@ def test_rmdir
def test_rmtree
check_singleton :rmtree
+ assert_equal(FileUtils.method(:rm_rf), FileUtils.method(:rmtree))
end
def test_safe_unlink
check_singleton :safe_unlink
+ assert_equal(FileUtils.method(:rm_f), FileUtils.method(:safe_unlink))
end
def test_symlink
check_singleton :symlink
+ assert_equal(FileUtils.method(:ln_s), FileUtils.method(:symlink))
end
def test_touch
@@ -2091,18 +2151,36 @@ def test_touch_mtime
end
def test_collect_methods
+ assert_include(FileUtils.collect_method(:preserve), 'cp')
+ assert_include(FileUtils.collect_method(:preserve), 'install')
+ assert_include(FileUtils.collect_method(:secure), 'mv')
+ assert_not_include(FileUtils.collect_method(:secure), 'cp')
end
def test_commands
+ commands = FileUtils.commands
+ assert_include(commands, 'mv')
+ assert_include(commands, 'chdir')
+ assert_equal(commands.uniq, commands)
end
def test_have_option?
+ assert(FileUtils.have_option?(:mv, :force))
+ assert(FileUtils.have_option?('mv', :secure))
+ assert_not_equal(true, FileUtils.have_option?(:mv, :preserve))
+ assert_raise(ArgumentError) {FileUtils.have_option?(:missing, :noop)}
end
def test_options
+ options = FileUtils.options
+ assert_include(options, 'force')
+ assert_include(options, 'verbose')
+ assert_equal(options.uniq, options)
end
def test_options_of
+ assert_equal(%w[force noop verbose secure], FileUtils.options_of(:mv))
+ assert_equal(%w[force noop verbose secure], FileUtils.options_of('mv'))
end
end
diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb
index 2e227b797f7fa9..90127716c60873 100644
--- a/test/ruby/test_file_exhaustive.rb
+++ b/test/ruby/test_file_exhaustive.rb
@@ -301,6 +301,21 @@ def test_stat_dotted_prefix
end
end if NTFS
+ def test_stat_symlink_loop
+ return unless symlinkfile
+ path = make_tmp_filename("symlink_loop")
+ File.symlink(File.basename(path), path)
+ assert_predicate(File.lstat(path), :symlink?)
+ assert_raise(Errno::ELOOP) { File.stat(path) }
+ end
+
+ def test_exist_p_symlink_loop
+ return unless symlinkfile
+ path = make_tmp_filename("symlink_loop")
+ File.symlink(File.basename(path), path)
+ assert_file.not_exist?(path)
+ end
+
def test_lstat
return unless symlinkfile
assert_equal(false, File.stat(symlinkfile).symlink?)
diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb
index fb9864c70e18ce..e4bb82f2f9a465 100644
--- a/test/ruby/test_time.rb
+++ b/test/ruby/test_time.rb
@@ -166,6 +166,18 @@ def test_new_from_string
}
end
+ def test_new_from_string_modified_by_precision
+ str = "2020-12-25 00:00:00" + "0" * 1_000_000
+ obj = Object.new
+ obj.define_singleton_method(:to_int) do
+ str.clear
+ 9
+ end
+ assert_raise_with_message(ArgumentError, /can't parse/) {
+ Time.new(str, precision: obj)
+ }
+ end
+
def test_time_add()
assert_equal(Time.utc(2000, 3, 21, 3, 30) + 3 * 3600,
Time.utc(2000, 3, 21, 6, 30))
diff --git a/thread.c b/thread.c
index 479f703b487da9..7690f3f712976d 100644
--- a/thread.c
+++ b/thread.c
@@ -5342,6 +5342,7 @@ rb_thread_atfork_internal(rb_thread_t *th, void (*atfork)(rb_thread_t *, const r
rb_gc_zombie_objspaces_atfork();
rb_gc_atfork_global_locks();
rb_generic_fields_lock_atfork();
+ rb_fiber_pool_lock_atfork();
ccan_list_head_init(&th->interrupt_exec_tasks);
vm->fork_gen++;
diff --git a/time.c b/time.c
index 1823b1078f0955..ef32b3dd02e8e2 100644
--- a/time.c
+++ b/time.c
@@ -2655,13 +2655,14 @@ time_init_parse(rb_execution_context_t *ec, VALUE time, VALUE str, VALUE zone, V
rb_raise(rb_eArgError, "time string should have ASCII compatible encoding");
}
+ size_t prec = NIL_P(precision) ? SIZE_MAX : NUM2SIZET(precision);
+
const char *const begin = RSTRING_PTR(str);
const char *const end = RSTRING_END(str);
const char *ptr = begin;
VALUE year = Qnil, subsec = Qnil;
int mon = -1, mday = -1, hour = -1, min = -1, sec = -1;
size_t ndigits;
- size_t prec = NIL_P(precision) ? SIZE_MAX : NUM2SIZET(precision);
if ((ptr < end) && (ISSPACE(*ptr) || ISSPACE(*(end-1)))) {
rb_raise(rb_eArgError, "can't parse: %+"PRIsVALUE, str);
diff --git a/tool/zjit_diff.rb b/tool/zjit_diff.rb
index 4f8f74d20f6225..50fd8cf8f4b28c 100755
--- a/tool/zjit_diff.rb
+++ b/tool/zjit_diff.rb
@@ -53,10 +53,11 @@ def bench!
def run_benchmarks(ruby_bench_path)
Dir.chdir(ruby_bench_path) do
+ zjit_stats = @options[:zjit_stats] ? ' --zjit-stats' : ''
@runner.cmd({ 'RUBIES_DIR' => RUBIES_DIR },
'./run_benchmarks.rb',
'--chruby',
- "before::#{@before_hash} --zjit-stats;after::#{@after_hash} --zjit-stats",
+ "before::#{@before_hash}#{zjit_stats};after::#{@after_hash}#{zjit_stats}",
'--out-name',
DATA_FILENAME,
*@options[:bench_args],
@@ -177,7 +178,7 @@ def parse_ref(ref)
DEFAULT_BENCHMARKS = %w[lobsters railsbench].freeze
-options = {}
+options = { zjit_stats: true }
subtext = <<~HELP
Subcommands:
@@ -231,6 +232,10 @@ def parse_ref(ref)
options[:force_rebuild] = true
end
+ opts.on('--[no-]zjit-stats', 'Pass --zjit-stats to each Ruby command') do |zjit_stats|
+ options[:zjit_stats] = zjit_stats
+ end
+
opts.on('--quiet', 'Silence output of commands except for benchmark result') do
options[:quiet] = true
end
diff --git a/vm_core.h b/vm_core.h
index 44e19a5463f1d6..9723d1151ff139 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -1722,11 +1722,16 @@ VM_ENV_BOX_UNCHECKED(const VALUE *ep)
#if VM_CHECK_MODE > 0
int rb_vm_ep_in_heap_p(const VALUE *ep);
#endif
+static inline rb_execution_context_t * rb_current_execution_context(bool expect_ec);
static inline int
VM_ENV_ESCAPED_P(const VALUE *ep)
{
- VM_ASSERT(rb_vm_ep_in_heap_p(ep) == !!VM_ENV_FLAGS(ep, VM_ENV_FLAG_ESCAPED));
+#if VM_CHECK_MODE > 0
+ if (rb_current_execution_context(false)) {
+ VM_ASSERT(rb_vm_ep_in_heap_p(ep) == !!VM_ENV_FLAGS(ep, VM_ENV_FLAG_ESCAPED));
+ }
+#endif
return VM_ENV_FLAGS(ep, VM_ENV_FLAG_ESCAPED) ? 1 : 0;
}
diff --git a/win32/win32.c b/win32/win32.c
index e4c27472a805d4..fd9c86ea1dd544 100644
--- a/win32/win32.c
+++ b/win32/win32.c
@@ -6070,8 +6070,12 @@ winnt_stat(const WCHAR *path, struct stati128 *st, BOOL lstat)
}
}
else {
- if ((open_error == ERROR_FILE_NOT_FOUND) || (open_error == ERROR_INVALID_NAME)
- || (open_error == ERROR_PATH_NOT_FOUND || (open_error == ERROR_BAD_NETPATH))) {
+ switch (open_error) {
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_INVALID_NAME:
+ case ERROR_PATH_NOT_FOUND:
+ case ERROR_BAD_NETPATH:
+ case ERROR_CANT_RESOLVE_FILENAME:
errno = map_errno(open_error);
return -1;
}
diff --git a/zjit.c b/zjit.c
index 4e44febf72b6bc..e32af670dd29e4 100644
--- a/zjit.c
+++ b/zjit.c
@@ -345,8 +345,20 @@ rb_zjit_class_initialized_p(VALUE klass)
return RCLASS_INITIALIZED_P(klass);
}
+// Whether rb_class_superclass can be called on the class without raising: it raises
+// TypeError when the superclasses array is unbuilt (an uninitialized class, e.g.
+// Class.allocate), except for BasicObject, which it special-cases to return nil.
+bool
+rb_zjit_can_load_superclass_p(VALUE klass)
+{
+ return klass == rb_cBasicObject || RCLASS_SUPERCLASSES(klass) != NULL;
+}
+
rb_alloc_func_t rb_zjit_class_get_alloc_func(VALUE klass);
+// Defined in struct.c, where struct_alloc is visible.
+bool rb_zjit_class_has_struct_allocator(VALUE klass);
+
VALUE rb_class_allocate_instance(VALUE klass);
bool
diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs
index 7da19ed0fa92c2..c4cb49cefd50c1 100644
--- a/zjit/bindgen/src/main.rs
+++ b/zjit/bindgen/src/main.rs
@@ -169,6 +169,7 @@ fn main() {
.allowlist_function("rb_singleton_class")
.allowlist_function("rb_define_class")
.allowlist_function("rb_class_get_superclass")
+ .allowlist_function("rb_class_superclass")
.allowlist_function("rb_gc_disable")
.allowlist_function("rb_gc_enable")
.allowlist_function("rb_gc_mark")
@@ -404,7 +405,9 @@ fn main() {
.allowlist_function("rb_insn_len")
.allowlist_function("rb_yarv_class_of")
.allowlist_function("rb_zjit_class_initialized_p")
+ .allowlist_function("rb_zjit_can_load_superclass_p")
.allowlist_function("rb_zjit_class_has_default_allocator")
+ .allowlist_function("rb_zjit_class_has_struct_allocator")
.allowlist_function("rb_zjit_class_get_alloc_func")
.allowlist_function("rb_get_ec_cfp")
.allowlist_function("rb_get_cfp_iseq")
diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs
index 79d8036451baa1..ebf983315c3200 100644
--- a/zjit/src/backend/lir.rs
+++ b/zjit/src/backend/lir.rs
@@ -1343,6 +1343,7 @@ impl Insn {
self.is_jump() ||
match self {
Insn::CRet(_) => true,
+ Insn::Abort => true,
_ => false
}
}
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index 1649c1006e1df7..d6525ae3c35289 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -25,7 +25,7 @@ use crate::stats::{counter_ptr, with_time_stat, trace_compile_phase, Counter, Co
use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr};
use crate::backend::lir::{self, Assembler, CArgLocation, C_ARG_OPNDS, C_RET_OPND, CFP, EC, NATIVE_BASE_PTR, NATIVE_STACK_PTR, Opnd, SP, SideExit, SideExitRecompile, SideExitTarget, StackMap, StackMapEntry, Target, asm_ccall, asm_comment};
use crate::hir::{self, iseq_to_hir, BlockId, Invariant, RangeType, SideExitReason::{self, *}, SpecialBackrefSymbol, SpecialObjectType};
-use crate::hir::{BlockHandler, CCallVariadicData, CCallWithFrameData, Const, FieldName, FrameState, Function, Insn, InsnId, Recompile, SendDirectData, SendFallbackReason, qualified_method_name};
+use crate::hir::{BlockHandler, CCallVariadicData, CCallWithFrameData, CondBranchHasTypeData, Const, FieldName, FrameState, Function, Insn, InsnId, Recompile, SendDirectData, SendFallbackReason, qualified_method_name};
use crate::hir_type::{types, Type};
use crate::options::{get_option, InlineDepth, DEFAULT_MAX_VERSIONS};
use crate::cast::IntoUsize;
@@ -493,6 +493,7 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
let insn = function.find(insn_id);
let symbol_range = perf::hir_symbol_range_start(&mut asm, &insn);
+ asm_comment!(asm, "Insn: {insn_id} {insn}");
let result = match &insn {
Insn::CondBranch { val, if_true, if_false } => {
let val_opnd = jit.get_opnd(*val);
@@ -516,6 +517,26 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
assert!(asm.current_block().insns.last().unwrap().is_terminator());
Ok(())
}
+ Insn::CondBranchHasType(cond_branch) => {
+ let CondBranchHasTypeData { val, expected, if_true, if_false } = &**cond_branch;
+ let val_opnd = jit.get_opnd(*val);
+ let val_type = function.type_of(*val);
+
+ let true_branch = lir::BranchEdge {
+ target: hir_to_lir[if_true.target].unwrap(),
+ args: if_true.args.iter().map(|insn_id| jit.get_opnd(*insn_id)).collect()
+ };
+
+ let false_branch = lir::BranchEdge {
+ target: hir_to_lir[if_false.target].unwrap(),
+ args: if_false.args.iter().map(|insn_id| jit.get_opnd(*insn_id)).collect()
+ };
+
+ gen_cond_branch_has_type(&mut jit, &mut asm, val_opnd, val_type, *expected, true_branch, false_branch);
+
+ assert!(asm.current_block().insns.last().unwrap().is_terminator());
+ Ok(())
+ }
Insn::Jump(target) => {
let lir_target = hir_to_lir[target.target].unwrap();
let branch_edge = lir::BranchEdge {
@@ -731,10 +752,6 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::UnboxFixnum { val } => gen_unbox_fixnum(asm, opnd!(val)),
Insn::Test { val } => gen_test(asm, opnd!(val)),
Insn::RefineType { val, .. } => opnd!(val),
- Insn::HasType { val, expected } => {
- let val_type = function.type_of(*val);
- gen_has_type(jit, asm, opnd!(val), val_type, *expected)
- }
&Insn::GuardType { val, guard_type, state, recompile } => {
let val_type = function.type_of(val);
gen_guard_type(jit, asm, function, opnd!(val), val_type, guard_type, recompile, &function.frame_state(state))
@@ -804,7 +821,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::ArrayMax { ref elements, state } => gen_array_max(jit, asm, function, opnds!(elements), &function.frame_state(state)),
&Insn::ArrayMin { ref elements, state } => gen_array_min(jit, asm, function, opnds!(elements), &function.frame_state(state)),
&Insn::Throw { throw_state, val, state } => no_output!(gen_throw(jit, asm, function, throw_state, opnd!(val), &function.frame_state(state))),
- &Insn::CondBranch { .. }
+ &Insn::CondBranch { .. } | &Insn::CondBranchHasType { .. }
| &Insn::Jump { .. } | Insn::Entries { .. } => unreachable!(),
};
@@ -2824,10 +2841,8 @@ fn gen_throw(jit: &mut JITState, asm: &mut Assembler, function: &Function, throw
}
asm_ccall!(asm, rb_zjit_throw, EC, CFP, Opnd::UImm(throw_state.into()), val);
- // rb_zjit_throw() never returns. Trap in case it somehow does, and end the
- // LIR block with an unreachable ret to give it a normal terminator.
+ // rb_zjit_throw() never returns. Trap in case it somehow does.
asm.abort();
- asm.cret(C_RET_OPND);
}
/// Compile Fixnum + Fixnum
@@ -3040,45 +3055,43 @@ fn gen_test(asm: &mut Assembler, val: lir::Opnd) -> lir::Opnd {
asm.csel_e(0.into(), 1.into())
}
-fn gen_has_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, val_type: Type, ty: Type) -> lir::Opnd {
- if ty.is_subtype(types::Fixnum) {
+/// Branch to `if_true` if `val` has type `ty` and to `if_false` otherwise, using only the
+/// condition flags -- the test result is never materialized as a boolean. Terminates the
+/// current block.
+fn gen_cond_branch_has_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, val_type: Type, ty: Type, if_true: lir::BranchEdge, if_false: lir::BranchEdge) {
+ let true_target = Target::Block(Box::new(if_true));
+ let false_target = Target::Block(Box::new(if_false));
+
+ // Each arm sets the flags and picks the conditional jump taken when `val` has type `ty`;
+ // the not-taken path falls to the unconditional jmp to `false_target` below.
+ let jcc: fn(Target) -> lir::Insn = if ty.is_subtype(types::Fixnum) {
asm.test(val, Opnd::UImm(RUBY_FIXNUM_FLAG as u64));
- asm.csel_nz(Opnd::Imm(1), Opnd::Imm(0))
+ lir::Insn::Jnz
} else if ty.is_subtype(types::Flonum) {
// Flonum: (val & RUBY_FLONUM_MASK) == RUBY_FLONUM_FLAG
let masked = asm.and(val, Opnd::UImm(RUBY_FLONUM_MASK as u64));
asm.cmp(masked, Opnd::UImm(RUBY_FLONUM_FLAG as u64));
- asm.csel_e(Opnd::Imm(1), Opnd::Imm(0))
+ lir::Insn::Je
} else if ty.is_subtype(types::StaticSymbol) {
// Static symbols have (val & 0xff) == RUBY_SYMBOL_FLAG
// Use 8-bit comparison like YJIT does.
// If `val` is a constant (rare but possible), put it in a register to allow masking.
let val = asm.load_imm(val);
asm.cmp(val.with_num_bits(8), Opnd::UImm(RUBY_SYMBOL_FLAG as u64));
- asm.csel_e(Opnd::Imm(1), Opnd::Imm(0))
+ lir::Insn::Je
} else if ty.is_subtype(types::NilClass) {
asm.cmp(val, Qnil.into());
- asm.csel_e(Opnd::Imm(1), Opnd::Imm(0))
+ lir::Insn::Je
} else if ty.is_subtype(types::TrueClass) {
asm.cmp(val, Qtrue.into());
- asm.csel_e(Opnd::Imm(1), Opnd::Imm(0))
+ lir::Insn::Je
} else if ty.is_subtype(types::FalseClass) {
asm.cmp(val, Qfalse.into());
- asm.csel_e(Opnd::Imm(1), Opnd::Imm(0))
+ lir::Insn::Je
} else if ty.is_immediate() {
// All immediate types' guard should have been handled above
panic!("unexpected immediate guard type: {ty}");
} else if let Some(expected_class) = ty.runtime_exact_ruby_class() {
- let hir_block_id = asm.current_block().hir_block_id;
- let rpo_idx = asm.current_block().rpo_index;
-
- // Create a result block that all paths converge to
- let result_block = asm.new_block(hir_block_id, false, rpo_idx);
- let result_edge = |v| Target::Block(Box::new(lir::BranchEdge {
- target: result_block,
- args: vec![v],
- }));
-
// If val isn't in a register, load it to use it as the base of Opnd::mem later.
// TODO: Max thinks codegen should not care about the shapes of the operands except to create them. (Shopify/ruby#685)
let val = asm.load_mem(val);
@@ -3087,49 +3100,30 @@ fn gen_has_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, val_typ
if !is_known_heap_basic_object {
// Immediate -> definitely not the class
asm.test(val, (RUBY_IMMEDIATE_MASK as u64).into());
- asm.jnz(jit, result_edge(Opnd::Imm(0)));
+ asm.jnz(jit, false_target.clone());
// Qfalse -> definitely not the class
asm.cmp(val, Qfalse.into());
- asm.je(jit, result_edge(Opnd::Imm(0)));
+ asm.je(jit, false_target.clone());
}
// Heap object -> check klass field
let klass = asm.load(Opnd::mem(64, val, RUBY_OFFSET_RBASIC_KLASS));
asm.cmp(klass, Opnd::Value(expected_class));
- let result = asm.csel_e(Opnd::UImm(1), Opnd::Imm(0));
- asm.jmp(result_edge(result));
-
- // Result block -- receives the value via block parameter (phi node)
- asm.set_current_block(result_block);
- let label = jit.get_label(asm, result_block, hir_block_id);
- asm.write_label(label);
- let param = asm.new_block_param(VALUE_BITS);
- asm.current_block().add_parameter(param);
- param
+ lir::Insn::Je
} else if let Some(builtin_type) = ty.builtin_type_equivalent() {
- let hir_block_id = asm.current_block().hir_block_id;
- let rpo_idx = asm.current_block().rpo_index;
-
- // Create a result block that all paths converge to
- let result_block = asm.new_block(hir_block_id, false, rpo_idx);
- let result_edge = |v| Target::Block(Box::new(lir::BranchEdge {
- target: result_block,
- args: vec![v],
- }));
-
// If val isn't in a register, load it to use it as the base of Opnd::mem later.
let val = asm.load_mem(val);
let is_known_heap_basic_object = val_type.is_subtype(types::HeapBasicObject);
if !is_known_heap_basic_object {
- // Immediate -> definitely not the class
+ // Immediate -> definitely not the type
asm.test(val, (RUBY_IMMEDIATE_MASK as u64).into());
- asm.jnz(jit, result_edge(Opnd::Imm(0)));
+ asm.jnz(jit, false_target.clone());
- // Qfalse -> definitely not the class
+ // Qfalse -> definitely not the type
asm.cmp(val, Qfalse.into());
- asm.je(jit, result_edge(Opnd::Imm(0)));
+ asm.je(jit, false_target.clone());
}
// Heap object
@@ -3137,19 +3131,13 @@ fn gen_has_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, val_typ
let flags = asm.load(Opnd::mem(VALUE_BITS, val, RUBY_OFFSET_RBASIC_FLAGS));
let tag = asm.and(flags, Opnd::UImm(RUBY_T_MASK as u64));
asm.cmp(tag, Opnd::UImm(builtin_type as u64));
- let result = asm.csel_e(Opnd::UImm(1), Opnd::Imm(0));
- asm.jmp(result_edge(result));
-
- // Result block -- receives the value via block parameter (phi node)
- asm.set_current_block(result_block);
- let label = jit.get_label(asm, result_block, hir_block_id);
- asm.write_label(label);
- let param = asm.new_block_param(VALUE_BITS);
- asm.current_block().add_parameter(param);
- param
+ lir::Insn::Je
} else {
unimplemented!("unsupported type: {ty}");
- }
+ };
+
+ asm.push_insn(jcc(true_target));
+ asm.jmp(false_target);
}
/// Compile a type check with a side exit
diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs
index 3d8d561178bb09..1c4302f12e1374 100644
--- a/zjit/src/cruby.rs
+++ b/zjit/src/cruby.rs
@@ -1648,6 +1648,10 @@ pub fn class_has_leaf_allocator(class: VALUE) -> bool {
if class == unsafe { rb_cString } { return true; }
// rb_reg_s_alloc
if class == unsafe { rb_cRegexp } { return true; }
+ // struct_alloc, used by every Struct subclass, is leaf: it reads the hidden __members__ ivar
+ // and allocates, without calling into Ruby. It does modify the class's __members__ ivar once
+ // to cache the members, but without a Ractor check.
+ if unsafe { rb_zjit_class_has_struct_allocator(class) } { return true; }
// rb_class_allocate_instance
unsafe { rb_zjit_class_has_default_allocator(class) }
}
diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs
index 94d7bd4f24bfe5..dc0aa8698d362e 100644
--- a/zjit/src/cruby_bindings.inc.rs
+++ b/zjit/src/cruby_bindings.inc.rs
@@ -2279,6 +2279,7 @@ unsafe extern "C" {
pub fn rb_obj_frozen_p(obj: VALUE) -> VALUE;
pub fn rb_class_real(klass: VALUE) -> VALUE;
pub fn rb_class_inherited_p(scion: VALUE, ascendant: VALUE) -> VALUE;
+ pub fn rb_class_superclass(klass: VALUE) -> VALUE;
pub fn rb_backref_get() -> VALUE;
pub fn rb_range_new(beg: VALUE, end: VALUE, excl: ::std::os::raw::c_int) -> VALUE;
pub fn rb_reg_nth_match(n: ::std::os::raw::c_int, md: VALUE) -> VALUE;
@@ -2482,7 +2483,9 @@ unsafe extern "C" {
recv: VALUE,
) -> *const rb_callable_method_entry_struct;
pub fn rb_zjit_class_initialized_p(klass: VALUE) -> bool;
+ pub fn rb_zjit_can_load_superclass_p(klass: VALUE) -> bool;
pub fn rb_zjit_class_get_alloc_func(klass: VALUE) -> rb_alloc_func_t;
+ pub fn rb_zjit_class_has_struct_allocator(klass: VALUE) -> bool;
pub fn rb_zjit_class_has_default_allocator(klass: VALUE) -> bool;
pub fn rb_vm_get_untagged_block_handler(reg_cfp: *mut rb_control_frame_t) -> VALUE;
pub fn rb_vm_once_done_value(is: ISE, result: *mut VALUE) -> bool;
diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs
index 4dcd7ca71854c3..99715290e495b2 100644
--- a/zjit/src/cruby_methods.rs
+++ b/zjit/src/cruby_methods.rs
@@ -245,11 +245,13 @@ pub fn init() -> Annotations {
annotate!(rb_cNilClass, "nil?", inline_nilclass_nil_p);
annotate!(rb_mKernel, "nil?", inline_kernel_nil_p);
annotate!(rb_mKernel, "respond_to?", inline_kernel_respond_to_p);
+ annotate!(rb_mKernel, "dup", inline_kernel_dup);
annotate!(rb_cBasicObject, "==", inline_basic_object_eq, types::BoolExact, no_gc, leaf, elidable);
annotate!(rb_cBasicObject, "!", inline_basic_object_not, types::BoolExact, no_gc, leaf, elidable);
annotate!(rb_cBasicObject, "!=", inline_basic_object_neq, types::BoolExact);
annotate!(rb_cBasicObject, "initialize", inline_basic_object_initialize);
annotate!(rb_cClass, "allocate", inline_class_allocate);
+ annotate!(rb_cClass, "superclass", inline_class_superclass, types::Class.union(types::NilClass));
annotate!(rb_cInteger, "succ", inline_integer_succ);
annotate!(rb_cInteger, "^", inline_integer_xor);
annotate!(rb_cInteger, "==", inline_integer_eq);
@@ -920,6 +922,22 @@ fn inline_class_allocate(fun: &mut hir::Function, block: hir::BlockId, recv: hir
fun.try_inline_object_alloc(block, recv, state)
}
+fn inline_class_superclass(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option {
+ // Class#superclass takes no arguments; calls with the wrong argc bail out with
+ // ArgcParamMismatch before inlining is attempted.
+ debug_assert!(args.is_empty(), "Class#superclass takes no arguments");
+ // A class's superclass cannot change after the class is created (prepending a module only
+ // inserts ICLASSes, which superclass skips), so fold the lookup when the receiver is a
+ // compile-time constant.
+ let recv_class = fun.type_of(recv).ruby_object()?;
+ if !unsafe { RB_TYPE_P(recv_class, RUBY_T_CLASS) } { return None; }
+ // rb_class_superclass raises TypeError on an uninitialized class (e.g. from Class.allocate);
+ // don't fold.
+ if !unsafe { rb_zjit_can_load_superclass_p(recv_class) } { return None; }
+ let superclass = unsafe { rb_class_superclass(recv_class) };
+ Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(superclass) }))
+}
+
fn inline_basic_object_initialize(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option {
if !args.is_empty() { return None; }
let result = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(Qnil) });
@@ -1066,6 +1084,33 @@ fn inline_kernel_respond_to_p(
Some(fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(result) }))
}
+fn inline_kernel_dup(fun: &mut hir::Function, _block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option {
+ let &[] = args else { return None; };
+ // rb_obj_dup skips the call for "special objects". We don't check for
+ // bignum/float/rational/complex here because Numeric#dup defines its own no-op `dup` method.
+ //
+ // static inline int
+ // special_object_p(VALUE obj)
+ // {
+ // if (SPECIAL_CONST_P(obj)) return TRUE;
+ // switch (BUILTIN_TYPE(obj)) {
+ // case T_BIGNUM:
+ // case T_FLOAT:
+ // case T_SYMBOL:
+ // case T_RATIONAL:
+ // case T_COMPLEX:
+ // /* not a comprehensive list */
+ // return TRUE;
+ // default:
+ // return FALSE;
+ // }
+ // }
+ if fun.is_a(recv, types::Immediate.union(types::DynamicSymbol)) {
+ return Some(recv);
+ }
+ None
+}
+
fn inline_kernel_class(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option {
let &[recv] = args else { return None; };
let recv_class = fun.type_of(recv).runtime_exact_ruby_class()?;
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 082a0c57680334..4e8cc031bdb5a4 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -967,6 +967,15 @@ pub struct SendDirectData {
pub state: InsnId,
}
+/// Payload of [`Insn::CondBranchHasType`]. Boxed in the enum to keep `Insn` small.
+#[derive(Debug, Clone)]
+pub struct CondBranchHasTypeData {
+ pub val: InsnId,
+ pub expected: Type,
+ pub if_true: BranchEdge,
+ pub if_false: BranchEdge,
+}
+
/// Payload of [`Insn::CCallVariadic`]. Boxed in the enum to keep `Insn` small.
#[derive(Debug, Clone)]
pub struct CCallVariadicData {
@@ -1153,6 +1162,10 @@ pub enum Insn {
/// Conditional branch
CondBranch { val: InsnId, if_true: BranchEdge, if_false: BranchEdge },
+ /// Conditional branch on a type test: branch to if_true if val has type expected and to
+ /// if_false otherwise.
+ CondBranchHasType(Box),
+
/// Call a C function without pushing a frame
/// `name` and `owner` are for printing purposes only
CCall { cfunc: *const u8, recv: InsnId, args: Vec, name: ID, owner: VALUE, return_type: Type, elidable: bool },
@@ -1305,8 +1318,6 @@ pub enum Insn {
/// Refine the known type information of with additional type information.
/// Computes the intersection of the existing type and the new type.
RefineType { val: InsnId, new_type: Type },
- /// Return CBool[true] if val has type Type and CBool[false] otherwise.
- HasType { val: InsnId, expected: Type },
/// Side-exit if val doesn't have the expected type.
GuardType { val: InsnId, guard_type: Type, state: InsnId, recompile: Option },
@@ -1456,7 +1467,6 @@ macro_rules! for_each_operand_impl {
$visit_one!(*state);
}
Insn::RefineType { val, .. }
- | Insn::HasType { val, .. }
| Insn::Return { val }
| Insn::Test { val }
| Insn::BoxBool { val } => {
@@ -1539,6 +1549,11 @@ macro_rules! for_each_operand_impl {
$visit_many!(true_args);
$visit_many!(false_args);
}
+ Insn::CondBranchHasType(insn) => {
+ $visit_one!(insn.val);
+ $visit_many!(insn.if_true.args);
+ $visit_many!(insn.if_false.args);
+ }
Insn::ArrayDup { val, state }
| Insn::Throw { val, state, .. }
| Insn::HashDup { val, state } => {
@@ -1695,7 +1710,7 @@ impl Insn {
Insn::Comment { .. }
| Insn::Jump(_)
| Insn::Entries { .. }
- | Insn::CondBranch { .. } | Insn::EntryPoint { .. } | Insn::Return { .. }
+ | Insn::CondBranch { .. } | Insn::CondBranchHasType { .. } | Insn::EntryPoint { .. } | Insn::Return { .. }
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. }
| Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. }
| Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. }
@@ -1710,7 +1725,7 @@ impl Insn {
/// Return true if the instruction ends a basic block and false otherwise.
pub fn is_terminator(&self) -> bool {
match self {
- Insn::Unreachable | Insn::CondBranch { .. } | Insn::Jump(_) | Insn::Entries { .. } | Insn::Return { .. } | Insn::SideExit { .. } | Insn::Throw { .. } => true,
+ Insn::Unreachable | Insn::CondBranch { .. } | Insn::CondBranchHasType { .. } | Insn::Jump(_) | Insn::Entries { .. } | Insn::Return { .. } | Insn::SideExit { .. } | Insn::Throw { .. } => true,
_ => false,
}
}
@@ -1718,7 +1733,7 @@ impl Insn {
/// Return true if the instruction is a jump (has successor blocks in the CFG).
pub fn is_jump(&self) -> bool {
match self {
- Insn::CondBranch { .. } | Insn::Jump(_) | Insn::Entries { .. } => true,
+ Insn::CondBranch { .. } | Insn::CondBranchHasType { .. } | Insn::Jump(_) | Insn::Entries { .. } => true,
_ => false,
}
}
@@ -1847,6 +1862,11 @@ impl Insn {
Insn::Snapshot { .. } => effects::Empty,
Insn::Jump(_) => effects::Any,
Insn::CondBranch { .. } => effects::Any,
+ Insn::CondBranchHasType(insn)
+ => Effect::read_write(
+ if insn.expected.is_subtype(types::Immediate) { abstract_heaps::Empty } else { abstract_heaps::Memory },
+ abstract_heaps::Control
+ ),
Insn::CCall { elidable, .. } => {
if *elidable {
Effect::write(abstract_heaps::Allocator)
@@ -1940,11 +1960,6 @@ impl Insn {
Insn::InvokeProc { .. } => effects::Any,
Insn::InvokeBlockIseqDirect { .. } => effects::Any,
Insn::RefineType { .. } => effects::Empty,
- Insn::HasType { expected, .. }
- => Effect::read_write(
- if expected.is_subtype(types::Immediate) { abstract_heaps::Empty } else { abstract_heaps::Memory },
- abstract_heaps::Empty
- ),
Insn::Entries { .. } => effects::Any,
Insn::BreakPoint | Insn::Unreachable => Effect::read_write(abstract_heaps::Empty, abstract_heaps::Control),
}
@@ -2202,6 +2217,10 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::FixnumAref { recv, index } => write!(f, "FixnumAref {recv}, {index}"),
Insn::Jump(target) => { write!(f, "Jump {target}") }
Insn::CondBranch { val, if_true, if_false } => { write!(f, "CondBranch {val}, {if_true}, {if_false}") },
+ Insn::CondBranchHasType(insn) => {
+ let CondBranchHasTypeData { val, expected, if_true, if_false } = &**insn;
+ write!(f, "CondBranchHasType {val}, {}, {if_true}, {if_false}", expected.print(self.ptr_map))
+ },
Insn::SendDirect(insn) => {
let SendDirectData { recv, cme, iseq, args, block, jit_entry_idx, .. } = &**insn;
let blockiseq = block.map(|bh| match bh { BlockHandler::BlockIseq(iseq) => iseq, BlockHandler::BlockArg => unreachable!() });
@@ -2324,7 +2343,6 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
return Ok(())
},
Insn::RefineType { val, new_type, .. } => { write!(f, "RefineType {val}, {}", new_type.print(self.ptr_map)) },
- Insn::HasType { val, expected, .. } => { write!(f, "HasType {val}, {}", expected.print(self.ptr_map)) },
Insn::GuardBitEquals { val, expected, recompile, .. } => {
write!(f, "GuardBitEquals {val}, {}", expected.print(self.ptr_map))?;
if recompile.is_some() {
@@ -3428,6 +3446,7 @@ impl Function {
let (first, second, rest): (Option, Option, &[BlockId]) = match terminator {
Insn::CondBranch { if_true, if_false, .. } => (Some(if_true.target), Some(if_false.target), &[]),
+ Insn::CondBranchHasType(insn) => (Some(insn.if_true.target), Some(insn.if_false.target), &[]),
Insn::Jump(edge) => (Some(edge.target), None, &[]),
Insn::Entries { targets } => (None, None, targets.as_slice()),
@@ -3641,7 +3660,7 @@ impl Function {
Insn::LoadArg { val_type, .. } => *val_type,
Insn::SetGlobal { .. } | Insn::Jump(_) | Insn::Entries { .. } | Insn::EntryPoint { .. }
| Insn::Comment { .. }
- | Insn::CondBranch { .. } | Insn::Return { .. } | Insn::Throw { .. }
+ | Insn::CondBranch { .. } | Insn::CondBranchHasType { .. } | Insn::Return { .. } | Insn::Throw { .. }
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. }
| Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. }
| Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. }
@@ -3705,9 +3724,6 @@ impl Function {
Insn::CheckMatch { .. } => types::BasicObject,
Insn::GuardType { val, guard_type, .. } => self.type_of(*val).intersection(*guard_type),
Insn::RefineType { val, new_type, .. } => self.type_of(*val).intersection(*new_type),
- &Insn::HasType { val, expected } if self.is_a(val, expected) => Type::from_cbool(true),
- &Insn::HasType { val, expected } if !self.type_of(val).could_be(expected) => Type::from_cbool(false),
- Insn::HasType { .. } => types::CBool,
Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_const(*expected)),
Insn::GuardAnyBitSet { val, .. } => self.type_of(*val),
Insn::GuardNoBitsSet { val, .. } => self.type_of(*val),
@@ -3920,6 +3936,47 @@ impl Function {
}
continue;
}
+ Insn::CondBranchHasType(insn) => {
+ let CondBranchHasTypeData { val, expected, if_true, if_false } = &**insn;
+ let val_type = self.type_of(*val);
+ // If we're looking at
+ // CondBranchHasType v, T, if_true, if_false
+ // then we have four cases:
+ //
+ // * if v could be a T, then if_true is reachable
+ // * if v is a T, then if_false is not reachable
+ // * if v is not a T, then we don't know anything about if_false and
+ // have to assume it's reachable
+ // * if v could not be a T, then if_true is not reachable
+ // * if v is a T, then v has the Empty type which means neither is reachable
+ // * if v is not a T, then it has some other non-Empty type that
+ // overlaps with T and therefore if_false is reachable
+ //
+ // If you explode this decision tree out, you end up with the collapsed
+ // tree of two sequential not-mutually-exclusive checks:
+ //
+ // * if v could be a T, then if_true is reachable
+ // * (not else!) if v is not a T, then if_false is reachable
+ if val_type.could_be(*expected) {
+ reachable.insert(if_true.target);
+ let arg_types: Vec = if_true.args.iter().map(|a| self.type_of(*a)).collect();
+ for (idx, arg_type) in arg_types.into_iter().enumerate() {
+ let param = self.blocks[if_true.target].params[idx];
+ changed |= set_type!(param, self.type_of(param).union(arg_type));
+ }
+ traversed_back_edge |= rpo_order[if_true.target] <= rpo_index;
+ }
+ if !val_type.is_subtype(*expected) {
+ reachable.insert(if_false.target);
+ let arg_types: Vec = if_false.args.iter().map(|a| self.type_of(*a)).collect();
+ for (idx, arg_type) in arg_types.into_iter().enumerate() {
+ let param = self.blocks[if_false.target].params[idx];
+ changed |= set_type!(param, self.type_of(param).union(arg_type));
+ }
+ traversed_back_edge |= rpo_order[if_false.target] <= rpo_index;
+ }
+ continue;
+ }
&Insn::Jump(BranchEdge { target, ref args }) => {
reachable.insert(target);
let arg_types: Vec = args.iter().map(|a| self.type_of(*a)).collect();
@@ -6392,11 +6449,14 @@ impl Function {
*fun.blocks[block_id].insns().last().unwrap()
}
+ // The extra `&`/`&mut` tokens borrow the boxed CondBranchHasType payload's edges with the
+ // same mutability as the match ergonomics give the other arms' bindings.
macro_rules! edges_of {
- ($insn:expr) => {
+ ($insn:expr, $($borrow:tt)+) => {
match $insn {
Insn::Jump(edge) => [Some(edge), None],
Insn::CondBranch { if_true, if_false, .. } => [Some(if_true), Some(if_false)],
+ Insn::CondBranchHasType(insn) => [Some($($borrow)+ insn.if_true), Some($($borrow)+ insn.if_false)],
_ => [None, None],
}.into_iter().flatten()
};
@@ -6404,12 +6464,12 @@ impl Function {
fn outgoing_edges(fun: &Function, block_id: BlockId) -> impl Iterator- {
let insn_id = block_terminator(fun, block_id);
- edges_of!(&fun.insns[insn_id])
+ edges_of!(&fun.insns[insn_id], &)
}
fn outgoing_edges_mut(fun: &mut Function, block_id: BlockId) -> impl Iterator
- {
let insn_id = block_terminator(fun, block_id);
- edges_of!(&mut fun.insns[insn_id])
+ edges_of!(&mut fun.insns[insn_id], &mut)
}
// Instantiate the domain for abstract interpretation.
@@ -7001,6 +7061,16 @@ impl Function {
&Insn::CondBranch { val, ref if_false, .. } if self.is_a(val, Type::from_cbool(false)) => {
self.new_insn(Insn::Jump(if_false.clone()))
}
+ // Match infer_types implementation of CondBranchHasType.
+ Insn::CondBranchHasType(insn) if self.type_of(insn.val).is_subtype(types::Empty) => {
+ self.new_insn(Insn::Unreachable)
+ }
+ Insn::CondBranchHasType(insn) if self.is_a(insn.val, insn.expected) => {
+ self.new_insn(Insn::Jump(insn.if_true.clone()))
+ }
+ Insn::CondBranchHasType(insn) if !self.type_of(insn.val).could_be(insn.expected) => {
+ self.new_insn(Insn::Jump(insn.if_false.clone()))
+ }
_ => insn_id,
};
// If we're adding a new instruction, mark the two equivalent in the union-find and
@@ -7636,6 +7706,10 @@ impl Function {
check_edge(block_id, if_true)?;
check_edge(block_id, if_false)?;
}
+ Insn::CondBranchHasType(insn) => {
+ check_edge(block_id, &insn.if_true)?;
+ check_edge(block_id, &insn.if_false)?;
+ }
_ => {}
}
@@ -7728,6 +7802,10 @@ impl Function {
propagate(if_true.target)?;
propagate(if_false.target)?;
}
+ Insn::CondBranchHasType(insn) => {
+ propagate(insn.if_true.target)?;
+ propagate(insn.if_false.target)?;
+ }
Insn::Entries { targets } => {
for &target in targets {
propagate(target)?;
@@ -8123,7 +8201,7 @@ impl Function {
self.assert_subtype(insn_id, class, types::Class)
}
Insn::RefineType { .. } => Ok(()),
- Insn::HasType { val, .. } => self.assert_subtype(insn_id, val, types::BasicObject),
+ Insn::CondBranchHasType(ref insn) => self.assert_subtype(insn_id, insn.val, types::BasicObject),
Insn::IsBlockParamModified { flags } => self.assert_subtype(insn_id, flags, types::CUInt64),
// Frame instructions have no output to validate; their operands
// are validated by the recv+args group (PushLightweightFrame)
@@ -9561,7 +9639,6 @@ fn add_iseq_to_hir(
fun.push_insn(block, Insn::CheckInterrupts { state: exit_id });
}
let val = state.stack_pop()?;
- let test_id = fun.push_insn(block, Insn::HasType { val, expected: types::NilClass });
let target_idx = insn_idx_at_offset(insn_idx, offset);
let target = insn_idx_to_block[&target_idx];
let nil = fun.push_insn(block, Insn::Const { val: Const::Value(Qnil) });
@@ -9570,11 +9647,12 @@ fn add_iseq_to_hir(
let fall_through = fun.new_block(insn_idx);
- fun.push_insn(block, Insn::CondBranch {
- val: test_id,
+ fun.push_insn(block, Insn::CondBranchHasType(Box::new(CondBranchHasTypeData {
+ val,
+ expected: types::NilClass,
if_true: BranchEdge { target, args: iftrue_state.as_args(self_param) },
if_false: BranchEdge { target: fall_through, args: vec![] }
- });
+ })));
block = fall_through;
let new_type = types::NotNil;
@@ -10083,14 +10161,14 @@ fn add_iseq_to_hir(
continue;
}
seen_types.push(expected);
- let has_type = fun.push_insn(block, Insn::HasType { val: recv, expected });
let iftrue_block = fun.new_block(insn_idx);
let fall_through = fun.new_block(insn_idx);
- fun.push_insn(block, Insn::CondBranch {
- val: has_type,
+ fun.push_insn(block, Insn::CondBranchHasType(Box::new(CondBranchHasTypeData {
+ val: recv,
+ expected,
if_true: BranchEdge { target: iftrue_block, args: vec![] },
if_false: BranchEdge { target: fall_through, args: vec![] }
- });
+ })));
block = fall_through;
// Take a fresh Snapshot rather than
// reusing exit_id so type specialization resolves the receiver from
@@ -10160,14 +10238,14 @@ fn add_iseq_to_hir(
continue;
}
seen_types.push(expected);
- let has_type = fun.push_insn(block, Insn::HasType { val: recv, expected });
let iftrue_block = fun.new_block(insn_idx);
let fall_through = fun.new_block(insn_idx);
- fun.push_insn(block, Insn::CondBranch {
- val: has_type,
+ fun.push_insn(block, Insn::CondBranchHasType(Box::new(CondBranchHasTypeData {
+ val: recv,
+ expected,
if_true: BranchEdge { target: iftrue_block, args: vec![] },
if_false: BranchEdge { target: fall_through, args: vec![] }
- });
+ })));
block = fall_through;
// Take a fresh Snapshot rather than
// reusing exit_id so type specialization resolves the receiver from
@@ -10633,15 +10711,15 @@ fn add_iseq_to_hir(
fun.push_insn(block, Insn::Send { recv, cd, block: None, args: vec![], caller_splat_length: None, state: exit_id, reason: ObjToStringNotString })
}
} else {
- let has_type = fun.push_insn(block, Insn::HasType { val: recv, expected: types::String });
let iftrue_block = fun.new_block(insn_idx);
let iffalse_block = fun.new_block(insn_idx);
let join_block = fun.new_block(insn_idx);
- fun.push_insn(block, Insn::CondBranch {
- val: has_type,
+ fun.push_insn(block, Insn::CondBranchHasType(Box::new(CondBranchHasTypeData {
+ val: recv,
+ expected: types::String,
if_true: BranchEdge { target: iftrue_block, args: vec![] },
if_false: BranchEdge { target: iffalse_block, args: vec![] }
- });
+ })));
// true block
let refined = fun.push_insn(iftrue_block, Insn::RefineType { val: recv, new_type: types::String });
fun.push_insn(iftrue_block, Insn::Jump(BranchEdge { target: join_block, args: vec![refined] }));
@@ -10660,15 +10738,15 @@ fn add_iseq_to_hir(
let val = state.stack_pop()?;
// Mirror logic of rb_obj_as_string_result() (`anytostring` in insns.def)
- let has_type = fun.push_insn(block, Insn::HasType { val: str, expected: types::String });
let iftrue_block = fun.new_block(insn_idx);
let iffalse_block = fun.new_block(insn_idx);
let join_block = fun.new_block(insn_idx);
- fun.push_insn(block, Insn::CondBranch {
- val: has_type,
+ fun.push_insn(block, Insn::CondBranchHasType(Box::new(CondBranchHasTypeData {
+ val: str,
+ expected: types::String,
if_true: BranchEdge { target: iftrue_block, args: vec![] },
if_false: BranchEdge { target: iffalse_block, args: vec![] }
- });
+ })));
// true block
let refined = fun.push_insn(iftrue_block, Insn::RefineType { val: str, new_type: types::String });
fun.push_insn(iftrue_block, Insn::Jump(BranchEdge { target: join_block, args: vec![refined] }));
@@ -11512,6 +11590,47 @@ mod validation_tests {
});
}
+ // A `CondBranchHasType` whose `val` has been refined to a disjoint type is
+ // `Empty` (Bottom): an impossible value, so the branch is dead. `infer_types`
+ // uses `could_be` and so marks neither edge reachable (both params stay
+ // `Empty`), and `fold_constants` folds the branch to `Unreachable` to match.
+ #[test]
+ fn condbranchhastype_bottom_val_folds_to_unreachable() {
+ let mut function = Function::new(std::ptr::null());
+ let entry = function.entry_block;
+ let if_true = function.new_block(0);
+ let if_false = function.new_block(0);
+ let p_true = function.push_insn(if_true, Insn::Param);
+ function.push_insn(if_true, Insn::Return { val: p_true });
+ let p_false = function.push_insn(if_false, Insn::Param);
+ function.push_insn(if_false, Insn::Return { val: p_false });
+ // Refine a nil to Fixnum: NilClass ∩ Fixnum = Empty (Bottom).
+ let nil = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
+ let bottom = function.push_insn(entry, Insn::RefineType { val: nil, new_type: types::Fixnum });
+ let arg = function.push_insn(entry, Insn::Const { val: Const::Value(VALUE::fixnum_from_usize(3)) });
+ function.push_insn(entry, Insn::CondBranchHasType(Box::new(CondBranchHasTypeData {
+ val: bottom,
+ expected: types::Fixnum,
+ if_true: BranchEdge { target: if_true, args: vec![arg] },
+ if_false: BranchEdge { target: if_false, args: vec![arg] },
+ })));
+ function.seal_entries();
+ crate::cruby::with_rubyvm(|| {
+ function.infer_types();
+ assert!(function.type_of(bottom).bit_equal(types::Empty), "refine to disjoint type should be Bottom");
+ // `could_be`-only means neither edge is reachable, so both params are Empty.
+ assert!(function.type_of(p_true).bit_equal(types::Empty),
+ "if_true param should be Empty, got {}", function.type_of(p_true));
+ assert!(function.type_of(p_false).bit_equal(types::Empty),
+ "if_false param should be Empty, got {}", function.type_of(p_false));
+ // The dead branch folds to Unreachable, agreeing with infer_types.
+ function.fold_constants();
+ let last = *function.blocks[entry].insns.last().unwrap();
+ assert!(matches!(function.find_ref(last), Insn::Unreachable),
+ "expected entry terminator to fold to Unreachable");
+ });
+ }
+
#[test]
fn instruction_appears_twice_in_same_block() {
let mut function = Function::new(std::ptr::null());
diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs
index fb4d338f387b5f..544b3c1eaffd2c 100644
--- a/zjit/src/hir/opt_tests.rs
+++ b/zjit/src/hir/opt_tests.rs
@@ -1858,10 +1858,129 @@ mod hir_opt_tests {
v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
PushInlineFrame :m, v20 (0x1038), num_args=1
PatchPoint MethodRedefined(NilClass@0x1058, nil?@0x1060, cme:0x1068)
- v84:Fixnum[0] = Const Value(0)
+ v82:Fixnum[0] = Const Value(0)
PopInlineFrame
CheckInterrupts
- Return v84
+ Return v82
+ ");
+ }
+
+ // Regression test for a dead string-interpolation raise block whose receiver is
+ // refined to Bottom. Across the multi-version recompile, the polymorphic `enc.foo`
+ // guard funnels the common `mode == nil` path so `mode` freezes to a monomorphic
+ // NilClass guard upstream, while the rare string-`mode` calls have already frozen
+ // the `#{mode}` interpolation site as String-profiled. That leaves
+ // `GuardType(NilClass, String)` = Bottom feeding the CondBranchHasType on the dead
+ // `raise` block. Without folding that to Unreachable, the dead block survives,
+ // `#{uri.class}` (Module#to_s -> BasicObject) flows into a StringConcat that requires
+ // String operands, and `validate()` aborts the process during the driver's own JIT
+ // compile inside eval. With the fold the block is proved dead and eval completes,
+ // so hir_string can then compile a valid function.
+ #[test]
+ fn test_dead_string_interp_raise_block_folds_to_unreachable() {
+ // NB: 31, not 30. boot_rubyvm() resets the threshold to 2 whenever it equals
+ // DEFAULT_CALL_THRESHOLD (30), which would make profiling sample the very first
+ // (String) call and defeat the race we need.
+ set_call_threshold(31);
+ set_num_profiles(1);
+ set_max_versions(2);
+ set_inline_threshold(0);
+ eval(r#"
+ class FakeURI; end
+ class A; def foo; 1; end; end
+ class B; def foo; 2; end; end
+
+ def open_uri(uri, enc, mode)
+ enc.foo
+ unless mode == nil || mode == 'r' || mode == 'rb'
+ raise ArgumentError.new("invalid access mode #{mode} (#{uri.class} resource is read only.)")
+ end
+ :ok
+ end
+
+ u = FakeURI.new
+ encs = [A.new, B.new]
+ i = 0
+ while i < 3000
+ enc = encs[i % 2]
+ mode = (i % 31 == 0) ? "zz" : nil
+ begin; open_uri(u, enc, mode); rescue ArgumentError; end
+ i += 1
+ end
+ :done
+ "#);
+ assert_snapshot!(hir_string("open_uri"), @"
+ fn open_uri@:7:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :uri@0x1000
+ v4:BasicObject = LoadField v2, :enc@0x1001
+ v5:BasicObject = LoadField v2, :mode@0x1002
+ Jump bb3(v1, v3, v4, v5)
+ bb2():
+ EntryPoint JIT(0)
+ v8:BasicObject = LoadArg :self@0
+ v9:BasicObject = LoadArg :uri@1
+ v10:BasicObject = LoadArg :enc@2
+ v11:BasicObject = LoadArg :mode@3
+ Jump bb3(v8, v9, v10, v11)
+ bb3(v13:BasicObject, v14:BasicObject, v15:BasicObject, v16:BasicObject):
+ CondBranchHasType v15, ObjectSubclass[class_exact:A], bb8(), bb9()
+ bb8():
+ PatchPoint NoSingletonClass(A@0x1008)
+ PatchPoint MethodRedefined(A@0x1008, foo@0x1010, cme:0x1018)
+ v167:Fixnum[1] = Const Value(1)
+ Jump bb7(v167)
+ bb9():
+ CondBranchHasType v15, ObjectSubclass[class_exact:B], bb10(), bb11()
+ bb10():
+ PatchPoint NoSingletonClass(B@0x1040)
+ PatchPoint MethodRedefined(B@0x1040, foo@0x1010, cme:0x1048)
+ v170:Fixnum[2] = Const Value(2)
+ Jump bb7(v170)
+ bb11():
+ v32:BasicObject = Send v15, :foo # SendFallbackReason: Send: polymorphic fallback
+ Jump bb7(v32)
+ bb7(v21:BasicObject):
+ PatchPoint NoEPEscape(open_uri)
+ v40:NilClass = Const Value(nil)
+ PatchPoint MethodRedefined(NilClass@0x1070, ==@0x1078, cme:0x1080)
+ v173:NilClass = GuardType v16, NilClass recompile
+ v174:CBool = IsBitEqual v173, v40
+ CondBranch v174, bb6(), bb12()
+ bb12():
+ v51:StringExact[VALUE(0x10a8)] = Const Value(VALUE(0x10a8))
+ v52:StringExact = StringCopy v51
+ PatchPoint NoSingletonClass(String@0x10b0)
+ PatchPoint MethodRedefined(String@0x10b0, ==@0x1078, cme:0x10b8)
+ v179 = GuardType v173, StringExact recompile
+ v180:BoolExact = StringEqual v179, v52
+ v57:CBool = Test v180
+ CondBranch v57, bb6(), bb13()
+ bb13():
+ v63:StringExact[VALUE(0x10e0)] = Const Value(VALUE(0x10e0))
+ v64:StringExact = StringCopy v63
+ PatchPoint NoSingletonClass(String@0x10b0)
+ PatchPoint MethodRedefined(String@0x10b0, ==@0x1078, cme:0x10b8)
+ v184 = GuardType v173, StringExact recompile
+ v185:BoolExact = StringEqual v184, v64
+ v69:CBool = Test v185
+ CondBranch v69, bb6(), bb14()
+ bb6():
+ v133:StaticSymbol[:ok] = Const Value(VALUE(0x10e8))
+ CheckInterrupts
+ Return v133
+ bb14():
+ v76:NilClass = Const Value(nil)
+ PatchPoint StableConstantNames(0x10f0, ArgumentError)
+ v79:ClassSubclass[ArgumentError@0x10f8] = Const Value(VALUE(0x10f8))
+ v81:StringExact[VALUE(0x1100)] = Const Value(VALUE(0x1100))
+ PatchPoint NoEPEscape(open_uri)
+ PatchPoint NoSingletonClass(String@0x10b0)
+ v88 = GuardType v173, String
+ Unreachable
");
}
@@ -8889,9 +9008,9 @@ mod hir_opt_tests {
v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
v13:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
v14:StringExact = StringCopy v13
- v34:StringExact = StringConcat v10, v14
+ v32:StringExact = StringConcat v10, v14
CheckInterrupts
- Return v34
+ Return v32
");
}
@@ -8914,10 +9033,10 @@ mod hir_opt_tests {
v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
v12:Fixnum[1] = Const Value(1)
PatchPoint MethodRedefined(Integer@0x1008, to_s@0x1010, cme:0x1018)
- v40:StringExact = CCallVariadic v12, :Integer#to_s@0x1040
- v32:StringExact = StringConcat v10, v40
+ v38:StringExact = CCallVariadic v12, :Integer#to_s@0x1040
+ v30:StringExact = StringConcat v10, v38
CheckInterrupts
- Return v32
+ Return v30
");
}
@@ -8947,9 +9066,9 @@ mod hir_opt_tests {
v14:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint NoSingletonClass(String@0x1010)
v19:String = GuardType v10, String
- v29:StringExact = StringConcat v14, v19
+ v28:StringExact = StringConcat v14, v19
CheckInterrupts
- Return v29
+ Return v28
");
}
@@ -8982,9 +9101,9 @@ mod hir_opt_tests {
v14:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint NoSingletonClass(MyString@0x1010)
v19:String = GuardType v10, String
- v29:StringExact = StringConcat v14, v19
+ v28:StringExact = StringConcat v14, v19
CheckInterrupts
- Return v29
+ Return v28
");
}
@@ -9015,19 +9134,18 @@ mod hir_opt_tests {
v18:ArrayExact = GuardType v10, ArrayExact
PatchPoint NoSingletonClass(Array@0x1010)
PatchPoint MethodRedefined(Array@0x1010, to_s@0x1018, cme:0x1020)
- v39:BasicObject = CCallWithFrame v18, :Array#to_s@0x1048
- v21:CBool = HasType v39, String
- CondBranch v21, bb4(), bb5()
+ v38:BasicObject = CCallWithFrame v18, :Array#to_s@0x1048
+ CondBranchHasType v38, String, bb4(), bb5()
bb4():
- v23:String = RefineType v39, String
- Jump bb6(v23)
+ v22:String = RefineType v38, String
+ Jump bb6(v22)
bb5():
- v25:StringExact = AnyToString v18
- Jump bb6(v25)
- bb6(v27:String):
- v29:StringExact = StringConcat v14, v27
+ v24:StringExact = AnyToString v18
+ Jump bb6(v24)
+ bb6(v26:String):
+ v28:StringExact = StringConcat v14, v26
CheckInterrupts
- Return v29
+ Return v28
");
}
@@ -9051,10 +9169,10 @@ mod hir_opt_tests {
v5:BasicObject = LoadArg :self@0
Jump bb3(v5)
bb3(v8:BasicObject):
- v36:NilClass = Const Value(nil)
- v20:NilClass = Const Value(nil)
+ v35:NilClass = Const Value(nil)
+ v19:NilClass = Const Value(nil)
CheckInterrupts
- Return v20
+ Return v19
");
}
@@ -9078,13 +9196,13 @@ mod hir_opt_tests {
v5:BasicObject = LoadArg :self@0
Jump bb3(v5)
bb3(v8:BasicObject):
- v37:NilClass = Const Value(nil)
+ v36:NilClass = Const Value(nil)
v13:Fixnum[1] = Const Value(1)
PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010)
- v39:Fixnum[1] = Const Value(1)
v38:Fixnum[1] = Const Value(1)
+ v37:Fixnum[1] = Const Value(1)
CheckInterrupts
- Return v39
+ Return v38
");
}
@@ -10012,22 +10130,20 @@ mod hir_opt_tests {
v27:NilClass = Const Value(nil)
Jump bb5(v16, v27)
bb5(v30:BasicObject, v31:Falsy):
- v36:CBool = HasType v31, FalseClass
- CondBranch v36, bb8(), bb9()
+ CondBranchHasType v31, FalseClass, bb8(), bb9()
bb8():
PatchPoint MethodRedefined(FalseClass@0x1008, !@0x1010, cme:0x1018)
- v57:TrueClass = Const Value(true)
- Jump bb7(v57)
+ v55:TrueClass = Const Value(true)
+ Jump bb7(v55)
bb9():
- v42:CBool = HasType v31, NilClass
- CondBranch v42, bb10(), bb11()
+ CondBranchHasType v31, NilClass, bb10(), bb11()
bb10():
PatchPoint MethodRedefined(NilClass@0x1040, !@0x1010, cme:0x1018)
- v60:TrueClass = Const Value(true)
- Jump bb7(v60)
+ v58:TrueClass = Const Value(true)
+ Jump bb7(v58)
bb11():
- v48:BasicObject = Send v31, :! # SendFallbackReason: Send: polymorphic fallback
- Jump bb7(v48)
+ v46:BasicObject = Send v31, :! # SendFallbackReason: Send: polymorphic fallback
+ Jump bb7(v46)
bb7(v35:BasicObject):
CheckInterrupts
Return v35
@@ -10805,16 +10921,15 @@ mod hir_opt_tests {
Jump bb3(v6, v7)
bb3(v9:HeapBasicObject, v10:BasicObject):
v17:Fixnum[5] = Const Value(5)
- v21:CBool = HasType v10, ObjectSubclass[class_exact:C]
- CondBranch v21, bb5(), bb6()
+ CondBranchHasType v10, ObjectSubclass[class_exact:C], bb5(), bb6()
bb5():
- v24:ObjectSubclass[class_exact:C] = RefineType v10, ObjectSubclass[class_exact:C]
+ v23:ObjectSubclass[class_exact:C] = RefineType v10, ObjectSubclass[class_exact:C]
PatchPoint MethodRedefined(C@0x1008, foo=@0x1010, cme:0x1018)
- SetIvar v24, :@foo, v17
+ SetIvar v23, :@foo, v17
Jump bb4(v17)
bb6():
- v27:BasicObject = Send v10, :foo=, v17 # SendFallbackReason: Send: polymorphic fallback
- Jump bb4(v27)
+ v26:BasicObject = Send v10, :foo=, v17 # SendFallbackReason: Send: polymorphic fallback
+ Jump bb4(v26)
bb4(v20:BasicObject):
CheckInterrupts
Return v17
@@ -11471,17 +11586,16 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- v16:CBool = HasType v10, ObjectSubclass[class_exact:C]
- CondBranch v16, bb5(), bb6()
+ CondBranchHasType v10, ObjectSubclass[class_exact:C], bb5(), bb6()
bb5():
- v19:ObjectSubclass[class_exact:C] = RefineType v10, ObjectSubclass[class_exact:C]
+ v18:ObjectSubclass[class_exact:C] = RefineType v10, ObjectSubclass[class_exact:C]
PatchPoint NoSingletonClass(C@0x1008)
PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018)
- v31:BasicObject = GetIvar v19, :@foo
- Jump bb4(v31)
+ v30:BasicObject = GetIvar v18, :@foo
+ Jump bb4(v30)
bb6():
- v22:BasicObject = Send v10, :foo # SendFallbackReason: Send: polymorphic fallback
- Jump bb4(v22)
+ v21:BasicObject = Send v10, :foo # SendFallbackReason: Send: polymorphic fallback
+ Jump bb4(v21)
bb4(v15:BasicObject):
CheckInterrupts
Return v15
@@ -12365,6 +12479,37 @@ mod hir_opt_tests {
");
}
+ #[test]
+ fn test_specialize_struct_new_generates_object_alloc_class() {
+ eval(r#"
+ C = Struct.new
+ def test = C.new
+ test
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:3:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb3(v1)
+ bb2():
+ EntryPoint JIT(0)
+ v4:BasicObject = LoadArg :self@0
+ Jump bb3(v4)
+ bb3(v6:BasicObject):
+ v10:NilClass = Const Value(nil)
+ PatchPoint StableConstantNames(0x1000, C)
+ v13:ClassSubclass[C@0x1008] = Const Value(VALUE(0x1008))
+ PatchPoint MethodRedefined(C@0x1008, new@0x1009, cme:0x1010)
+ v42:ObjectSubclass[class_exact:C] = ObjectAllocClass C:VALUE(0x1008)
+ PatchPoint NoSingletonClass(C@0x1008)
+ PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040)
+ v47:BasicObject = CCallVariadic v42, :Struct#initialize@0x1068
+ CheckInterrupts
+ Return v42
+ ");
+ }
+
#[test]
fn test_inline_struct_aref_embedded() {
eval(r#"
@@ -12774,10 +12919,10 @@ mod hir_opt_tests {
v14:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
v18:Fixnum = GuardType v10, Fixnum
PatchPoint MethodRedefined(Integer@0x1010, to_s@0x1018, cme:0x1020)
- v38:StringExact = CCallVariadic v18, :Integer#to_s@0x1048
- v29:StringExact = StringConcat v14, v38
+ v37:StringExact = CCallVariadic v18, :Integer#to_s@0x1048
+ v28:StringExact = StringConcat v14, v37
CheckInterrupts
- Return v29
+ Return v28
");
}
@@ -15645,44 +15790,42 @@ mod hir_opt_tests {
Jump bb3(v7, v8, v9)
bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject):
v19:ArrayExact = ToArray v13
- v22:CBool = HasType v12, ObjectSubclass[class_exact:CallerSplatA]
- CondBranch v22, bb5(), bb6()
+ CondBranchHasType v12, ObjectSubclass[class_exact:CallerSplatA], bb5(), bb6()
bb5():
- v25:ObjectSubclass[class_exact:CallerSplatA] = RefineType v12, ObjectSubclass[class_exact:CallerSplatA]
+ v24:ObjectSubclass[class_exact:CallerSplatA] = RefineType v12, ObjectSubclass[class_exact:CallerSplatA]
PatchPoint NoSingletonClass(CallerSplatA@0x1008)
- v42:CInt64 = ArrayLength v19
- v43:CInt64[1] = GuardBitEquals v42, CInt64(1) recompile
- v44:CInt64 = CCall v19, :rb_jit_ruby2_keywords_splat_p@0x1010
- v45:CInt64[0] = GuardBitEquals v44, CInt64(0)
+ v40:CInt64 = ArrayLength v19
+ v41:CInt64[1] = GuardBitEquals v40, CInt64(1) recompile
+ v42:CInt64 = CCall v19, :rb_jit_ruby2_keywords_splat_p@0x1010
+ v43:CInt64[0] = GuardBitEquals v42, CInt64(0)
PatchPoint MethodRedefined(CallerSplatA@0x1008, target@0x1011, cme:0x1018)
- v47:CInt64[0] = Const CInt64(0)
- v48:BasicObject = ArrayAref v19, v47
- v49:ArrayExact = NewArray v48
- PushInlineFrame :target, v25 (0x1040), num_args=1
+ v45:CInt64[0] = Const CInt64(0)
+ v46:BasicObject = ArrayAref v19, v45
+ v47:ArrayExact = NewArray v46
+ PushInlineFrame :target, v24 (0x1040), num_args=1
CheckInterrupts
PopInlineFrame
- Jump bb4(v49)
+ Jump bb4(v47)
bb6():
- v28:CBool = HasType v12, ObjectSubclass[class_exact:CallerSplatB]
- CondBranch v28, bb7(), bb8()
+ CondBranchHasType v12, ObjectSubclass[class_exact:CallerSplatB], bb7(), bb8()
bb7():
- v31:ObjectSubclass[class_exact:CallerSplatB] = RefineType v12, ObjectSubclass[class_exact:CallerSplatB]
+ v29:ObjectSubclass[class_exact:CallerSplatB] = RefineType v12, ObjectSubclass[class_exact:CallerSplatB]
PatchPoint NoSingletonClass(CallerSplatB@0x1060)
- v53:CInt64 = ArrayLength v19
- v54:CInt64[1] = GuardBitEquals v53, CInt64(1) recompile
- v55:CInt64 = CCall v19, :rb_jit_ruby2_keywords_splat_p@0x1010
- v56:CInt64[0] = GuardBitEquals v55, CInt64(0)
+ v51:CInt64 = ArrayLength v19
+ v52:CInt64[1] = GuardBitEquals v51, CInt64(1) recompile
+ v53:CInt64 = CCall v19, :rb_jit_ruby2_keywords_splat_p@0x1010
+ v54:CInt64[0] = GuardBitEquals v53, CInt64(0)
PatchPoint MethodRedefined(CallerSplatB@0x1060, target@0x1011, cme:0x1068)
- v58:CInt64[0] = Const CInt64(0)
- v59:BasicObject = ArrayAref v19, v58
- v60:ArrayExact = NewArray v59
- PushInlineFrame :target, v31 (0x1090), num_args=1
+ v56:CInt64[0] = Const CInt64(0)
+ v57:BasicObject = ArrayAref v19, v56
+ v58:ArrayExact = NewArray v57
+ PushInlineFrame :target, v29 (0x1090), num_args=1
CheckInterrupts
PopInlineFrame
- Jump bb4(v60)
+ Jump bb4(v58)
bb8():
- v34:BasicObject = Send v12, :target, v19 # SendFallbackReason: Send: polymorphic fallback
- Jump bb4(v34)
+ v32:BasicObject = Send v12, :target, v19 # SendFallbackReason: Send: polymorphic fallback
+ Jump bb4(v32)
bb4(v21:BasicObject):
CheckInterrupts
Return v21
@@ -17381,23 +17524,21 @@ mod hir_opt_tests {
bb3(v9:BasicObject, v10:BasicObject):
PatchPoint StableConstantNames(0x1008, String)
v16:ClassSubclass[String@0x1010] = Const Value(VALUE(0x1010))
- v19:CBool = HasType v10, Fixnum
- CondBranch v19, bb5(), bb6()
+ CondBranchHasType v10, Fixnum, bb5(), bb6()
bb5():
PatchPoint MethodRedefined(Integer@0x1018, is_a?@0x1020, cme:0x1028)
- v45:FalseClass = Const Value(false)
- Jump bb4(v45)
+ v43:FalseClass = Const Value(false)
+ Jump bb4(v43)
bb6():
- v25:CBool = HasType v10, StringExact
- CondBranch v25, bb7(), bb8()
+ CondBranchHasType v10, StringExact, bb7(), bb8()
bb7():
PatchPoint NoSingletonClass(String@0x1010)
PatchPoint MethodRedefined(String@0x1010, is_a?@0x1020, cme:0x1028)
- v46:TrueClass = Const Value(true)
- Jump bb4(v46)
+ v44:TrueClass = Const Value(true)
+ Jump bb4(v44)
bb8():
- v31:BasicObject = Send v10, :is_a?, v16 # SendFallbackReason: Send: polymorphic fallback
- Jump bb4(v31)
+ v29:BasicObject = Send v10, :is_a?, v16 # SendFallbackReason: Send: polymorphic fallback
+ Jump bb4(v29)
bb4(v18:BasicObject):
CheckInterrupts
Return v18
@@ -18146,14 +18287,15 @@ mod hir_opt_tests {
}
#[test]
- fn test_print_nil_module_name() {
+ fn test_fold_class_superclass() {
eval(r#"
- X = [Module.new].freeze
- def test = X[0]
+ class A; end
+ class B < A; end
+ def test = B.superclass
test
"#);
assert_snapshot!(hir_string("test"), @"
- fn test@:3:
+ fn test@:4:
bb1():
EntryPoint interpreter
v1:BasicObject = LoadSelf
@@ -18163,111 +18305,23 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint StableConstantNames(0x1000, X)
- v11:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
- v13:Fixnum[0] = Const Value(0)
- PatchPoint NoSingletonClass(Array@0x1010)
- PatchPoint MethodRedefined(Array@0x1010, []@0x1018, cme:0x1020)
- v35:ModuleExact[VALUE(0x1048)] = Const Value(VALUE(0x1048))
+ PatchPoint StableConstantNames(0x1000, B)
+ v11:ClassSubclass[B@0x1008] = Const Value(VALUE(0x1008))
+ PatchPoint MethodRedefined(Class@0x1010, superclass@0x1018, cme:0x1020)
+ v22:ClassSubclass[A@0x1048] = Const Value(VALUE(0x1048))
CheckInterrupts
- Return v35
+ Return v22
");
}
#[test]
- fn no_load_from_ep_right_after_entrypoint() {
- let formatted = eval("
- def read_nil_local(a, _b, _c)
- formatted ||= a
- @formatted = formatted
- -> { formatted } # the environment escapes
- end
-
- def call
- puts [], [], [], [] # fill VM stack with junk
- read_nil_local(true, 1, 1) # expected SendDirect
- end
-
- call # profile
- call # compile
- @formatted
- ");
- assert_eq!(Qtrue, formatted, "{}", formatted.obj_info());
- assert_snapshot!(hir_string("read_nil_local"), @"
- fn read_nil_local@:3:
- bb1():
- EntryPoint interpreter
- v1:BasicObject = LoadSelf
- v2:CPtr = LoadSP
- v3:BasicObject = LoadField v2, :a@0x1000
- v4:BasicObject = LoadField v2, :_b@0x1001
- v5:BasicObject = LoadField v2, :_c@0x1002
- Jump bb3(v1, v3, v4, v5)
- bb2():
- EntryPoint JIT(0)
- v9:BasicObject = LoadArg :self@0
- v10:BasicObject = LoadArg :a@1
- v11:CPtr = GetEP 0
- StoreField v11, :a@0x1001, v10
- v13:BasicObject = LoadArg :_b@2
- StoreField v11, :_b@0x1002, v13
- v15:BasicObject = LoadArg :_c@3
- StoreField v11, :_c@0x1003, v15
- v17:NilClass = Const Value(nil)
- StoreField v11, :formatted@0x1004, v17
- Jump bb3(v9, v10, v13, v15)
- bb3(v20:BasicObject, v21:BasicObject, v22:BasicObject, v23:BasicObject):
- v81:NilClass = Const Value(nil)
- SetLocal :formatted, l0, EP@3, v21
- v46:HeapBasicObject = GuardType v20, HeapBasicObject
- v47:CShape = LoadField v46, :shape_id@0x1005
- v48:CShape[0x1006] = Const CShape(0x1006)
- v49:CBool = IsBitEqual v47, v48
- CondBranch v49, bb7(), bb8()
- bb7():
- StoreField v46, :@formatted@0x1007, v21
- WriteBarrier v46, v21
- Jump bb6()
- bb8():
- v54:CShape[0x1008] = GuardBitEquals v47, CShape(0x1008) recompile
- StoreField v46, :@formatted@0x1007, v21
- WriteBarrier v46, v21
- v58:CShape[0x1006] = Const CShape(0x1006)
- StoreField v46, :shape_id@0x1005, v58
- Jump bb6()
- bb6():
- v64:ClassSubclass[VMFrozenCore] = Const Value(VALUE(0x1010))
- PatchPoint MethodRedefined(Class@0x1018, lambda@0x1020, cme:0x1028)
- v80:BasicObject = CCallWithFrame v64, :RubyVM::FrozenCore.lambda@0x1050, block=0x1058
- v67:CPtr = GetEP 0
- v68:BasicObject = LoadField v67, :a@0x1001
- v69:BasicObject = LoadField v67, :_b@0x1002
- v70:BasicObject = LoadField v67, :_c@0x1003
- v71:BasicObject = LoadField v67, :formatted@0x1004
- CheckInterrupts
- Return v80
- ");
- }
-
- #[test]
- fn test_fold_load_field_frozen_constant_object() {
- // Basic case: frozen constant object with attr_accessor
- eval("
- class TestFrozen
- attr_accessor :a
- def initialize
- @a = 1
- end
- end
-
- FROZEN_OBJ = TestFrozen.new.freeze
-
- def test = FROZEN_OBJ.a
- test
+ fn test_fold_basic_object_superclass() {
+ eval(r#"
+ def test = BasicObject.superclass
test
- ");
+ "#);
assert_snapshot!(hir_string("test"), @"
- fn test@:11:
+ fn test@:2:
bb1():
EntryPoint interpreter
v1:BasicObject = LoadSelf
@@ -18277,37 +18331,28 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint StableConstantNames(0x1000, FROZEN_OBJ)
- v11:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
- PatchPoint NoSingletonClass(TestFrozen@0x1010)
- PatchPoint MethodRedefined(TestFrozen@0x1010, a@0x1018, cme:0x1020)
- v27:Fixnum[1] = Const Value(1)
+ PatchPoint StableConstantNames(0x1000, BasicObject)
+ v11:ClassSubclass[BasicObject@0x1008] = Const Value(VALUE(0x1008))
+ PatchPoint MethodRedefined(Class@0x1010, superclass@0x1018, cme:0x1020)
+ v22:NilClass = Const Value(nil)
CheckInterrupts
- Return v27
+ Return v22
");
}
#[test]
- fn test_fold_load_field_frozen_multiple_ivars() {
- // Frozen object with multiple instance variables
- eval("
- class TestMultiIvars
- attr_accessor :a, :b, :c
- def initialize
- @a = 10
- @b = 20
- @c = 30
- end
+ fn test_fold_class_superclass_skips_prepended_module() {
+ eval(r#"
+ class A; end
+ module M; end
+ class B < A
+ prepend M
end
-
- MULTI_FROZEN = TestMultiIvars.new.freeze
-
- def test = MULTI_FROZEN.b
- test
+ def test = B.superclass
test
- ");
+ "#);
assert_snapshot!(hir_string("test"), @"
- fn test@:13:
+ fn test@:7:
bb1():
EntryPoint interpreter
v1:BasicObject = LoadSelf
@@ -18317,29 +18362,689 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint StableConstantNames(0x1000, MULTI_FROZEN)
- v11:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
- PatchPoint NoSingletonClass(TestMultiIvars@0x1010)
- PatchPoint MethodRedefined(TestMultiIvars@0x1010, b@0x1018, cme:0x1020)
- v27:Fixnum[20] = Const Value(20)
+ PatchPoint StableConstantNames(0x1000, B)
+ v11:ClassSubclass[B@0x1008] = Const Value(VALUE(0x1008))
+ PatchPoint MethodRedefined(Class@0x1010, superclass@0x1018, cme:0x1020)
+ v22:ClassSubclass[A@0x1048] = Const Value(VALUE(0x1048))
CheckInterrupts
- Return v27
+ Return v22
");
}
#[test]
- fn test_fold_load_field_frozen_string_value() {
- // Frozen object with a string ivar
+ fn test_fold_singleton_class_superclass() {
eval(r#"
- class TestFrozenStr
- attr_accessor :name
- def initialize
- @name = "hello"
- end
- end
-
- FROZEN_STR = TestFrozenStr.new.freeze
-
+ class C; end
+ C1 = C.new.singleton_class.singleton_class
+ def test = C1.superclass
+ test
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:4:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb3(v1)
+ bb2():
+ EntryPoint JIT(0)
+ v4:BasicObject = LoadArg :self@0
+ Jump bb3(v4)
+ bb3(v6:BasicObject):
+ PatchPoint StableConstantNames(0x1000, C1)
+ v11:ClassSubclass[Class@0x1008] = Const Value(VALUE(0x1008))
+ PatchPoint MethodRedefined(Class@0x1010, superclass@0x1018, cme:0x1020)
+ v22:ClassSubclass[Class@0x1048] = Const Value(VALUE(0x1048))
+ CheckInterrupts
+ Return v22
+ ");
+ }
+
+ #[test]
+ fn test_dont_fold_uninitialized_class_superclass() {
+ eval(r#"
+ C = Class.allocate
+ def test = C.superclass
+ begin; test; rescue TypeError; end
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:3:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb3(v1)
+ bb2():
+ EntryPoint JIT(0)
+ v4:BasicObject = LoadArg :self@0
+ Jump bb3(v4)
+ bb3(v6:BasicObject):
+ PatchPoint StableConstantNames(0x1000, C)
+ v11:ClassExact[C@0x1008] = Const Value(VALUE(0x1008))
+ PatchPoint NoSingletonClass(Class@0x1010)
+ PatchPoint MethodRedefined(Class@0x1010, superclass@0x1018, cme:0x1020)
+ v23:NilClass|Class = CCallWithFrame v11, :Class#superclass@0x1048
+ CheckInterrupts
+ Return v23
+ ");
+ }
+
+ #[test]
+ fn test_dont_fold_uninitialized_class_with_included_module_superclass() {
+ // include sets RCLASS_SUPER to the module's ICLASS but leaves the superclasses array
+ // unbuilt, so Class#superclass still raises TypeError; make sure we don't fold.
+ // Call test before the include: interpreted Class#superclass has a (bogus) assertion
+ // that RCLASS_SUPER is unset whenever the superclasses array is, which aborts dev
+ // builds after the include. Compilation happens at hir_string time, after it.
+ eval(r#"
+ module M; end
+ C = Class.allocate
+ def test = C.superclass
+ begin; test; rescue TypeError; end
+ C.include M
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:4:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb3(v1)
+ bb2():
+ EntryPoint JIT(0)
+ v4:BasicObject = LoadArg :self@0
+ Jump bb3(v4)
+ bb3(v6:BasicObject):
+ PatchPoint StableConstantNames(0x1000, C)
+ v11:ClassExact[C@0x1008] = Const Value(VALUE(0x1008))
+ PatchPoint NoSingletonClass(Class@0x1010)
+ PatchPoint MethodRedefined(Class@0x1010, superclass@0x1018, cme:0x1020)
+ v23:NilClass|Class = CCallWithFrame v11, :Class#superclass@0x1048
+ CheckInterrupts
+ Return v23
+ ");
+ }
+
+ #[test]
+ fn test_dont_fold_unknown_receiver_superclass() {
+ eval(r#"
+ def test(c) = c.superclass
+ test(String)
+ test(String)
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :c@0x1000
+ Jump bb3(v1, v3)
+ bb2():
+ EntryPoint JIT(0)
+ v6:BasicObject = LoadArg :self@0
+ v7:BasicObject = LoadArg :c@1
+ Jump bb3(v6, v7)
+ bb3(v9:BasicObject, v10:BasicObject):
+ PatchPoint MethodRedefined(Class@0x1008, superclass@0x1010, cme:0x1018)
+ v23:ClassSubclass[class_exact*:Class@VALUE(0x1008)] = GuardType v10, ClassSubclass[class_exact*:Class@VALUE(0x1008)] recompile
+ v24:NilClass|Class = CCallWithFrame v23, :Class#superclass@0x1040
+ CheckInterrupts
+ Return v24
+ ");
+ }
+
+ #[test]
+ fn test_fold_profiled_receiver_class_superclass() {
+ eval(r#"
+ def test(o) = o.class.superclass
+ test("abc")
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :o@0x1000
+ Jump bb3(v1, v3)
+ bb2():
+ EntryPoint JIT(0)
+ v6:BasicObject = LoadArg :self@0
+ v7:BasicObject = LoadArg :o@1
+ Jump bb3(v6, v7)
+ bb3(v9:BasicObject, v10:BasicObject):
+ PatchPoint NoSingletonClass(String@0x1008)
+ PatchPoint MethodRedefined(String@0x1008, class@0x1010, cme:0x1018)
+ v25:StringExact = GuardType v10, StringExact recompile
+ v26:ClassSubclass[String@0x1008] = Const Value(VALUE(0x1008))
+ PatchPoint MethodRedefined(Class@0x1040, superclass@0x1048, cme:0x1050)
+ v30:ClassSubclass[Object@0x1078] = Const Value(VALUE(0x1078))
+ CheckInterrupts
+ Return v30
+ ");
+ }
+
+ #[test]
+ fn test_elide_kernel_dup_fixnum() {
+ eval(r#"
+ def test(o) = o.dup
+ test(3)
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :o@0x1000
+ Jump bb3(v1, v3)
+ bb2():
+ EntryPoint JIT(0)
+ v6:BasicObject = LoadArg :self@0
+ v7:BasicObject = LoadArg :o@1
+ Jump bb3(v6, v7)
+ bb3(v9:BasicObject, v10:BasicObject):
+ PatchPoint MethodRedefined(Integer@0x1008, dup@0x1010, cme:0x1018)
+ v22:Fixnum = GuardType v10, Fixnum recompile
+ CheckInterrupts
+ Return v22
+ ");
+ }
+
+ #[test]
+ fn test_elide_kernel_dup_bignum() {
+ eval(r#"
+ def test(o) = o.dup
+ test(300000000000000000000000000000000000000000000000000000000000000)
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :o@0x1000
+ Jump bb3(v1, v3)
+ bb2():
+ EntryPoint JIT(0)
+ v6:BasicObject = LoadArg :self@0
+ v7:BasicObject = LoadArg :o@1
+ Jump bb3(v6, v7)
+ bb3(v9:BasicObject, v10:BasicObject):
+ PatchPoint MethodRedefined(Integer@0x1008, dup@0x1010, cme:0x1018)
+ v22:Bignum = GuardType v10, Bignum recompile
+ CheckInterrupts
+ Return v22
+ ");
+ }
+
+ #[test]
+ fn test_elide_kernel_dup_nil() {
+ eval(r#"
+ def test(o) = o.dup
+ test(nil)
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :o@0x1000
+ Jump bb3(v1, v3)
+ bb2():
+ EntryPoint JIT(0)
+ v6:BasicObject = LoadArg :self@0
+ v7:BasicObject = LoadArg :o@1
+ Jump bb3(v6, v7)
+ bb3(v9:BasicObject, v10:BasicObject):
+ PatchPoint MethodRedefined(NilClass@0x1008, dup@0x1010, cme:0x1018)
+ v23:NilClass = GuardType v10, NilClass recompile
+ CheckInterrupts
+ Return v23
+ ");
+ }
+
+ #[test]
+ fn test_elide_kernel_dup_true() {
+ eval(r#"
+ def test(o) = o.dup
+ test(true)
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :o@0x1000
+ Jump bb3(v1, v3)
+ bb2():
+ EntryPoint JIT(0)
+ v6:BasicObject = LoadArg :self@0
+ v7:BasicObject = LoadArg :o@1
+ Jump bb3(v6, v7)
+ bb3(v9:BasicObject, v10:BasicObject):
+ PatchPoint MethodRedefined(TrueClass@0x1008, dup@0x1010, cme:0x1018)
+ v23:TrueClass = GuardType v10, TrueClass recompile
+ CheckInterrupts
+ Return v23
+ ");
+ }
+
+ #[test]
+ fn test_elide_kernel_dup_false() {
+ eval(r#"
+ def test(o) = o.dup
+ test(false)
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :o@0x1000
+ Jump bb3(v1, v3)
+ bb2():
+ EntryPoint JIT(0)
+ v6:BasicObject = LoadArg :self@0
+ v7:BasicObject = LoadArg :o@1
+ Jump bb3(v6, v7)
+ bb3(v9:BasicObject, v10:BasicObject):
+ PatchPoint MethodRedefined(FalseClass@0x1008, dup@0x1010, cme:0x1018)
+ v23:FalseClass = GuardType v10, FalseClass recompile
+ CheckInterrupts
+ Return v23
+ ");
+ }
+
+ #[test]
+ fn test_elide_kernel_dup_static_symbol() {
+ eval(r#"
+ def test(o) = o.dup
+ test(:foo)
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :o@0x1000
+ Jump bb3(v1, v3)
+ bb2():
+ EntryPoint JIT(0)
+ v6:BasicObject = LoadArg :self@0
+ v7:BasicObject = LoadArg :o@1
+ Jump bb3(v6, v7)
+ bb3(v9:BasicObject, v10:BasicObject):
+ PatchPoint MethodRedefined(Symbol@0x1008, dup@0x1010, cme:0x1018)
+ v23:StaticSymbol = GuardType v10, StaticSymbol recompile
+ CheckInterrupts
+ Return v23
+ ");
+ }
+
+ #[test]
+ fn test_elide_kernel_dup_dynamic_symbol() {
+ eval(r#"
+ def test(o) = o.dup
+ test(:"v#{1 + 1}")
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :o@0x1000
+ Jump bb3(v1, v3)
+ bb2():
+ EntryPoint JIT(0)
+ v6:BasicObject = LoadArg :self@0
+ v7:BasicObject = LoadArg :o@1
+ Jump bb3(v6, v7)
+ bb3(v9:BasicObject, v10:BasicObject):
+ PatchPoint MethodRedefined(Symbol@0x1008, dup@0x1010, cme:0x1018)
+ v23:DynamicSymbol = GuardType v10, DynamicSymbol recompile
+ CheckInterrupts
+ Return v23
+ ");
+ }
+
+ #[test]
+ fn test_elide_kernel_dup_static_flonum() {
+ eval(r#"
+ def test(o) = o.dup
+ test(0.0)
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :o@0x1000
+ Jump bb3(v1, v3)
+ bb2():
+ EntryPoint JIT(0)
+ v6:BasicObject = LoadArg :self@0
+ v7:BasicObject = LoadArg :o@1
+ Jump bb3(v6, v7)
+ bb3(v9:BasicObject, v10:BasicObject):
+ PatchPoint MethodRedefined(Float@0x1008, dup@0x1010, cme:0x1018)
+ v22:Flonum = GuardType v10, Flonum recompile
+ CheckInterrupts
+ Return v22
+ ");
+ }
+
+ #[test]
+ fn test_elide_kernel_dup_bigfloat() {
+ eval(r#"
+ def test(o) = o.dup
+ test(3000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0)
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :o@0x1000
+ Jump bb3(v1, v3)
+ bb2():
+ EntryPoint JIT(0)
+ v6:BasicObject = LoadArg :self@0
+ v7:BasicObject = LoadArg :o@1
+ Jump bb3(v6, v7)
+ bb3(v9:BasicObject, v10:BasicObject):
+ PatchPoint MethodRedefined(Float@0x1008, dup@0x1010, cme:0x1018)
+ v22:HeapFloat = GuardType v10, HeapFloat recompile
+ CheckInterrupts
+ Return v22
+ ");
+ }
+
+ #[test]
+ fn test_elide_kernel_dup_rational() {
+ eval(r#"
+ def test(o) = o.dup
+ test(1.to_r)
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :o@0x1000
+ Jump bb3(v1, v3)
+ bb2():
+ EntryPoint JIT(0)
+ v6:BasicObject = LoadArg :self@0
+ v7:BasicObject = LoadArg :o@1
+ Jump bb3(v6, v7)
+ bb3(v9:BasicObject, v10:BasicObject):
+ PatchPoint NoSingletonClass(Rational@0x1008)
+ PatchPoint MethodRedefined(Rational@0x1008, dup@0x1010, cme:0x1018)
+ v23:NumericSubclass[class_exact:Rational] = GuardType v10, NumericSubclass[class_exact:Rational] recompile
+ CheckInterrupts
+ Return v23
+ ");
+ }
+
+ #[test]
+ fn test_elide_kernel_dup_complex() {
+ eval(r#"
+ def test(o) = o.dup
+ test(Complex.rect(3, 4))
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :o@0x1000
+ Jump bb3(v1, v3)
+ bb2():
+ EntryPoint JIT(0)
+ v6:BasicObject = LoadArg :self@0
+ v7:BasicObject = LoadArg :o@1
+ Jump bb3(v6, v7)
+ bb3(v9:BasicObject, v10:BasicObject):
+ PatchPoint NoSingletonClass(Complex@0x1008)
+ PatchPoint MethodRedefined(Complex@0x1008, dup@0x1010, cme:0x1018)
+ v23:NumericSubclass[class_exact:Complex] = GuardType v10, NumericSubclass[class_exact:Complex] recompile
+ CheckInterrupts
+ Return v23
+ ");
+ }
+
+ #[test]
+ fn test_no_elide_kernel_dup_heap_object() {
+ eval(r#"
+ def test(o) = o.dup
+ test(Object.new)
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :o@0x1000
+ Jump bb3(v1, v3)
+ bb2():
+ EntryPoint JIT(0)
+ v6:BasicObject = LoadArg :self@0
+ v7:BasicObject = LoadArg :o@1
+ Jump bb3(v6, v7)
+ bb3(v9:BasicObject, v10:BasicObject):
+ PatchPoint NoSingletonClass(Object@0x1008)
+ PatchPoint MethodRedefined(Object@0x1008, dup@0x1010, cme:0x1018)
+ v24:ObjectExact = GuardType v10, ObjectExact recompile
+ v25:BasicObject = CCallWithFrame v24, :Kernel#dup@0x1040
+ CheckInterrupts
+ Return v25
+ ");
+ }
+
+ #[test]
+ fn test_print_nil_module_name() {
+ eval(r#"
+ X = [Module.new].freeze
+ def test = X[0]
+ test
+ "#);
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:3:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb3(v1)
+ bb2():
+ EntryPoint JIT(0)
+ v4:BasicObject = LoadArg :self@0
+ Jump bb3(v4)
+ bb3(v6:BasicObject):
+ PatchPoint StableConstantNames(0x1000, X)
+ v11:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v13:Fixnum[0] = Const Value(0)
+ PatchPoint NoSingletonClass(Array@0x1010)
+ PatchPoint MethodRedefined(Array@0x1010, []@0x1018, cme:0x1020)
+ v35:ModuleExact[VALUE(0x1048)] = Const Value(VALUE(0x1048))
+ CheckInterrupts
+ Return v35
+ ");
+ }
+
+ #[test]
+ fn no_load_from_ep_right_after_entrypoint() {
+ let formatted = eval("
+ def read_nil_local(a, _b, _c)
+ formatted ||= a
+ @formatted = formatted
+ -> { formatted } # the environment escapes
+ end
+
+ def call
+ puts [], [], [], [] # fill VM stack with junk
+ read_nil_local(true, 1, 1) # expected SendDirect
+ end
+
+ call # profile
+ call # compile
+ @formatted
+ ");
+ assert_eq!(Qtrue, formatted, "{}", formatted.obj_info());
+ assert_snapshot!(hir_string("read_nil_local"), @"
+ fn read_nil_local@:3:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ v2:CPtr = LoadSP
+ v3:BasicObject = LoadField v2, :a@0x1000
+ v4:BasicObject = LoadField v2, :_b@0x1001
+ v5:BasicObject = LoadField v2, :_c@0x1002
+ Jump bb3(v1, v3, v4, v5)
+ bb2():
+ EntryPoint JIT(0)
+ v9:BasicObject = LoadArg :self@0
+ v10:BasicObject = LoadArg :a@1
+ v11:CPtr = GetEP 0
+ StoreField v11, :a@0x1001, v10
+ v13:BasicObject = LoadArg :_b@2
+ StoreField v11, :_b@0x1002, v13
+ v15:BasicObject = LoadArg :_c@3
+ StoreField v11, :_c@0x1003, v15
+ v17:NilClass = Const Value(nil)
+ StoreField v11, :formatted@0x1004, v17
+ Jump bb3(v9, v10, v13, v15)
+ bb3(v20:BasicObject, v21:BasicObject, v22:BasicObject, v23:BasicObject):
+ v81:NilClass = Const Value(nil)
+ SetLocal :formatted, l0, EP@3, v21
+ v46:HeapBasicObject = GuardType v20, HeapBasicObject
+ v47:CShape = LoadField v46, :shape_id@0x1005
+ v48:CShape[0x1006] = Const CShape(0x1006)
+ v49:CBool = IsBitEqual v47, v48
+ CondBranch v49, bb7(), bb8()
+ bb7():
+ StoreField v46, :@formatted@0x1007, v21
+ WriteBarrier v46, v21
+ Jump bb6()
+ bb8():
+ v54:CShape[0x1008] = GuardBitEquals v47, CShape(0x1008) recompile
+ StoreField v46, :@formatted@0x1007, v21
+ WriteBarrier v46, v21
+ v58:CShape[0x1006] = Const CShape(0x1006)
+ StoreField v46, :shape_id@0x1005, v58
+ Jump bb6()
+ bb6():
+ v64:ClassSubclass[VMFrozenCore] = Const Value(VALUE(0x1010))
+ PatchPoint MethodRedefined(Class@0x1018, lambda@0x1020, cme:0x1028)
+ v80:BasicObject = CCallWithFrame v64, :RubyVM::FrozenCore.lambda@0x1050, block=0x1058
+ v67:CPtr = GetEP 0
+ v68:BasicObject = LoadField v67, :a@0x1001
+ v69:BasicObject = LoadField v67, :_b@0x1002
+ v70:BasicObject = LoadField v67, :_c@0x1003
+ v71:BasicObject = LoadField v67, :formatted@0x1004
+ CheckInterrupts
+ Return v80
+ ");
+ }
+
+ #[test]
+ fn test_fold_load_field_frozen_constant_object() {
+ // Basic case: frozen constant object with attr_accessor
+ eval("
+ class TestFrozen
+ attr_accessor :a
+ def initialize
+ @a = 1
+ end
+ end
+
+ FROZEN_OBJ = TestFrozen.new.freeze
+
+ def test = FROZEN_OBJ.a
+ test
+ test
+ ");
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:11:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb3(v1)
+ bb2():
+ EntryPoint JIT(0)
+ v4:BasicObject = LoadArg :self@0
+ Jump bb3(v4)
+ bb3(v6:BasicObject):
+ PatchPoint StableConstantNames(0x1000, FROZEN_OBJ)
+ v11:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ PatchPoint NoSingletonClass(TestFrozen@0x1010)
+ PatchPoint MethodRedefined(TestFrozen@0x1010, a@0x1018, cme:0x1020)
+ v27:Fixnum[1] = Const Value(1)
+ CheckInterrupts
+ Return v27
+ ");
+ }
+
+ #[test]
+ fn test_fold_load_field_frozen_multiple_ivars() {
+ // Frozen object with multiple instance variables
+ eval("
+ class TestMultiIvars
+ attr_accessor :a, :b, :c
+ def initialize
+ @a = 10
+ @b = 20
+ @c = 30
+ end
+ end
+
+ MULTI_FROZEN = TestMultiIvars.new.freeze
+
+ def test = MULTI_FROZEN.b
+ test
+ test
+ ");
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:13:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb3(v1)
+ bb2():
+ EntryPoint JIT(0)
+ v4:BasicObject = LoadArg :self@0
+ Jump bb3(v4)
+ bb3(v6:BasicObject):
+ PatchPoint StableConstantNames(0x1000, MULTI_FROZEN)
+ v11:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ PatchPoint NoSingletonClass(TestMultiIvars@0x1010)
+ PatchPoint MethodRedefined(TestMultiIvars@0x1010, b@0x1018, cme:0x1020)
+ v27:Fixnum[20] = Const Value(20)
+ CheckInterrupts
+ Return v27
+ ");
+ }
+
+ #[test]
+ fn test_fold_load_field_frozen_string_value() {
+ // Frozen object with a string ivar
+ eval(r#"
+ class TestFrozenStr
+ attr_accessor :name
+ def initialize
+ @name = "hello"
+ end
+ end
+
+ FROZEN_STR = TestFrozenStr.new.freeze
+
def test = FROZEN_STR.name
test
test
@@ -19708,31 +20413,29 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- v16:CBool = HasType v10, ObjectSubclass[class_exact:C]
- CondBranch v16, bb5(), bb6()
+ CondBranchHasType v10, ObjectSubclass[class_exact:C], bb5(), bb6()
bb5():
PatchPoint NoSingletonClass(C@0x1008)
PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018)
- v42:Fixnum[3] = Const Value(3)
- Jump bb4(v42)
+ v40:Fixnum[3] = Const Value(3)
+ Jump bb4(v40)
bb6():
- v22:CBool = HasType v10, ObjectSubclass[class_exact:D]
- CondBranch v22, bb7(), bb8()
+ CondBranchHasType v10, ObjectSubclass[class_exact:D], bb7(), bb8()
bb7():
PatchPoint NoSingletonClass(D@0x1040)
PatchPoint MethodRedefined(D@0x1040, foo@0x1010, cme:0x1048)
- v45:Fixnum[4] = Const Value(4)
- Jump bb4(v45)
+ v43:Fixnum[4] = Const Value(4)
+ Jump bb4(v43)
bb8():
- v28:BasicObject = Send v10, :foo # SendFallbackReason: Send: polymorphic fallback
- Jump bb4(v28)
+ v26:BasicObject = Send v10, :foo # SendFallbackReason: Send: polymorphic fallback
+ Jump bb4(v26)
bb4(v15:BasicObject):
- v31:Fixnum[2] = Const Value(2)
+ v29:Fixnum[2] = Const Value(2)
PatchPoint MethodRedefined(Integer@0x1070, +@0x1078, cme:0x1080)
- v48:Fixnum = GuardType v15, Fixnum recompile
- v49:Fixnum = FixnumAdd v48, v31
+ v46:Fixnum = GuardType v15, Fixnum recompile
+ v47:Fixnum = FixnumAdd v46, v29
CheckInterrupts
- Return v49
+ Return v47
");
}
@@ -19762,23 +20465,21 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- v16:CBool = HasType v10, ObjectSubclass[class_exact:C]
- CondBranch v16, bb5(), bb6()
+ CondBranchHasType v10, ObjectSubclass[class_exact:C], bb5(), bb6()
bb5():
- v19:ObjectSubclass[class_exact:C] = RefineType v10, ObjectSubclass[class_exact:C]
+ v18:ObjectSubclass[class_exact:C] = RefineType v10, ObjectSubclass[class_exact:C]
PatchPoint NoSingletonClass(C@0x1008)
PatchPoint MethodRedefined(C@0x1008, itself@0x1010, cme:0x1018)
- Jump bb4(v19)
+ Jump bb4(v18)
bb6():
- v22:CBool = HasType v10, Fixnum
- CondBranch v22, bb7(), bb8()
+ CondBranchHasType v10, Fixnum, bb7(), bb8()
bb7():
- v25:Fixnum = RefineType v10, Fixnum
+ v23:Fixnum = RefineType v10, Fixnum
PatchPoint MethodRedefined(Integer@0x1040, itself@0x1010, cme:0x1018)
- Jump bb4(v25)
+ Jump bb4(v23)
bb8():
- v28:BasicObject = Send v10, :itself # SendFallbackReason: Send: polymorphic fallback
- Jump bb4(v28)
+ v26:BasicObject = Send v10, :itself # SendFallbackReason: Send: polymorphic fallback
+ Jump bb4(v26)
bb4(v15:BasicObject):
CheckInterrupts
Return v15
@@ -19818,11 +20519,11 @@ mod hir_opt_tests {
PatchPoint StableConstantNames(0x1068, Integer)
v31:ClassSubclass[Integer@0x1070] = Const Value(VALUE(0x1070))
PatchPoint MethodRedefined(Class@0x1078, ==@0x1080, cme:0x1088)
- v82:CBool = IsBitEqual v12, v31
- v83:BoolExact = BoxBool v82
+ v77:CBool = IsBitEqual v12, v31
+ v78:BoolExact = BoxBool v77
PopInlineFrame
CheckInterrupts
- Return v83
+ Return v78
");
}
@@ -19858,33 +20559,31 @@ mod hir_opt_tests {
v9:BasicObject = LoadArg :i@2
Jump bb3(v7, v8, v9)
bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject):
- v21:CBool = HasType v12, ArrayExact
- CondBranch v21, bb5(), bb6()
+ CondBranchHasType v12, ArrayExact, bb5(), bb6()
bb5():
- v24:ArrayExact = RefineType v12, ArrayExact
+ v23:ArrayExact = RefineType v12, ArrayExact
PatchPoint NoSingletonClass(Array@0x1008)
PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018)
- v43:Fixnum = GuardType v13, Fixnum
- v44:CInt64 = UnboxFixnum v43
- v45:CInt64 = ArrayLength v24
- v46:CInt64 = GuardLess v44, v45
- v47:CInt64 = AdjustBounds v46, v45
- v48:CInt64[0] = Const CInt64(0)
- v49:CInt64 = GuardGreaterEq v47, v48
- v50:BasicObject = ArrayAref v24, v49
- Jump bb4(v50)
+ v41:Fixnum = GuardType v13, Fixnum
+ v42:CInt64 = UnboxFixnum v41
+ v43:CInt64 = ArrayLength v23
+ v44:CInt64 = GuardLess v42, v43
+ v45:CInt64 = AdjustBounds v44, v43
+ v46:CInt64[0] = Const CInt64(0)
+ v47:CInt64 = GuardGreaterEq v45, v46
+ v48:BasicObject = ArrayAref v23, v47
+ Jump bb4(v48)
bb6():
- v27:CBool = HasType v12, HashExact
- CondBranch v27, bb7(), bb8()
+ CondBranchHasType v12, HashExact, bb7(), bb8()
bb7():
- v30:HashExact = RefineType v12, HashExact
+ v28:HashExact = RefineType v12, HashExact
PatchPoint NoSingletonClass(Hash@0x1040)
PatchPoint MethodRedefined(Hash@0x1040, []@0x1010, cme:0x1048)
- v54:BasicObject = HashAref v30, v13
- Jump bb4(v54)
+ v52:BasicObject = HashAref v28, v13
+ Jump bb4(v52)
bb8():
- v33:BasicObject = Send v12, :[], v13 # SendFallbackReason: Send: polymorphic fallback
- Jump bb4(v33)
+ v31:BasicObject = Send v12, :[], v13 # SendFallbackReason: Send: polymorphic fallback
+ Jump bb4(v31)
bb4(v20:BasicObject):
CheckInterrupts
Return v20
@@ -19923,24 +20622,22 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :x@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- v16:CBool = HasType v10, Fixnum
- CondBranch v16, bb5(), bb6()
+ CondBranchHasType v10, Fixnum, bb5(), bb6()
bb5():
- v19:Fixnum = RefineType v10, Fixnum
+ v18:Fixnum = RefineType v10, Fixnum
PatchPoint MethodRedefined(Integer@0x1008, to_s@0x1010, cme:0x1018)
- v37:StringExact = CCallVariadic v19, :Integer#to_s@0x1040
- Jump bb4(v37)
+ v35:StringExact = CCallVariadic v18, :Integer#to_s@0x1040
+ Jump bb4(v35)
bb6():
- v22:CBool = HasType v10, Bignum
- CondBranch v22, bb7(), bb8()
+ CondBranchHasType v10, Bignum, bb7(), bb8()
bb7():
- v25:Bignum = RefineType v10, Bignum
+ v23:Bignum = RefineType v10, Bignum
PatchPoint MethodRedefined(Integer@0x1008, to_s@0x1010, cme:0x1018)
- v40:StringExact = CCallVariadic v25, :Integer#to_s@0x1040
- Jump bb4(v40)
+ v38:StringExact = CCallVariadic v23, :Integer#to_s@0x1040
+ Jump bb4(v38)
bb8():
- v28:BasicObject = Send v10, :to_s # SendFallbackReason: Send: polymorphic fallback
- Jump bb4(v28)
+ v26:BasicObject = Send v10, :to_s # SendFallbackReason: Send: polymorphic fallback
+ Jump bb4(v26)
bb4(v15:BasicObject):
CheckInterrupts
Return v15
@@ -19976,24 +20673,22 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :x@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- v16:CBool = HasType v10, Flonum
- CondBranch v16, bb5(), bb6()
+ CondBranchHasType v10, Flonum, bb5(), bb6()
bb5():
- v19:Flonum = RefineType v10, Flonum
+ v18:Flonum = RefineType v10, Flonum
PatchPoint MethodRedefined(Float@0x1008, to_s@0x1010, cme:0x1018)
- v37:BasicObject = CCallWithFrame v19, :Float#to_s@0x1040
- Jump bb4(v37)
+ v35:BasicObject = CCallWithFrame v18, :Float#to_s@0x1040
+ Jump bb4(v35)
bb6():
- v22:CBool = HasType v10, HeapFloat
- CondBranch v22, bb7(), bb8()
+ CondBranchHasType v10, HeapFloat, bb7(), bb8()
bb7():
- v25:HeapFloat = RefineType v10, HeapFloat
+ v23:HeapFloat = RefineType v10, HeapFloat
PatchPoint MethodRedefined(Float@0x1008, to_s@0x1010, cme:0x1018)
- v40:BasicObject = CCallWithFrame v25, :Float#to_s@0x1040
- Jump bb4(v40)
+ v38:BasicObject = CCallWithFrame v23, :Float#to_s@0x1040
+ Jump bb4(v38)
bb8():
- v28:BasicObject = Send v10, :to_s # SendFallbackReason: Send: polymorphic fallback
- Jump bb4(v28)
+ v26:BasicObject = Send v10, :to_s # SendFallbackReason: Send: polymorphic fallback
+ Jump bb4(v26)
bb4(v15:BasicObject):
CheckInterrupts
Return v15
@@ -20029,24 +20724,22 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :x@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- v16:CBool = HasType v10, StaticSymbol
- CondBranch v16, bb5(), bb6()
+ CondBranchHasType v10, StaticSymbol, bb5(), bb6()
bb5():
- v19:StaticSymbol = RefineType v10, StaticSymbol
+ v18:StaticSymbol = RefineType v10, StaticSymbol
PatchPoint MethodRedefined(Symbol@0x1008, to_s@0x1010, cme:0x1018)
- v36:StringExact = InvokeBuiltin leaf , v19
- Jump bb4(v36)
+ v34:StringExact = InvokeBuiltin leaf , v18
+ Jump bb4(v34)
bb6():
- v22:CBool = HasType v10, DynamicSymbol
- CondBranch v22, bb7(), bb8()
+ CondBranchHasType v10, DynamicSymbol, bb7(), bb8()
bb7():
- v25:DynamicSymbol = RefineType v10, DynamicSymbol
+ v23:DynamicSymbol = RefineType v10, DynamicSymbol
PatchPoint MethodRedefined(Symbol@0x1008, to_s@0x1010, cme:0x1018)
- v38:StringExact = InvokeBuiltin leaf , v25
- Jump bb4(v38)
+ v36:StringExact = InvokeBuiltin leaf , v23
+ Jump bb4(v36)
bb8():
- v28:BasicObject = Send v10, :to_s # SendFallbackReason: Send: polymorphic fallback
- Jump bb4(v28)
+ v26:BasicObject = Send v10, :to_s # SendFallbackReason: Send: polymorphic fallback
+ Jump bb4(v26)
bb4(v15:BasicObject):
CheckInterrupts
Return v15
@@ -20086,16 +20779,15 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- v16:CBool = HasType v10, ObjectSubclass[class_exact:C]
- CondBranch v16, bb5(), bb6()
+ CondBranchHasType v10, ObjectSubclass[class_exact:C], bb5(), bb6()
bb5():
PatchPoint NoSingletonClass(C@0x1008)
PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018)
- v31:Fixnum[3] = Const Value(3)
- Jump bb4(v31)
+ v30:Fixnum[3] = Const Value(3)
+ Jump bb4(v30)
bb6():
- v22:BasicObject = Send v10, :foo # SendFallbackReason: Send: polymorphic fallback
- Jump bb4(v22)
+ v21:BasicObject = Send v10, :foo # SendFallbackReason: Send: polymorphic fallback
+ Jump bb4(v21)
bb4(v15:BasicObject):
CheckInterrupts
Return v15
@@ -21603,25 +22295,23 @@ mod hir_opt_tests {
v9:BasicObject = LoadArg :b@2
Jump bb3(v7, v8, v9)
bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject):
- v21:CBool = HasType v12, HeapFloat
- CondBranch v21, bb5(), bb6()
+ CondBranchHasType v12, HeapFloat, bb5(), bb6()
bb5():
- v24:HeapFloat = RefineType v12, HeapFloat
+ v23:HeapFloat = RefineType v12, HeapFloat
PatchPoint MethodRedefined(Float@0x1008, *@0x1010, cme:0x1018)
- v42:BasicObject = CCallWithFrame v24, :Float#*@0x1040, v13
- Jump bb4(v42)
+ v40:BasicObject = CCallWithFrame v23, :Float#*@0x1040, v13
+ Jump bb4(v40)
bb6():
- v27:CBool = HasType v12, Flonum
- CondBranch v27, bb7(), bb8()
+ CondBranchHasType v12, Flonum, bb7(), bb8()
bb7():
- v30:Flonum = RefineType v12, Flonum
+ v28:Flonum = RefineType v12, Flonum
PatchPoint MethodRedefined(Float@0x1008, *@0x1010, cme:0x1018)
- v45:Flonum = GuardType v13, Flonum recompile
- v46:Float = FloatMul v30, v45
- Jump bb4(v46)
+ v43:Flonum = GuardType v13, Flonum recompile
+ v44:Float = FloatMul v28, v43
+ Jump bb4(v44)
bb8():
- v33:BasicObject = Send v12, :*, v13 # SendFallbackReason: Send: polymorphic fallback
- Jump bb4(v33)
+ v31:BasicObject = Send v12, :*, v13 # SendFallbackReason: Send: polymorphic fallback
+ Jump bb4(v31)
bb4(v20:BasicObject):
CheckInterrupts
Return v20
@@ -21895,7 +22585,7 @@ mod hir_opt_tests {
v8:BasicObject = LoadArg :x@1
Jump bb3(v7, v8)
bb3(v11:HeapBasicObject, v12:BasicObject):
- v92:NilClass = Const Value(nil)
+ v90:NilClass = Const Value(nil)
v17:Fixnum[1] = Const Value(1)
v20:CShape = LoadField v11, :shape_id@0x1001
v21:CShape[0x1002] = Const CShape(0x1002)
@@ -21919,31 +22609,29 @@ mod hir_opt_tests {
bb4():
PatchPoint NoEPEscape(f)
v43:Fixnum[1] = Const Value(1)
- v47:CBool = HasType v12, Fixnum
- CondBranch v47, bb10(), bb11()
+ CondBranchHasType v12, Fixnum, bb10(), bb11()
bb10():
- v50:Fixnum = RefineType v12, Fixnum
+ v49:Fixnum = RefineType v12, Fixnum
PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018)
- v84:Fixnum = FixnumAdd v50, v43
- Jump bb9(v84)
+ v82:Fixnum = FixnumAdd v49, v43
+ Jump bb9(v82)
bb11():
- v53:CBool = HasType v12, Flonum
- CondBranch v53, bb12(), bb13()
+ CondBranchHasType v12, Flonum, bb12(), bb13()
bb12():
- v56:Flonum = RefineType v12, Flonum
+ v54:Flonum = RefineType v12, Flonum
PatchPoint MethodRedefined(Float@0x1040, +@0x1010, cme:0x1048)
- v87:Float = FloatAdd v56, v43
- Jump bb9(v87)
+ v85:Float = FloatAdd v54, v43
+ Jump bb9(v85)
bb13():
PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018)
- v90:Fixnum = GuardType v12, Fixnum recompile
- v91:Fixnum = FixnumAdd v90, v43
- Jump bb9(v91)
+ v88:Fixnum = GuardType v12, Fixnum recompile
+ v89:Fixnum = FixnumAdd v88, v43
+ Jump bb9(v89)
bb9(v46:Float|Fixnum):
- v67:CShape = LoadField v11, :shape_id@0x1001
- v68:CShape[0x1002] = Const CShape(0x1002)
- v69:CBool = IsBitEqual v67, v68
- CondBranch v69, bb15(), bb16()
+ v65:CShape = LoadField v11, :shape_id@0x1001
+ v66:CShape[0x1002] = Const CShape(0x1002)
+ v67:CBool = IsBitEqual v65, v66
+ CondBranch v67, bb15(), bb16()
bb15():
StoreField v11, :@a@0x1003, v46
WriteBarrier v11, v46
@@ -23862,56 +24550,53 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :obj@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- v16:CBool = HasType v10, ObjectSubclass[class_exact:C]
- CondBranch v16, bb5(), bb6()
+ CondBranchHasType v10, ObjectSubclass[class_exact:C], bb5(), bb6()
bb5():
- v19:ObjectSubclass[class_exact:C] = RefineType v10, ObjectSubclass[class_exact:C]
+ v18:ObjectSubclass[class_exact:C] = RefineType v10, ObjectSubclass[class_exact:C]
PatchPoint NoSingletonClass(C@0x1008)
PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018)
- PushInlineFrame :foo, v19 (0x1040), num_args=0
- v57:CPtr = GetEP 0
- v58:CInt64 = LoadField v57, :VM_ENV_DATA_INDEX_SPECVAL@0x1060
- v59:CInt64[-4] = Const CInt64(-4)
- v60:CInt64 = IntAnd v58, v59
- v61:BasicObject = InvokeBlockIseqDirect (0x1068), v60
+ PushInlineFrame :foo, v18 (0x1040), num_args=0
+ v54:CPtr = GetEP 0
+ v55:CInt64 = LoadField v54, :VM_ENV_DATA_INDEX_SPECVAL@0x1060
+ v56:CInt64[-4] = Const CInt64(-4)
+ v57:CInt64 = IntAnd v55, v56
+ v58:BasicObject = InvokeBlockIseqDirect (0x1068), v57
CheckInterrupts
PopInlineFrame
- Jump bb4(v61)
+ Jump bb4(v58)
bb6():
- v22:CBool = HasType v10, ObjectSubclass[class_exact:A]
- CondBranch v22, bb7(), bb8()
+ CondBranchHasType v10, ObjectSubclass[class_exact:A], bb7(), bb8()
bb7():
- v25:ObjectSubclass[class_exact:A] = RefineType v10, ObjectSubclass[class_exact:A]
+ v23:ObjectSubclass[class_exact:A] = RefineType v10, ObjectSubclass[class_exact:A]
PatchPoint NoSingletonClass(A@0x1088)
PatchPoint MethodRedefined(A@0x1088, foo@0x1010, cme:0x1018)
- PushInlineFrame :foo, v25 (0x1040), num_args=0
- v75:CPtr = GetEP 0
- v76:CInt64 = LoadField v75, :VM_ENV_DATA_INDEX_SPECVAL@0x1060
- v77:CInt64[-4] = Const CInt64(-4)
- v78:CInt64 = IntAnd v76, v77
- v79:BasicObject = InvokeBlockIseqDirect (0x1068), v78
+ PushInlineFrame :foo, v23 (0x1040), num_args=0
+ v72:CPtr = GetEP 0
+ v73:CInt64 = LoadField v72, :VM_ENV_DATA_INDEX_SPECVAL@0x1060
+ v74:CInt64[-4] = Const CInt64(-4)
+ v75:CInt64 = IntAnd v73, v74
+ v76:BasicObject = InvokeBlockIseqDirect (0x1068), v75
CheckInterrupts
PopInlineFrame
- Jump bb4(v79)
+ Jump bb4(v76)
bb8():
- v28:CBool = HasType v10, ObjectSubclass[class_exact:B]
- CondBranch v28, bb9(), bb10()
+ CondBranchHasType v10, ObjectSubclass[class_exact:B], bb9(), bb10()
bb9():
- v31:ObjectSubclass[class_exact:B] = RefineType v10, ObjectSubclass[class_exact:B]
+ v28:ObjectSubclass[class_exact:B] = RefineType v10, ObjectSubclass[class_exact:B]
PatchPoint NoSingletonClass(B@0x1090)
PatchPoint MethodRedefined(B@0x1090, foo@0x1010, cme:0x1018)
- PushInlineFrame :foo, v31 (0x1040), num_args=0
- v93:CPtr = GetEP 0
- v94:CInt64 = LoadField v93, :VM_ENV_DATA_INDEX_SPECVAL@0x1060
- v95:CInt64[-4] = Const CInt64(-4)
- v96:CInt64 = IntAnd v94, v95
- v97:BasicObject = InvokeBlockIseqDirect (0x1068), v96
+ PushInlineFrame :foo, v28 (0x1040), num_args=0
+ v90:CPtr = GetEP 0
+ v91:CInt64 = LoadField v90, :VM_ENV_DATA_INDEX_SPECVAL@0x1060
+ v92:CInt64[-4] = Const CInt64(-4)
+ v93:CInt64 = IntAnd v91, v92
+ v94:BasicObject = InvokeBlockIseqDirect (0x1068), v93
CheckInterrupts
PopInlineFrame
- Jump bb4(v97)
+ Jump bb4(v94)
bb10():
- v34:BasicObject = Send v10, 0x1068, :foo # SendFallbackReason: Send: polymorphic fallback
- Jump bb4(v34)
+ v31:BasicObject = Send v10, 0x1068, :foo # SendFallbackReason: Send: polymorphic fallback
+ Jump bb4(v31)
bb4(v15:BasicObject):
PatchPoint NoEPEscape(test)
CheckInterrupts
@@ -23994,26 +24679,24 @@ mod hir_opt_tests {
v51:BasicObject = LoadField v20, :VM_ENV_DATA_INDEX_SPECVAL@0x1004
Jump bb6(v51, v13)
bb6(v18:BasicObject, v19:BasicObject):
- v55:CBool = HasType v12, ObjectSubclass[class_exact:B]
- CondBranch v55, bb15(), bb16()
+ CondBranchHasType v12, ObjectSubclass[class_exact:B], bb15(), bb16()
bb15():
- v74:NilClass = GuardBitEquals v18, Value(nil) recompile
+ v72:NilClass = GuardBitEquals v18, Value(nil) recompile
PatchPoint NoSingletonClass(B@0x1010)
PatchPoint MethodRedefined(B@0x1010, foo@0x1018, cme:0x1020)
- v78:Fixnum[43] = Const Value(43)
- Jump bb14(v78)
+ v76:Fixnum[43] = Const Value(43)
+ Jump bb14(v76)
bb16():
- v61:CBool = HasType v12, ObjectSubclass[class_exact:A]
- CondBranch v61, bb17(), bb18()
+ CondBranchHasType v12, ObjectSubclass[class_exact:A], bb17(), bb18()
bb17():
- v79:NilClass = GuardBitEquals v18, Value(nil) recompile
+ v77:NilClass = GuardBitEquals v18, Value(nil) recompile
PatchPoint NoSingletonClass(A@0x1048)
PatchPoint MethodRedefined(A@0x1048, foo@0x1018, cme:0x1050)
- v83:Fixnum[42] = Const Value(42)
- Jump bb14(v83)
+ v81:Fixnum[42] = Const Value(42)
+ Jump bb14(v81)
bb18():
- v67:BasicObject = Send v12, &block, :foo, v18 # SendFallbackReason: Send: polymorphic fallback
- Jump bb14(v67)
+ v65:BasicObject = Send v12, &block, :foo, v18 # SendFallbackReason: Send: polymorphic fallback
+ Jump bb14(v65)
bb14(v54:BasicObject):
CheckInterrupts
Return v54
diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs
index 2894c25f5f299c..964209a3f1d981 100644
--- a/zjit/src/hir/tests.rs
+++ b/zjit/src/hir/tests.rs
@@ -2409,29 +2409,27 @@ pub(crate) mod hir_build_tests {
bb3(v6:BasicObject):
v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
v12:Fixnum[123] = Const Value(123)
- v15:CBool[false] = HasType v12, String
- CondBranch v15, bb4(), bb5()
+ CondBranchHasType v12, String, bb4(), bb5()
bb4():
- v17 = RefineType v12, String
- Jump bb6(v17)
+ v16 = RefineType v12, String
+ Jump bb6(v16)
bb5():
- v19:Fixnum[123] = RefineType v12, NotString
- v20:BasicObject = Send v19, :to_s # SendFallbackReason: ObjToString: result is not a string
- Jump bb6(v20)
- bb6(v22:BasicObject):
- v24:CBool = HasType v22, String
- CondBranch v24, bb7(), bb8()
+ v18:Fixnum[123] = RefineType v12, NotString
+ v19:BasicObject = Send v18, :to_s # SendFallbackReason: ObjToString: result is not a string
+ Jump bb6(v19)
+ bb6(v21:BasicObject):
+ CondBranchHasType v21, String, bb7(), bb8()
bb7():
- v26:String = RefineType v22, String
- Jump bb9(v26)
+ v24:String = RefineType v21, String
+ Jump bb9(v24)
bb8():
- v28:StringExact = AnyToString v12
- Jump bb9(v28)
- bb9(v30:String):
- v32:StringExact = StringConcat v10, v30
- v34:Symbol = StringIntern v32
+ v26:StringExact = AnyToString v12
+ Jump bb9(v26)
+ bb9(v28:String):
+ v30:StringExact = StringConcat v10, v28
+ v32:Symbol = StringIntern v30
CheckInterrupts
- Return v34
+ Return v32
");
}
@@ -5318,16 +5316,15 @@ pub(crate) mod hir_build_tests {
v7:BasicObject = LoadArg :x@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- v16:CBool = HasType v10, NilClass
- v17:NilClass = Const Value(nil)
- CondBranch v16, bb4(v9, v17, v17), bb5()
+ v16:NilClass = Const Value(nil)
+ CondBranchHasType v10, NilClass, bb4(v9, v16, v16), bb5()
bb5():
- v19:NotNil = RefineType v10, NotNil
- v21:BasicObject = Send v19, :itself # SendFallbackReason: Uncategorized(opt_send_without_block)
- Jump bb4(v9, v19, v21)
- bb4(v23:BasicObject, v24:BasicObject, v25:BasicObject):
+ v18:NotNil = RefineType v10, NotNil
+ v20:BasicObject = Send v18, :itself # SendFallbackReason: Uncategorized(opt_send_without_block)
+ Jump bb4(v9, v18, v20)
+ bb4(v22:BasicObject, v23:BasicObject, v24:BasicObject):
CheckInterrupts
- Return v25
+ Return v24
");
}
@@ -5363,20 +5360,19 @@ pub(crate) mod hir_build_tests {
CondBranch v15, bb6(), bb4(v9, v16)
bb6():
v18:Truthy = RefineType v10, Truthy
- v23:CBool[false] = HasType v18, NilClass
- v24:NilClass = Const Value(nil)
- CondBranch v23, bb5(v9, v24, v24), bb7()
+ v23:NilClass = Const Value(nil)
+ CondBranchHasType v18, NilClass, bb5(v9, v23, v23), bb7()
bb7():
- v26:Truthy = RefineType v18, NotNil
- v28:BasicObject = Send v26, :itself # SendFallbackReason: Uncategorized(opt_send_without_block)
+ v25:Truthy = RefineType v18, NotNil
+ v27:BasicObject = Send v25, :itself # SendFallbackReason: Uncategorized(opt_send_without_block)
CheckInterrupts
- Return v28
- bb4(v33:BasicObject, v34:Falsy):
- v38:Fixnum[4] = Const Value(4)
- Jump bb5(v33, v34, v38)
- bb5(v40:BasicObject, v41:Falsy, v42:Fixnum[4]):
+ Return v27
+ bb4(v32:BasicObject, v33:Falsy):
+ v37:Fixnum[4] = Const Value(4)
+ Jump bb5(v32, v33, v37)
+ bb5(v39:BasicObject, v40:Falsy, v41:Fixnum[4]):
CheckInterrupts
- Return v42
+ Return v41
");
}
@@ -5797,28 +5793,26 @@ pub(crate) mod hir_build_tests {
bb3(v6:BasicObject):
v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
v12:Fixnum[1] = Const Value(1)
- v15:CBool[false] = HasType v12, String
- CondBranch v15, bb4(), bb5()
+ CondBranchHasType v12, String, bb4(), bb5()
bb4():
- v17 = RefineType v12, String
- Jump bb6(v17)
+ v16 = RefineType v12, String
+ Jump bb6(v16)
bb5():
- v19:Fixnum[1] = RefineType v12, NotString
- v20:BasicObject = Send v19, :to_s # SendFallbackReason: ObjToString: result is not a string
- Jump bb6(v20)
- bb6(v22:BasicObject):
- v24:CBool = HasType v22, String
- CondBranch v24, bb7(), bb8()
+ v18:Fixnum[1] = RefineType v12, NotString
+ v19:BasicObject = Send v18, :to_s # SendFallbackReason: ObjToString: result is not a string
+ Jump bb6(v19)
+ bb6(v21:BasicObject):
+ CondBranchHasType v21, String, bb7(), bb8()
bb7():
- v26:String = RefineType v22, String
- Jump bb9(v26)
+ v24:String = RefineType v21, String
+ Jump bb9(v24)
bb8():
- v28:StringExact = AnyToString v12
- Jump bb9(v28)
- bb9(v30:String):
- v32:StringExact = StringConcat v10, v30
+ v26:StringExact = AnyToString v12
+ Jump bb9(v26)
+ bb9(v28:String):
+ v30:StringExact = StringConcat v10, v28
CheckInterrupts
- Return v32
+ Return v30
");
}
@@ -5840,68 +5834,62 @@ pub(crate) mod hir_build_tests {
Jump bb3(v4)
bb3(v6:BasicObject):
v10:Fixnum[1] = Const Value(1)
- v13:CBool[false] = HasType v10, String
- CondBranch v13, bb4(), bb5()
+ CondBranchHasType v10, String, bb4(), bb5()
bb4():
- v15 = RefineType v10, String
- Jump bb6(v15)
+ v14 = RefineType v10, String
+ Jump bb6(v14)
bb5():
- v17:Fixnum[1] = RefineType v10, NotString
- v18:BasicObject = Send v17, :to_s # SendFallbackReason: ObjToString: result is not a string
- Jump bb6(v18)
- bb6(v20:BasicObject):
- v22:CBool = HasType v20, String
- CondBranch v22, bb7(), bb8()
+ v16:Fixnum[1] = RefineType v10, NotString
+ v17:BasicObject = Send v16, :to_s # SendFallbackReason: ObjToString: result is not a string
+ Jump bb6(v17)
+ bb6(v19:BasicObject):
+ CondBranchHasType v19, String, bb7(), bb8()
bb7():
- v24:String = RefineType v20, String
- Jump bb9(v24)
+ v22:String = RefineType v19, String
+ Jump bb9(v22)
bb8():
- v26:StringExact = AnyToString v10
- Jump bb9(v26)
- bb9(v28:String):
- v30:Fixnum[2] = Const Value(2)
- v33:CBool[false] = HasType v30, String
- CondBranch v33, bb10(), bb11()
+ v24:StringExact = AnyToString v10
+ Jump bb9(v24)
+ bb9(v26:String):
+ v28:Fixnum[2] = Const Value(2)
+ CondBranchHasType v28, String, bb10(), bb11()
bb10():
- v35 = RefineType v30, String
- Jump bb12(v35)
+ v32 = RefineType v28, String
+ Jump bb12(v32)
bb11():
- v37:Fixnum[2] = RefineType v30, NotString
- v38:BasicObject = Send v37, :to_s # SendFallbackReason: ObjToString: result is not a string
- Jump bb12(v38)
- bb12(v40:BasicObject):
- v42:CBool = HasType v40, String
- CondBranch v42, bb13(), bb14()
+ v34:Fixnum[2] = RefineType v28, NotString
+ v35:BasicObject = Send v34, :to_s # SendFallbackReason: ObjToString: result is not a string
+ Jump bb12(v35)
+ bb12(v37:BasicObject):
+ CondBranchHasType v37, String, bb13(), bb14()
bb13():
- v44:String = RefineType v40, String
- Jump bb15(v44)
+ v40:String = RefineType v37, String
+ Jump bb15(v40)
bb14():
- v46:StringExact = AnyToString v30
- Jump bb15(v46)
- bb15(v48:String):
- v50:Fixnum[3] = Const Value(3)
- v53:CBool[false] = HasType v50, String
- CondBranch v53, bb16(), bb17()
+ v42:StringExact = AnyToString v28
+ Jump bb15(v42)
+ bb15(v44:String):
+ v46:Fixnum[3] = Const Value(3)
+ CondBranchHasType v46, String, bb16(), bb17()
bb16():
- v55 = RefineType v50, String
- Jump bb18(v55)
+ v50 = RefineType v46, String
+ Jump bb18(v50)
bb17():
- v57:Fixnum[3] = RefineType v50, NotString
- v58:BasicObject = Send v57, :to_s # SendFallbackReason: ObjToString: result is not a string
- Jump bb18(v58)
- bb18(v60:BasicObject):
- v62:CBool = HasType v60, String
- CondBranch v62, bb19(), bb20()
+ v52:Fixnum[3] = RefineType v46, NotString
+ v53:BasicObject = Send v52, :to_s # SendFallbackReason: ObjToString: result is not a string
+ Jump bb18(v53)
+ bb18(v55:BasicObject):
+ CondBranchHasType v55, String, bb19(), bb20()
bb19():
- v64:String = RefineType v60, String
- Jump bb21(v64)
+ v58:String = RefineType v55, String
+ Jump bb21(v58)
bb20():
- v66:StringExact = AnyToString v50
- Jump bb21(v66)
- bb21(v68:String):
- v70:StringExact = StringConcat v28, v48, v68
+ v60:StringExact = AnyToString v46
+ Jump bb21(v60)
+ bb21(v62:String):
+ v64:StringExact = StringConcat v26, v44, v62
CheckInterrupts
- Return v70
+ Return v64
");
}
@@ -5924,28 +5912,26 @@ pub(crate) mod hir_build_tests {
bb3(v6:BasicObject):
v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
v12:NilClass = Const Value(nil)
- v15:CBool[false] = HasType v12, String
- CondBranch v15, bb4(), bb5()
+ CondBranchHasType v12, String, bb4(), bb5()
bb4():
- v17 = RefineType v12, String
- Jump bb6(v17)
+ v16 = RefineType v12, String
+ Jump bb6(v16)
bb5():
- v19:NilClass = RefineType v12, NotString
- v20:BasicObject = Send v19, :to_s # SendFallbackReason: ObjToString: result is not a string
- Jump bb6(v20)
- bb6(v22:BasicObject):
- v24:CBool = HasType v22, String
- CondBranch v24, bb7(), bb8()
+ v18:NilClass = RefineType v12, NotString
+ v19:BasicObject = Send v18, :to_s # SendFallbackReason: ObjToString: result is not a string
+ Jump bb6(v19)
+ bb6(v21:BasicObject):
+ CondBranchHasType v21, String, bb7(), bb8()
bb7():
- v26:String = RefineType v22, String
- Jump bb9(v26)
+ v24:String = RefineType v21, String
+ Jump bb9(v24)
bb8():
- v28:StringExact = AnyToString v12
- Jump bb9(v28)
- bb9(v30:String):
- v32:StringExact = StringConcat v10, v30
+ v26:StringExact = AnyToString v12
+ Jump bb9(v26)
+ bb9(v28:String):
+ v30:StringExact = StringConcat v10, v28
CheckInterrupts
- Return v32
+ Return v30
");
}
@@ -5967,68 +5953,62 @@ pub(crate) mod hir_build_tests {
Jump bb3(v4)
bb3(v6:BasicObject):
v10:Fixnum[1] = Const Value(1)
- v13:CBool[false] = HasType v10, String
- CondBranch v13, bb4(), bb5()
+ CondBranchHasType v10, String, bb4(), bb5()
bb4():
- v15 = RefineType v10, String
- Jump bb6(v15)
+ v14 = RefineType v10, String
+ Jump bb6(v14)
bb5():
- v17:Fixnum[1] = RefineType v10, NotString
- v18:BasicObject = Send v17, :to_s # SendFallbackReason: ObjToString: result is not a string
- Jump bb6(v18)
- bb6(v20:BasicObject):
- v22:CBool = HasType v20, String
- CondBranch v22, bb7(), bb8()
+ v16:Fixnum[1] = RefineType v10, NotString
+ v17:BasicObject = Send v16, :to_s # SendFallbackReason: ObjToString: result is not a string
+ Jump bb6(v17)
+ bb6(v19:BasicObject):
+ CondBranchHasType v19, String, bb7(), bb8()
bb7():
- v24:String = RefineType v20, String
- Jump bb9(v24)
+ v22:String = RefineType v19, String
+ Jump bb9(v22)
bb8():
- v26:StringExact = AnyToString v10
- Jump bb9(v26)
- bb9(v28:String):
- v30:Fixnum[2] = Const Value(2)
- v33:CBool[false] = HasType v30, String
- CondBranch v33, bb10(), bb11()
+ v24:StringExact = AnyToString v10
+ Jump bb9(v24)
+ bb9(v26:String):
+ v28:Fixnum[2] = Const Value(2)
+ CondBranchHasType v28, String, bb10(), bb11()
bb10():
- v35 = RefineType v30, String
- Jump bb12(v35)
+ v32 = RefineType v28, String
+ Jump bb12(v32)
bb11():
- v37:Fixnum[2] = RefineType v30, NotString
- v38:BasicObject = Send v37, :to_s # SendFallbackReason: ObjToString: result is not a string
- Jump bb12(v38)
- bb12(v40:BasicObject):
- v42:CBool = HasType v40, String
- CondBranch v42, bb13(), bb14()
+ v34:Fixnum[2] = RefineType v28, NotString
+ v35:BasicObject = Send v34, :to_s # SendFallbackReason: ObjToString: result is not a string
+ Jump bb12(v35)
+ bb12(v37:BasicObject):
+ CondBranchHasType v37, String, bb13(), bb14()
bb13():
- v44:String = RefineType v40, String
- Jump bb15(v44)
+ v40:String = RefineType v37, String
+ Jump bb15(v40)
bb14():
- v46:StringExact = AnyToString v30
- Jump bb15(v46)
- bb15(v48:String):
- v50:Fixnum[3] = Const Value(3)
- v53:CBool[false] = HasType v50, String
- CondBranch v53, bb16(), bb17()
+ v42:StringExact = AnyToString v28
+ Jump bb15(v42)
+ bb15(v44:String):
+ v46:Fixnum[3] = Const Value(3)
+ CondBranchHasType v46, String, bb16(), bb17()
bb16():
- v55 = RefineType v50, String
- Jump bb18(v55)
+ v50 = RefineType v46, String
+ Jump bb18(v50)
bb17():
- v57:Fixnum[3] = RefineType v50, NotString
- v58:BasicObject = Send v57, :to_s # SendFallbackReason: ObjToString: result is not a string
- Jump bb18(v58)
- bb18(v60:BasicObject):
- v62:CBool = HasType v60, String
- CondBranch v62, bb19(), bb20()
+ v52:Fixnum[3] = RefineType v46, NotString
+ v53:BasicObject = Send v52, :to_s # SendFallbackReason: ObjToString: result is not a string
+ Jump bb18(v53)
+ bb18(v55:BasicObject):
+ CondBranchHasType v55, String, bb19(), bb20()
bb19():
- v64:String = RefineType v60, String
- Jump bb21(v64)
+ v58:String = RefineType v55, String
+ Jump bb21(v58)
bb20():
- v66:StringExact = AnyToString v50
- Jump bb21(v66)
- bb21(v68:String):
- v70:RegexpExact = ToRegexp v28, v48, v68
+ v60:StringExact = AnyToString v46
+ Jump bb21(v60)
+ bb21(v62:String):
+ v64:RegexpExact = ToRegexp v26, v44, v62
CheckInterrupts
- Return v70
+ Return v64
");
}
@@ -6050,48 +6030,44 @@ pub(crate) mod hir_build_tests {
Jump bb3(v4)
bb3(v6:BasicObject):
v10:Fixnum[1] = Const Value(1)
- v13:CBool[false] = HasType v10, String
- CondBranch v13, bb4(), bb5()
+ CondBranchHasType v10, String, bb4(), bb5()
bb4():
- v15 = RefineType v10, String
- Jump bb6(v15)
+ v14 = RefineType v10, String
+ Jump bb6(v14)
bb5():
- v17:Fixnum[1] = RefineType v10, NotString
- v18:BasicObject = Send v17, :to_s # SendFallbackReason: ObjToString: result is not a string
- Jump bb6(v18)
- bb6(v20:BasicObject):
- v22:CBool = HasType v20, String
- CondBranch v22, bb7(), bb8()
+ v16:Fixnum[1] = RefineType v10, NotString
+ v17:BasicObject = Send v16, :to_s # SendFallbackReason: ObjToString: result is not a string
+ Jump bb6(v17)
+ bb6(v19:BasicObject):
+ CondBranchHasType v19, String, bb7(), bb8()
bb7():
- v24:String = RefineType v20, String
- Jump bb9(v24)
+ v22:String = RefineType v19, String
+ Jump bb9(v22)
bb8():
- v26:StringExact = AnyToString v10
- Jump bb9(v26)
- bb9(v28:String):
- v30:Fixnum[2] = Const Value(2)
- v33:CBool[false] = HasType v30, String
- CondBranch v33, bb10(), bb11()
+ v24:StringExact = AnyToString v10
+ Jump bb9(v24)
+ bb9(v26:String):
+ v28:Fixnum[2] = Const Value(2)
+ CondBranchHasType v28, String, bb10(), bb11()
bb10():
- v35 = RefineType v30, String
- Jump bb12(v35)
+ v32 = RefineType v28, String
+ Jump bb12(v32)
bb11():
- v37:Fixnum[2] = RefineType v30, NotString
- v38:BasicObject = Send v37, :to_s # SendFallbackReason: ObjToString: result is not a string
- Jump bb12(v38)
- bb12(v40:BasicObject):
- v42:CBool = HasType v40, String
- CondBranch v42, bb13(), bb14()
+ v34:Fixnum[2] = RefineType v28, NotString
+ v35:BasicObject = Send v34, :to_s # SendFallbackReason: ObjToString: result is not a string
+ Jump bb12(v35)
+ bb12(v37:BasicObject):
+ CondBranchHasType v37, String, bb13(), bb14()
bb13():
- v44:String = RefineType v40, String
- Jump bb15(v44)
+ v40:String = RefineType v37, String
+ Jump bb15(v40)
bb14():
- v46:StringExact = AnyToString v30
- Jump bb15(v46)
- bb15(v48:String):
- v50:RegexpExact = ToRegexp v28, v48, MULTILINE|IGNORECASE|EXTENDED|NOENCODING
+ v42:StringExact = AnyToString v28
+ Jump bb15(v42)
+ bb15(v44:String):
+ v46:RegexpExact = ToRegexp v26, v44, MULTILINE|IGNORECASE|EXTENDED|NOENCODING
CheckInterrupts
- Return v50
+ Return v46
");
}
diff --git a/zjit/src/options.rs b/zjit/src/options.rs
index a4b3d4cec3ea76..363aaea7c7e30e 100644
--- a/zjit/src/options.rs
+++ b/zjit/src/options.rs
@@ -711,6 +711,14 @@ pub fn set_inline_threshold(inline_threshold: InlineThreshold) {
unsafe { OPTIONS.as_mut().unwrap().inline_threshold = inline_threshold; }
}
+/// Update --zjit-num-profiles for testing
+#[cfg(test)]
+pub fn set_num_profiles(num_profiles: NumProfiles) {
+ rb_zjit_prepare_options();
+ unsafe { OPTIONS.as_mut().unwrap().num_profiles = num_profiles; }
+ update_profile_threshold();
+}
+
/// Set --zjit-mem-size for testing. It's used to force OOM in tests.
#[cfg(test)]
pub fn set_mem_bytes(mem_bytes: usize) {