From 85571cb7124a6193e4c6ae57bfed9c62bcb5de0c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 11 Aug 2018 03:53:36 +0000 Subject: tweak hpb stats destructor output Including the last rb_gc_count() call would be useful, and avoid showing giant SIZE_MAX initializers for acc.min fields. --- ext/mwrap/mwrap.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 138384d..b60c21d 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -1351,21 +1351,26 @@ static void dump_hpb(FILE *fp, unsigned flags) if (flags & DUMP_HPB_STATS) { fprintf(fp, "lifespan_max: %zu\n" - "lifespan_min: %zu\n" + "lifespan_min:%s%zu\n" "lifespan_mean: %0.3f\n" "lifespan_stddev: %0.3f\n" "deathspan_max: %zu\n" - "deathspan_min: %zu\n" + "deathspan_min:%s%zu\n" "deathspan_mean: %0.3f\n" - "deathspan_stddev: %0.3f\n", + "deathspan_stddev: %0.3f\n" + "gc_count: %zu\n", hpb_stats.alive.max, + hpb_stats.alive.min == SIZE_MAX ? " -" : " ", hpb_stats.alive.min, hpb_stats.alive.mean, acc_stddev_dbl(&hpb_stats.alive), hpb_stats.reborn.max, + hpb_stats.reborn.min == SIZE_MAX ? " -" : " ", hpb_stats.reborn.min, hpb_stats.reborn.mean, - acc_stddev_dbl(&hpb_stats.reborn)); + acc_stddev_dbl(&hpb_stats.reborn), + /* n.b.: unsafe to call rb_gc_count() in destructor */ + generation); } if (flags & DUMP_HPB_EACH) { struct alloc_hdr *h; -- cgit v1.2.3-24-ge0c7 From a58386266d4effd90efa840fb0b7d6aa5ce71748 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 11 Aug 2018 03:53:39 +0000 Subject: struct acc: use 64-bit counters Since there's no hope of atomicity here anyways, use 64-bit counters. --- ext/mwrap/mwrap.c | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index b60c21d..cbda436 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -218,14 +218,14 @@ static int has_ec_p(void) } struct acc { - size_t nr; - size_t min; - size_t max; + uint64_t nr; + int64_t min; + int64_t max; double m2; double mean; }; -#define ACC_INIT(name) { .nr=0, .min=SIZE_MAX, .max=0, .m2=0, .mean=0 } +#define ACC_INIT(name) { .nr=0, .min=INT64_MAX, .max=-1, .m2=0, .mean=0 } /* for tracking 16K-aligned heap page bodies (protected by GVL) */ struct { @@ -314,29 +314,35 @@ static void acc_add(struct acc *acc, size_t val) { double delta = val - acc->mean; - size_t nr = ++acc->nr; + uint64_t nr = ++acc->nr; - /* 32-bit overflow, ignore accuracy, just don't divide-by-zero */ + /* just don't divide-by-zero if we ever hit this (unlikely :P) */ if (nr) acc->mean += delta / nr; acc->m2 += delta * (val - acc->mean); - if (val < acc->min) - acc->min = val; - if (val > acc->max) - acc->max = val; + if ((int64_t)val < acc->min) + acc->min = (int64_t)val; + if ((int64_t)val > acc->max) + acc->max = (int64_t)val; } +#if SIZEOF_LONG == 8 +# define INT64toNUM(x) LONG2NUM((long)x) +#elif defined(HAVE_LONG_LONG) && SIZEOF_LONG_LONG == 8 +# define INT64toNUM(x) LL2NUM((LONG_LONG)x) +#endif + static VALUE acc_max(const struct acc *acc) { - return acc->max ? SIZET2NUM(acc->max) : DBL2NUM(HUGE_VAL); + return INT64toNUM(acc->max); } static VALUE acc_min(const struct acc *acc) { - return acc->min == SIZE_MAX ? DBL2NUM(HUGE_VAL) : SIZET2NUM(acc->min); + return acc->min == INT64_MAX ? INT2FIX(-1) : INT64toNUM(acc->min); } static VALUE @@ -1350,22 +1356,22 @@ static void dump_hpb(FILE *fp, unsigned flags) { if (flags & DUMP_HPB_STATS) { fprintf(fp, - "lifespan_max: %zu\n" - "lifespan_min:%s%zu\n" + "lifespan_max: %"PRId64"\n" + "lifespan_min:%s%"PRId64"\n" "lifespan_mean: %0.3f\n" "lifespan_stddev: %0.3f\n" - "deathspan_max: %zu\n" - "deathspan_min:%s%zu\n" + "deathspan_max: %"PRId64"\n" + "deathspan_min:%s%"PRId64"\n" "deathspan_mean: %0.3f\n" "deathspan_stddev: %0.3f\n" "gc_count: %zu\n", hpb_stats.alive.max, - hpb_stats.alive.min == SIZE_MAX ? " -" : " ", + hpb_stats.alive.min == INT64_MAX ? " -" : " ", hpb_stats.alive.min, hpb_stats.alive.mean, acc_stddev_dbl(&hpb_stats.alive), hpb_stats.reborn.max, - hpb_stats.reborn.min == SIZE_MAX ? " -" : " ", + hpb_stats.reborn.min == INT64_MAX ? " -" : " ", hpb_stats.reborn.min, hpb_stats.reborn.mean, acc_stddev_dbl(&hpb_stats.reborn), -- cgit v1.2.3-24-ge0c7 From f2155aca16e5786a1d1189d5eb10458111b78014 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 11 Aug 2018 04:04:42 +0000 Subject: doc: 2.1 pre-release updates Mwrap.total_bytes_allocated/total_bytes_freed API needs docs, and we need to clarify heap page stats are not reset. --- ext/mwrap/mwrap.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index cbda436..2ee04a5 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -1193,11 +1193,17 @@ static VALUE mwrap_quiet(VALUE mod) return rb_ensure(rb_yield, SIZET2NUM(cur), reset_locating, 0); } +/* + * total bytes allocated as tracked by mwrap + */ static VALUE total_inc(VALUE mod) { return SIZET2NUM(total_bytes_inc); } +/* + * total bytes freed as tracked by mwrap + */ static VALUE total_dec(VALUE mod) { return SIZET2NUM(total_bytes_dec); @@ -1338,6 +1344,10 @@ void Init_mwrap(void) * from posix_memalign(3) use in mainline Ruby: * * https://sourceware.org/bugzilla/show_bug.cgi?id=14581 + * + * These statistics are never reset by Mwrap.reset or + * any other method. They only make sense in the context + * of an entire program lifetime. */ hpb = rb_define_class_under(mod, "HeapPageBody", rb_cObject); rb_define_singleton_method(hpb, "stat", hpb_stat, -1); -- cgit v1.2.3-24-ge0c7 From 4a90540056afce4f73da97300e6709993355fe4f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 9 Aug 2022 00:19:11 +0000 Subject: memalign: perform rcu_read_unlock on ENOMEM We must not forget to release RCU read locks even if the process will probably die, soon, due to ENOMEM. I noticed this while working on the Perl5/XS port. Link: https://80x24.org/mwrap-perl/20191102020331.28050-4-e@80x24.org/ --- ext/mwrap/mwrap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 2ee04a5..4575e34 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -632,9 +632,9 @@ internal_memalign(void **pp, size_t alignment, size_t size, uintptr_t caller) p = ptr_align(p, alignment); h = ptr2hdr(p); alloc_insert_rcu(l, h, size, real); - update_stats_rcu_unlock(l); *pp = p; } + update_stats_rcu_unlock(l); } return real ? 0 : ENOMEM; -- cgit v1.2.3-24-ge0c7 From 32c9a817cab2e28534de62ff62c4686b520fb3fe Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 15 Aug 2022 21:22:17 +0000 Subject: workaround breakage from urcu v0.11.4 urcu v0.11.4+ introduced commit 7ca7fe9c03 (Make temporary variable in _rcu_dereference non-const, 2021-07-29) which conflicts with our use of _LGPL_SOURCE. In retrospect, CMM_LOAD_SHARED and CMM_STORE_SHARED seem sufficient for our use of the `totals' cds_lfht pointer since the constructur should always fire before any threads are running. This is fixed in urcu v0.12.4 and v0.13.2 (released 2022-08-18) but I suspect older versions will live on in enterprise/LTS distros for a long while. Link: https://lore.kernel.org/lttng-dev/20220809181927.GA3718@dcvr/ --- ext/mwrap/mwrap.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 4575e34..477b1cb 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -139,8 +139,8 @@ __attribute__((constructor)) static void resolve_malloc(void) _exit(1); } #endif /* !FreeBSD */ - totals = lfht_new(); - if (!totals) + CMM_STORE_SHARED(totals, lfht_new()); + if (!CMM_LOAD_SHARED(totals)) fprintf(stderr, "failed to allocate totals table\n"); err = pthread_atfork(call_rcu_before_fork, @@ -375,7 +375,7 @@ static struct src_loc *totals_add_rcu(struct src_loc *k) struct cds_lfht *t; again: - t = rcu_dereference(totals); + t = CMM_LOAD_SHARED(totals); if (!t) goto out_unlock; cds_lfht_lookup(t, k->hval, loc_eq, k, &iter); cur = cds_lfht_iter_get_node(&iter); @@ -417,7 +417,7 @@ static struct src_loc *update_stats_rcu_lock(size_t size, uintptr_t caller) static const size_t xlen = sizeof(caller); char *dst; - if (caa_unlikely(!totals)) return 0; + if (caa_unlikely(!CMM_LOAD_SHARED(totals))) return 0; if (locating++) goto out; /* do not recurse into another *alloc */ uatomic_add(&total_bytes_inc, size); @@ -808,7 +808,7 @@ static void *dump_to_file(void *x) ++locating; rcu_read_lock(); - t = rcu_dereference(totals); + t = CMM_LOAD_SHARED(totals); if (!t) goto out_unlock; cds_lfht_for_each_entry(t, &iter, l, hnode) { @@ -877,7 +877,7 @@ static void *totals_reset(void *ign) uatomic_set(&total_bytes_dec, 0); rcu_read_lock(); - t = rcu_dereference(totals); + t = CMM_LOAD_SHARED(totals); cds_lfht_for_each_entry(t, &iter, l, hnode) { uatomic_set(&l->total, 0); uatomic_set(&l->allocations, 0); @@ -945,7 +945,7 @@ static VALUE dump_each_rcu(VALUE x) struct cds_lfht_iter iter; struct src_loc *l; - t = rcu_dereference(totals); + t = CMM_LOAD_SHARED(totals); cds_lfht_for_each_entry(t, &iter, l, hnode) { VALUE v[6]; if (l->total <= a->min) continue; @@ -1049,9 +1049,9 @@ static VALUE mwrap_aref(VALUE mod, VALUE loc) if (!k) return val; + t = CMM_LOAD_SHARED(totals); + if (!t) return val; rcu_read_lock(); - t = rcu_dereference(totals); - if (!t) goto out_unlock; cds_lfht_lookup(t, k->hval, loc_eq, k, &iter); cur = cds_lfht_iter_get_node(&iter); @@ -1059,7 +1059,6 @@ static VALUE mwrap_aref(VALUE mod, VALUE loc) l = caa_container_of(cur, struct src_loc, hnode); val = TypedData_Wrap_Struct(cSrcLoc, &src_loc_type, l); } -out_unlock: rcu_read_unlock(); return val; } -- cgit v1.2.3-24-ge0c7 From a90e237d93256ded3088c83520683fc5835da72d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 20 Aug 2022 21:35:21 +0000 Subject: constify arg for totals_add_rcu It's not modified by that function, so constify it for ease-of-reading and review. --- ext/mwrap/mwrap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 477b1cb..8592ea7 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -367,7 +367,7 @@ acc_stddev(const struct acc *acc) return DBL2NUM(acc_stddev_dbl(acc)); } -static struct src_loc *totals_add_rcu(struct src_loc *k) +static struct src_loc *totals_add_rcu(const struct src_loc *k) { struct cds_lfht_iter iter; struct cds_lfht_node *cur; -- cgit v1.2.3-24-ge0c7 From 7ec73b9ccdeaed30e813884f7765281be422bc21 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 20 Aug 2022 21:35:22 +0000 Subject: support Ruby 3.0.x HEAP_PAGE_SIZE no longer estimates malloc overhead in Ruby 3.0.x, but we can now rely on the GC::INTERNAL_CONSTANTS hash to access the true value. We also need to account for Ractors in 3.0+, and thus we need to rely on thread-specific `ruby_current_ec' instead of process-wide `ruby_current_execution_context_ptr' (which no longer exists in 3.0+). Finally, the VM seems prone to making some small immortal allocations in a few places we were not expecting in 2.7 and earlier. Account for that and loosen some exact checks to account for it. Tested on Ruby 3.0.3 and 3.0.4 --- ext/mwrap/mwrap.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 8592ea7..cd68eed 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -3,7 +3,7 @@ * License: GPL-2.0+ */ #define _LGPL_SOURCE /* allows URCU to inline some stuff */ -#include +#include /* defines HAVE_RUBY_RACTOR_H on 3.0+ */ #include #include #include @@ -22,10 +22,24 @@ #include #include "jhash.h" +#if __STDC_VERSION__ >= 201112 +# define MWRAP_TSD _Thread_local +#elif defined(__GNUC__) +# define MWRAP_TSD __thread +#else +# error _Thread_local nor __thread supported +#endif + static ID id_uminus; const char *rb_source_location_cstr(int *line); /* requires 2.6.0dev */ extern int __attribute__((weak)) ruby_thread_has_gvl_p(void); + +#ifdef HAVE_RUBY_RACTOR_H /* Ruby 3.0+ */ +extern MWRAP_TSD void * __attribute__((weak)) ruby_current_ec; +#else /* Ruby 2.6-2.7 */ extern void * __attribute__((weak)) ruby_current_execution_context_ptr; +# define ruby_current_ec ruby_current_execution_context_ptr +#endif extern void * __attribute__((weak)) ruby_current_vm_ptr; /* for rb_gc_count */ extern size_t __attribute__((weak)) rb_gc_count(void); extern VALUE __attribute__((weak)) rb_cObject; @@ -40,9 +54,12 @@ static size_t total_bytes_inc, total_bytes_dec; /* match values in Ruby gc.c */ #define HEAP_PAGE_ALIGN_LOG 14 enum { - HEAP_PAGE_ALIGN = (1UL << HEAP_PAGE_ALIGN_LOG), + HEAP_PAGE_ALIGN = (1UL << HEAP_PAGE_ALIGN_LOG) +#ifndef HEAP_PAGE_SIZE /* Ruby 2.6-2.7 only */ + , REQUIRED_SIZE_BY_MALLOC = (sizeof(size_t) * 5), HEAP_PAGE_SIZE = (HEAP_PAGE_ALIGN - REQUIRED_SIZE_BY_MALLOC) +#endif }; #define IS_HEAP_PAGE_BODY ((struct src_loc *)-1) @@ -75,7 +92,7 @@ static int resolving_malloc; } \ } while (0) -static __thread size_t locating; +static MWRAP_TSD size_t locating; static size_t generation; static size_t page_size; static struct cds_lfht *totals; @@ -214,7 +231,7 @@ static char *int2str(int num, char *dst, size_t * size) static int has_ec_p(void) { return (ruby_thread_has_gvl_p() && ruby_current_vm_ptr && - ruby_current_execution_context_ptr); + ruby_current_ec); } struct acc { -- cgit v1.2.3-24-ge0c7 From 0f1880761db6b1c819887ba9541e78617d61802e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 20 Aug 2022 22:55:05 +0000 Subject: quiet uninitialized and unused variable warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These warnings are already suppressed in similar ways in the Perl/XS port. The only warning which remains is related to cfree, which I'm leaving alone, for now, due to lack of use/test-cases: ../../../../ext/mwrap/mwrap.c:689:6: warning: ‘cfree’ specifies less restrictive attributes than its target ‘free’: ‘leaf’, ‘nothrow’ [-Wmissing-attributes] 689 | void cfree(void *) __attribute__((alias("free"))); | ^~~~~ /usr/include/stdlib.h:565:13: note: ‘cfree’ target declared here 565 | extern void free (void *__ptr) __THROW; | ^~~~ --- ext/mwrap/mwrap.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index cd68eed..a097b2e 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -169,11 +169,18 @@ __attribute__((constructor)) static void resolve_malloc(void) --locating; } +#ifdef NDEBUG +#define QUIET_CC_WARNING(var) (void)var; +#else +#define QUIET_CC_WARNING(var) +#endif + static void mutex_lock(pthread_mutex_t *m) { int err = pthread_mutex_lock(m); assert(err == 0); + QUIET_CC_WARNING(err) } static void @@ -181,6 +188,7 @@ mutex_unlock(pthread_mutex_t *m) { int err = pthread_mutex_unlock(m); assert(err == 0); + QUIET_CC_WARNING(err) } #ifndef HAVE_MEMPCPY @@ -660,16 +668,14 @@ internal_memalign(void **pp, size_t alignment, size_t size, uintptr_t caller) static void * memalign_result(int err, void *p) { - if (caa_unlikely(err)) { + if (caa_unlikely(err)) errno = err; - return 0; - } return p; } void *memalign(size_t alignment, size_t size) { - void *p; + void *p = NULL; int err = internal_memalign(&p, alignment, size, RETURN_ADDRESS(0)); return memalign_result(err, p); } @@ -684,7 +690,7 @@ void cfree(void *) __attribute__((alias("free"))); void *valloc(size_t size) { - void *p; + void *p = NULL; int err = internal_memalign(&p, page_size, size, RETURN_ADDRESS(0)); return memalign_result(err, p); } @@ -702,7 +708,7 @@ void *valloc(size_t size) void *pvalloc(size_t size) { size_t alignment = page_size; - void *p; + void *p = NULL; int err; if (add_overflow_p(size, alignment)) { -- cgit v1.2.3-24-ge0c7 From 61f9d94e11e046094c607bdf56c9633938e074b2 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Aug 2022 17:17:57 +0000 Subject: various doc updates Copyright years are unnecessary churn and probably aren't necessary: https://www.linuxfoundation.org/blog/copyright-notices-in-open-source-software-projects/ add POP3 and IMAP archive info, and drop the outdated link to the live demo since I no longer have the resources to run it. Finally, add rdoc (olddoc) + rsync support --- ext/mwrap/mwrap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index a097b2e..85b847b 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 mwrap hackers + * Copyright (C) mwrap hackers * License: GPL-2.0+ */ #define _LGPL_SOURCE /* allows URCU to inline some stuff */ -- cgit v1.2.3-24-ge0c7 From 867b36082111f564f6e55f63cd21fa0c94df2d20 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 23 Aug 2022 09:00:30 +0000 Subject: support --enabled-shared builds of Ruby Most GNU/Linux distros build Ruby with --enable-shared, so it makes sense to support it properly even if it's not the default favored by ruby-core. __attribute__((weak)) on a local function is not weak enough for the shared library, so we add the extra check for the function's existence to have_ec_p(), instead. Tested with ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [x86_64-linux-gnu] on Debian 11 (bullseye) as well as default builds w/o --enable-shared. Note: FreeBSD 12.3 appears broken with mwrap and I no longer have older FreeBSD systems handy. --- ext/mwrap/mwrap.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 85b847b..9d90298 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -32,7 +32,6 @@ static ID id_uminus; const char *rb_source_location_cstr(int *line); /* requires 2.6.0dev */ -extern int __attribute__((weak)) ruby_thread_has_gvl_p(void); #ifdef HAVE_RUBY_RACTOR_H /* Ruby 3.0+ */ extern MWRAP_TSD void * __attribute__((weak)) ruby_current_ec; @@ -45,6 +44,7 @@ extern size_t __attribute__((weak)) rb_gc_count(void); extern VALUE __attribute__((weak)) rb_cObject; extern VALUE __attribute__((weak)) rb_eTypeError; extern VALUE __attribute__((weak)) rb_yield(VALUE); +int __attribute__((weak)) ruby_thread_has_gvl_p(void); static size_t total_bytes_inc, total_bytes_dec; @@ -64,11 +64,6 @@ enum { #define IS_HEAP_PAGE_BODY ((struct src_loc *)-1) -int __attribute__((weak)) ruby_thread_has_gvl_p(void) -{ - return 0; -} - #ifdef __FreeBSD__ void *__malloc(size_t); void __free(void *); @@ -238,8 +233,8 @@ static char *int2str(int num, char *dst, size_t * size) */ static int has_ec_p(void) { - return (ruby_thread_has_gvl_p() && ruby_current_vm_ptr && - ruby_current_ec); + return ruby_thread_has_gvl_p && ruby_thread_has_gvl_p() && + ruby_current_vm_ptr && ruby_current_ec; } struct acc { -- cgit v1.2.3-24-ge0c7 From d505b403d5d8810bc104425d9296b358f49bddb5 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Sep 2022 09:27:41 +0000 Subject: cleanup some FreeBSD-related workarounds While FreeBSD 12.3 + Ruby 3.0.4p208 remains broken with mwrap (likely due to threading bugs in Ruby) at least get the code more consistently closer to a working state. Thus we'll remember to account for PTHREAD_MUTEX_INITIALIZER requiring malloc for HeapPageBody tracking, as we do with other mutexes. For reference, the Perl5 port of mwrap seems to have no problems on FreeBSD 12.3, so it seems down to misbehavior w.r.t. pthreads usage within Ruby itself. Perl5 on FreeBSD is configured with threads support, but Perl5 doesn't spawn background threads by default like Ruby can. --- ext/mwrap/mwrap.c | 93 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 53 insertions(+), 40 deletions(-) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 9d90298..1d6baec 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -99,9 +99,43 @@ union padded_mutex { /* a round-robin pool of mutexes */ #define MUTEX_NR (1 << 6) #define MUTEX_MASK (MUTEX_NR - 1) +#ifdef __FreeBSD__ +# define STATIC_MTX_INIT_OK (0) +#else /* only tested on Linux + glibc */ +# define STATIC_MTX_INIT_OK (1) +#endif static size_t mutex_i; static union padded_mutex mutexes[MUTEX_NR] = { +#if STATIC_MTX_INIT_OK [0 ... (MUTEX_NR-1)].mtx = PTHREAD_MUTEX_INITIALIZER +#endif +}; + +#define ACC_INIT(name) { .nr=0, .min=INT64_MAX, .max=-1, .m2=0, .mean=0 } +struct acc { + uint64_t nr; + int64_t min; + int64_t max; + double m2; + double mean; +}; + +/* for tracking 16K-aligned heap page bodies (protected by GVL) */ +struct { + pthread_mutex_t lock; + struct cds_list_head bodies; + struct cds_list_head freed; + + struct acc alive; + struct acc reborn; +} hpb_stats = { +#if STATIC_MTX_INIT_OK + .lock = PTHREAD_MUTEX_INITIALIZER, +#endif + .bodies = CDS_LIST_HEAD_INIT(hpb_stats.bodies), + .freed = CDS_LIST_HEAD_INIT(hpb_stats.freed), + .alive = ACC_INIT(hpb_stats.alive), + .reborn = ACC_INIT(hpb_stats.reborn) }; static pthread_mutex_t *mutex_assign(void) @@ -120,12 +154,11 @@ __attribute__((constructor)) static void resolve_malloc(void) int err; ++locating; -#ifdef __FreeBSD__ /* * PTHREAD_MUTEX_INITIALIZER on FreeBSD means lazy initialization, * which happens at pthread_mutex_lock, and that calls calloc */ - { + if (!STATIC_MTX_INIT_OK) { size_t i; for (i = 0; i < MUTEX_NR; i++) { @@ -135,22 +168,28 @@ __attribute__((constructor)) static void resolve_malloc(void) _exit(1); } } + err = pthread_mutex_init(&hpb_stats.lock, 0); + if (err) { + fprintf(stderr, "error: %s\n", strerror(err)); + _exit(1); + } /* initialize mutexes used by urcu-bp */ rcu_read_lock(); rcu_read_unlock(); +#ifndef __FreeBSD__ + } else { + if (!real_malloc) { + resolving_malloc = 1; + real_malloc = dlsym(RTLD_NEXT, "malloc"); + } + real_free = dlsym(RTLD_NEXT, "free"); + if (!real_malloc || !real_free) { + fprintf(stderr, "missing malloc/aligned_alloc/free\n" + "\t%p %p\n", real_malloc, real_free); + _exit(1); + } +#endif /* !__FreeBSD__ */ } -#else /* !FreeBSD (tested on GNU/Linux) */ - if (!real_malloc) { - resolving_malloc = 1; - real_malloc = dlsym(RTLD_NEXT, "malloc"); - } - real_free = dlsym(RTLD_NEXT, "free"); - if (!real_malloc || !real_free) { - fprintf(stderr, "missing malloc/aligned_alloc/free\n" - "\t%p %p\n", real_malloc, real_free); - _exit(1); - } -#endif /* !FreeBSD */ CMM_STORE_SHARED(totals, lfht_new()); if (!CMM_LOAD_SHARED(totals)) fprintf(stderr, "failed to allocate totals table\n"); @@ -237,32 +276,6 @@ static int has_ec_p(void) ruby_current_vm_ptr && ruby_current_ec; } -struct acc { - uint64_t nr; - int64_t min; - int64_t max; - double m2; - double mean; -}; - -#define ACC_INIT(name) { .nr=0, .min=INT64_MAX, .max=-1, .m2=0, .mean=0 } - -/* for tracking 16K-aligned heap page bodies (protected by GVL) */ -struct { - pthread_mutex_t lock; - struct cds_list_head bodies; - struct cds_list_head freed; - - struct acc alive; - struct acc reborn; -} hpb_stats = { - .lock = PTHREAD_MUTEX_INITIALIZER, - .bodies = CDS_LIST_HEAD_INIT(hpb_stats.bodies), - .freed = CDS_LIST_HEAD_INIT(hpb_stats.freed), - .alive = ACC_INIT(hpb_stats.alive), - .reborn = ACC_INIT(hpb_stats.reborn) -}; - /* allocated via real_malloc/real_free */ struct src_loc { pthread_mutex_t *mtx; -- cgit v1.2.3-24-ge0c7 From fd15a49eb477b345e97ca3bbd6cde135cd0d6583 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Sep 2022 10:50:53 +0000 Subject: Ractor compatibility We can no longer depend on having GVL/GIL when we have a Ruby execution context, so so make `kbuf' thread-specific to avoid data corruption. This was ported from the Perl5 version, since Perl5 has had MVM/Ractor-like abilities with native threads for decades, despite the Perl5 ecosystem largely avoids and discourages threads. --- ext/mwrap/mwrap.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 1d6baec..90f560e 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -308,7 +308,9 @@ struct alloc_hdr { size_t size; }; -static char kbuf[PATH_MAX + INT2STR_MAX + sizeof(struct alloc_hdr) + 2]; +static MWRAP_TSD char kbuf[ + PATH_MAX + INT2STR_MAX + sizeof(struct alloc_hdr) + 2 +]; static struct alloc_hdr *ptr2hdr(void *p) { -- cgit v1.2.3-24-ge0c7 From 13ecb7e5cf4ee769801e53d9df87141c6730e825 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Sep 2022 10:58:20 +0000 Subject: paranoid safety fix to clamp pathnames to PATH_MAX While I doubt Ruby (nor Perl) would store pathnames longer than PATH_MAX by default, it's possible `eval' users to specify whatever path (and line number) they wish to use. Likely was the case with `# line $FILE' directives in Perl5 which prompted this clamping. --- ext/mwrap/mwrap.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 90f560e..08761d6 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -470,6 +470,8 @@ static struct src_loc *update_stats_rcu_lock(size_t size, uintptr_t caller) /* avoid vsnprintf or anything which could call malloc here: */ len = strlen(ptr); + if (len > PATH_MAX) + len = PATH_MAX; k = (void *)kbuf; k->total = size; dst = mempcpy(k->k, ptr, len); -- cgit v1.2.3-24-ge0c7 From a03b72e85011b71e031447b9c8c917e6f2c08c81 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 7 Jan 2023 21:51:14 +0000 Subject: drop heap page support for Ruby <= 3.0 Ruby 3.1 uses mmap, nowadays, and I don't think it's worth the effort to suport it since mmap and munmap don't require the symmetry *memalign + free do. --- ext/mwrap/mwrap.c | 327 ++---------------------------------------------------- 1 file changed, 12 insertions(+), 315 deletions(-) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 08761d6..6875486 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -51,19 +51,6 @@ static size_t total_bytes_inc, total_bytes_dec; /* true for glibc/dlmalloc/ptmalloc, not sure about jemalloc */ #define ASSUMED_MALLOC_ALIGNMENT (sizeof(void *) * 2) -/* match values in Ruby gc.c */ -#define HEAP_PAGE_ALIGN_LOG 14 -enum { - HEAP_PAGE_ALIGN = (1UL << HEAP_PAGE_ALIGN_LOG) -#ifndef HEAP_PAGE_SIZE /* Ruby 2.6-2.7 only */ - , - REQUIRED_SIZE_BY_MALLOC = (sizeof(size_t) * 5), - HEAP_PAGE_SIZE = (HEAP_PAGE_ALIGN - REQUIRED_SIZE_BY_MALLOC) -#endif -}; - -#define IS_HEAP_PAGE_BODY ((struct src_loc *)-1) - #ifdef __FreeBSD__ void *__malloc(size_t); void __free(void *); @@ -111,33 +98,6 @@ static union padded_mutex mutexes[MUTEX_NR] = { #endif }; -#define ACC_INIT(name) { .nr=0, .min=INT64_MAX, .max=-1, .m2=0, .mean=0 } -struct acc { - uint64_t nr; - int64_t min; - int64_t max; - double m2; - double mean; -}; - -/* for tracking 16K-aligned heap page bodies (protected by GVL) */ -struct { - pthread_mutex_t lock; - struct cds_list_head bodies; - struct cds_list_head freed; - - struct acc alive; - struct acc reborn; -} hpb_stats = { -#if STATIC_MTX_INIT_OK - .lock = PTHREAD_MUTEX_INITIALIZER, -#endif - .bodies = CDS_LIST_HEAD_INIT(hpb_stats.bodies), - .freed = CDS_LIST_HEAD_INIT(hpb_stats.freed), - .alive = ACC_INIT(hpb_stats.alive), - .reborn = ACC_INIT(hpb_stats.reborn) -}; - static pthread_mutex_t *mutex_assign(void) { return &mutexes[uatomic_add_return(&mutex_i, 1) & MUTEX_MASK].mtx; @@ -168,11 +128,6 @@ __attribute__((constructor)) static void resolve_malloc(void) _exit(1); } } - err = pthread_mutex_init(&hpb_stats.lock, 0); - if (err) { - fprintf(stderr, "error: %s\n", strerror(err)); - _exit(1); - } /* initialize mutexes used by urcu-bp */ rcu_read_lock(); rcu_read_unlock(); @@ -300,9 +255,6 @@ struct alloc_hdr { struct src_loc *loc; } live; struct rcu_head dead; - struct { - size_t at; /* rb_gc_count() */ - } hpb_freed; } as; void *real; /* what to call real_free on */ size_t size; @@ -344,64 +296,6 @@ static int loc_eq(struct cds_lfht_node *node, const void *key) memcmp(k->k, existing->k, loc_size(k)) == 0); } -/* note: not atomic */ -static void -acc_add(struct acc *acc, size_t val) -{ - double delta = val - acc->mean; - uint64_t nr = ++acc->nr; - - /* just don't divide-by-zero if we ever hit this (unlikely :P) */ - if (nr) - acc->mean += delta / nr; - - acc->m2 += delta * (val - acc->mean); - if ((int64_t)val < acc->min) - acc->min = (int64_t)val; - if ((int64_t)val > acc->max) - acc->max = (int64_t)val; -} - -#if SIZEOF_LONG == 8 -# define INT64toNUM(x) LONG2NUM((long)x) -#elif defined(HAVE_LONG_LONG) && SIZEOF_LONG_LONG == 8 -# define INT64toNUM(x) LL2NUM((LONG_LONG)x) -#endif - -static VALUE -acc_max(const struct acc *acc) -{ - return INT64toNUM(acc->max); -} - -static VALUE -acc_min(const struct acc *acc) -{ - return acc->min == INT64_MAX ? INT2FIX(-1) : INT64toNUM(acc->min); -} - -static VALUE -acc_mean(const struct acc *acc) -{ - return DBL2NUM(acc->nr ? acc->mean : HUGE_VAL); -} - -static double -acc_stddev_dbl(const struct acc *acc) -{ - if (acc->nr > 1) { - double variance = acc->m2 / (acc->nr - 1); - return sqrt(variance); - } - return 0.0; -} - -static VALUE -acc_stddev(const struct acc *acc) -{ - return DBL2NUM(acc_stddev_dbl(acc)); -} - static struct src_loc *totals_add_rcu(const struct src_loc *k) { struct cds_lfht_iter iter; @@ -519,7 +413,7 @@ void free(void *p) struct src_loc *l = h->as.live.loc; if (!real_free) return; /* oh well, leak a little */ - if (l && l != IS_HEAP_PAGE_BODY) { + if (l) { size_t age = generation - h->as.live.gen; uatomic_add(&total_bytes_dec, h->size); @@ -534,19 +428,6 @@ void free(void *p) mutex_unlock(l->mtx); call_rcu(&h->as.dead, free_hdr_rcu); - } else if (l == IS_HEAP_PAGE_BODY) { - size_t gen = generation; - size_t age = gen - h->as.live.gen; - - h->as.hpb_freed.at = gen; - - mutex_lock(&hpb_stats.lock); - acc_add(&hpb_stats.alive, age); - - /* hpb_stats.bodies => hpb_stats.freed */ - cds_list_move(&h->anode, &hpb_stats.freed); - - mutex_unlock(&hpb_stats.lock); } else { real_free(h->real); } @@ -614,65 +495,18 @@ internal_memalign(void **pp, size_t alignment, size_t size, uintptr_t caller) return ENOMEM; - if (alignment == HEAP_PAGE_ALIGN && size == HEAP_PAGE_SIZE) { - if (has_ec_p()) generation = rb_gc_count(); - l = IS_HEAP_PAGE_BODY; - } else { - l = update_stats_rcu_lock(size, caller); - } + l = update_stats_rcu_lock(size, caller); - if (l == IS_HEAP_PAGE_BODY) { - void *p; - size_t gen = generation; - - mutex_lock(&hpb_stats.lock); - - /* reuse existing entry */ - if (!cds_list_empty(&hpb_stats.freed)) { - size_t deathspan; - - h = cds_list_first_entry(&hpb_stats.freed, - struct alloc_hdr, anode); - /* hpb_stats.freed => hpb_stats.bodies */ - cds_list_move(&h->anode, &hpb_stats.bodies); - assert(h->size == size); - assert(h->real); - real = h->real; - p = hdr2ptr(h); - assert(ptr_is_aligned(p, alignment)); - - deathspan = gen - h->as.hpb_freed.at; - acc_add(&hpb_stats.reborn, deathspan); - } - else { - real = real_malloc(asize); - if (!real) return ENOMEM; - - p = hdr2ptr(real); - if (!ptr_is_aligned(p, alignment)) - p = ptr_align(p, alignment); - h = ptr2hdr(p); - h->size = size; - h->real = real; - cds_list_add(&h->anode, &hpb_stats.bodies); - } - mutex_unlock(&hpb_stats.lock); - h->as.live.loc = l; - h->as.live.gen = gen; + real = real_malloc(asize); + if (real) { + void *p = hdr2ptr(real); + if (!ptr_is_aligned(p, alignment)) + p = ptr_align(p, alignment); + h = ptr2hdr(p); + alloc_insert_rcu(l, h, size, real); *pp = p; } - else { - real = real_malloc(asize); - if (real) { - void *p = hdr2ptr(real); - if (!ptr_is_aligned(p, alignment)) - p = ptr_align(p, alignment); - h = ptr2hdr(p); - alloc_insert_rcu(l, h, size, real); - *pp = p; - } - update_stats_rcu_unlock(l); - } + update_stats_rcu_unlock(l); return real ? 0 : ENOMEM; } @@ -1243,73 +1077,6 @@ static VALUE total_dec(VALUE mod) return SIZET2NUM(total_bytes_dec); } -static VALUE hpb_each_yield(VALUE ignore) -{ - struct alloc_hdr *h, *next; - - cds_list_for_each_entry_safe(h, next, &hpb_stats.bodies, anode) { - VALUE v[2]; /* [ generation, address ] */ - void *addr = hdr2ptr(h); - assert(ptr_is_aligned(addr, HEAP_PAGE_ALIGN)); - v[0] = LONG2NUM((long)addr); - v[1] = SIZET2NUM(h->as.live.gen); - rb_yield_values2(2, v); - } - return Qnil; -} - -/* - * call-seq: - * - * Mwrap::HeapPageBody.each { |gen, addr| } -> Integer - * - * Yields the generation (GC.count) the heap page body was created - * and address of the heap page body as an Integer. Returns the - * number of allocated pages as an Integer. This return value should - * match the result of GC.stat(:heap_allocated_pages) - */ -static VALUE hpb_each(VALUE mod) -{ - ++locating; - return rb_ensure(hpb_each_yield, Qfalse, reset_locating, 0); -} - -/* - * call-seq: - * - * Mwrap::HeapPageBody.stat -> Hash - * Mwrap::HeapPageBody.stat(hash) -> hash - * - * The maximum lifespan of a heap page body in the Ruby VM. - * This may be Infinity if no heap page bodies were ever freed. - */ -static VALUE hpb_stat(int argc, VALUE *argv, VALUE hpb) -{ - VALUE h; - - rb_scan_args(argc, argv, "01", &h); - if (NIL_P(h)) - h = rb_hash_new(); - else if (!RB_TYPE_P(h, T_HASH)) - rb_raise(rb_eTypeError, "not a hash %+"PRIsVALUE, h); - - ++locating; -#define S(x) ID2SYM(rb_intern(#x)) - rb_hash_aset(h, S(lifespan_max), acc_max(&hpb_stats.alive)); - rb_hash_aset(h, S(lifespan_min), acc_min(&hpb_stats.alive)); - rb_hash_aset(h, S(lifespan_mean), acc_mean(&hpb_stats.alive)); - rb_hash_aset(h, S(lifespan_stddev), acc_stddev(&hpb_stats.alive)); - rb_hash_aset(h, S(deathspan_max), acc_max(&hpb_stats.reborn)); - rb_hash_aset(h, S(deathspan_min), acc_min(&hpb_stats.reborn)); - rb_hash_aset(h, S(deathspan_mean), acc_mean(&hpb_stats.reborn)); - rb_hash_aset(h, S(deathspan_stddev), acc_stddev(&hpb_stats.reborn)); - rb_hash_aset(h, S(resurrects), SIZET2NUM(hpb_stats.reborn.nr)); -#undef S - --locating; - - return h; -} - /* * Document-module: Mwrap * @@ -1328,19 +1095,13 @@ static VALUE hpb_stat(int argc, VALUE *argv, VALUE hpb) * * dump_fd: a writable FD to dump to * * dump_path: a path to dump to, the file is opened in O_APPEND mode * * dump_min: the minimum allocation size (total) to dump - * * dump_heap: mask of heap_page_body statistics to dump * * If both `dump_fd' and `dump_path' are specified, dump_path takes * precedence. - * - * dump_heap bitmask - * * 0x01 - summary stats (same info as HeapPageBody.stat) - * * 0x02 - all live heaps (similar to HeapPageBody.each) - * * 0x04 - skip non-heap_page_body-related output */ void Init_mwrap(void) { - VALUE mod, hpb; + VALUE mod; ++locating; mod = rb_define_module("Mwrap"); @@ -1372,67 +1133,9 @@ void Init_mwrap(void) rb_define_method(cSrcLoc, "max_lifespan", src_loc_max_lifespan, 0); rb_define_method(cSrcLoc, "name", src_loc_name, 0); - /* - * Information about "struct heap_page_body" allocations from - * Ruby gc.c. This can be useful for tracking fragmentation - * from posix_memalign(3) use in mainline Ruby: - * - * https://sourceware.org/bugzilla/show_bug.cgi?id=14581 - * - * These statistics are never reset by Mwrap.reset or - * any other method. They only make sense in the context - * of an entire program lifetime. - */ - hpb = rb_define_class_under(mod, "HeapPageBody", rb_cObject); - rb_define_singleton_method(hpb, "stat", hpb_stat, -1); - rb_define_singleton_method(hpb, "each", hpb_each, 0); - --locating; } -enum { - DUMP_HPB_STATS = 0x1, - DUMP_HPB_EACH = 0x2, - DUMP_HPB_EXCL = 0x4, -}; - -static void dump_hpb(FILE *fp, unsigned flags) -{ - if (flags & DUMP_HPB_STATS) { - fprintf(fp, - "lifespan_max: %"PRId64"\n" - "lifespan_min:%s%"PRId64"\n" - "lifespan_mean: %0.3f\n" - "lifespan_stddev: %0.3f\n" - "deathspan_max: %"PRId64"\n" - "deathspan_min:%s%"PRId64"\n" - "deathspan_mean: %0.3f\n" - "deathspan_stddev: %0.3f\n" - "gc_count: %zu\n", - hpb_stats.alive.max, - hpb_stats.alive.min == INT64_MAX ? " -" : " ", - hpb_stats.alive.min, - hpb_stats.alive.mean, - acc_stddev_dbl(&hpb_stats.alive), - hpb_stats.reborn.max, - hpb_stats.reborn.min == INT64_MAX ? " -" : " ", - hpb_stats.reborn.min, - hpb_stats.reborn.mean, - acc_stddev_dbl(&hpb_stats.reborn), - /* n.b.: unsafe to call rb_gc_count() in destructor */ - generation); - } - if (flags & DUMP_HPB_EACH) { - struct alloc_hdr *h; - - cds_list_for_each_entry(h, &hpb_stats.bodies, anode) { - void *addr = hdr2ptr(h); - - fprintf(fp, "%p\t%zu\n", addr, h->as.live.gen); - } - } -} - /* rb_cloexec_open isn't usable by non-Ruby processes */ #ifndef O_CLOEXEC # define O_CLOEXEC 0 @@ -1446,7 +1149,6 @@ static void mwrap_dump_destructor(void) struct dump_arg a = { .min = 0 }; size_t i; int dump_fd; - unsigned dump_heap = 0; char *dump_path; char *s; @@ -1478,9 +1180,6 @@ static void mwrap_dump_destructor(void) if ((s = strstr(opt, "dump_min:"))) sscanf(s, "dump_min:%zu", &a.min); - if ((s = strstr(opt, "dump_heap:"))) - sscanf(s, "dump_heap:%u", &dump_heap); - switch (dump_fd) { case 0: goto out; case 1: a.fp = stdout; break; @@ -1500,9 +1199,7 @@ static void mwrap_dump_destructor(void) } /* we'll leak some memory here, but this is a destructor */ } - if ((dump_heap & DUMP_HPB_EXCL) == 0) - dump_to_file(&a); - dump_hpb(a.fp, dump_heap); + dump_to_file(&a); out: --locating; } -- cgit v1.2.3-24-ge0c7 From 2c25edb01139365f4754985c1e3494765dd1e5a7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 7 Jan 2023 20:13:13 +0000 Subject: undefine Mwrap::SourceLocation.allocate This quiets `undefining the allocator of T_DATA class Mwrap::SourceLocation' warnings. --- ext/mwrap/mwrap.c | 1 + 1 file changed, 1 insertion(+) (limited to 'ext/mwrap/mwrap.c') diff --git a/ext/mwrap/mwrap.c b/ext/mwrap/mwrap.c index 6875486..160007f 100644 --- a/ext/mwrap/mwrap.c +++ b/ext/mwrap/mwrap.c @@ -1115,6 +1115,7 @@ void Init_mwrap(void) * This class is only available since mwrap 2.0.0+. */ cSrcLoc = rb_define_class_under(mod, "SourceLocation", rb_cObject); + rb_undef_alloc_func(cSrcLoc); rb_define_singleton_method(mod, "dump", mwrap_dump, -1); rb_define_singleton_method(mod, "reset", mwrap_reset, 0); rb_define_singleton_method(mod, "clear", mwrap_clear, 0); -- cgit v1.2.3-24-ge0c7