diff --git a/.gitignore b/.gitignore index 7519ab3..b1c7e15 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,4 @@ .ruby-version Gemfile.lock -# ignore resources directory, where temp build artifacts are placed -res - -# ignore the gc module build directory -mod_gc +tmp diff --git a/Gemfile b/Gemfile index 9880b5c..6e8458f 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,4 @@ source "https://rubygems.org" gem "rake" +gem "rake-compiler" diff --git a/Rakefile b/Rakefile index 0fb1984..6da5a51 100644 --- a/Rakefile +++ b/Rakefile @@ -1,34 +1,36 @@ # frozen_string_literal: true -$LOAD_PATH << File.expand_path("tool", __dir__) +require "fileutils" +require "net/http" +require "rbconfig" +require "rake/extensiontask" -require 'fileutils' -require 'tempfile' -require 'net/http' -require 'digest' -require 'etc' -require 'mmtk_support' +task default: [:compile, :install] -task default: %i[compile install] - -desc <<~DESC - Install the MMTk GC shared object into the Shared GC dir for the currently - running Ruby -DESC -task :install, :compile do - install_mmtk +Rake::ExtensionTask.new do |ext| + ext.name = "librubygc.mmtk" + ext.ext_dir = "gc/mmtk" + ext.lib_dir = "tmp/binaries" end -desc <<~DESC - Build the MMTk GC implementation shared object -DESC -task :compile do - MMTkSupport.new.build +task :install do + FileUtils.mv(Dir.glob("tmp/binaries/*"), RbConfig::CONFIG["modular_gc_dir"]) end -desc <<~DESC - Remove all generated build artifacts -DESC -task :clean do - system("git clean -ffdx") +RUBY_HEADERS = %w[ + ccan/check_type/check_type.h ccan/container_of/container_of.h ccan/list/list.h ccan/str/str.h + gc/gc_impl.h gc/gc.h gc/extconf_base.rb + darray.h +] +task :vendor_ruby_headers do + RUBY_HEADERS.each do |file| + Net::HTTP.start("raw.githubusercontent.com", 443, use_ssl: true) do |http| + resp = http.get("ruby/ruby/refs/heads/master/#{file}") + + FileUtils.mkdir_p(File.dirname(file)) + open(file, "wb") do |file| + file.write(resp.body) + end + end + end end diff --git a/ccan/check_type/check_type.h b/ccan/check_type/check_type.h new file mode 100644 index 0000000..659e1a5 --- /dev/null +++ b/ccan/check_type/check_type.h @@ -0,0 +1,63 @@ +/* CC0 (Public domain) - see ccan/licenses/CC0 file for details */ +#ifndef CCAN_CHECK_TYPE_H +#define CCAN_CHECK_TYPE_H + +/** + * ccan_check_type - issue a warning or build failure if type is not correct. + * @expr: the expression whose type we should check (not evaluated). + * @type: the exact type we expect the expression to be. + * + * This macro is usually used within other macros to try to ensure that a macro + * argument is of the expected type. No type promotion of the expression is + * done: an unsigned int is not the same as an int! + * + * ccan_check_type() always evaluates to 0. + * + * If your compiler does not support typeof, then the best we can do is fail + * to compile if the sizes of the types are unequal (a less complete check). + * + * Example: + * // They should always pass a 64-bit value to _set_some_value! + * #define set_some_value(expr) \ + * _set_some_value((ccan_check_type((expr), uint64_t), (expr))) + */ + +/** + * ccan_check_types_match - issue a warning or build failure if types are not same. + * @expr1: the first expression (not evaluated). + * @expr2: the second expression (not evaluated). + * + * This macro is usually used within other macros to try to ensure that + * arguments are of identical types. No type promotion of the expressions is + * done: an unsigned int is not the same as an int! + * + * ccan_check_types_match() always evaluates to 0. + * + * If your compiler does not support typeof, then the best we can do is fail + * to compile if the sizes of the types are unequal (a less complete check). + * + * Example: + * // Do subtraction to get to enclosing type, but make sure that + * // pointer is of correct type for that member. + * #define ccan_container_of(mbr_ptr, encl_type, mbr) \ + * (ccan_check_types_match((mbr_ptr), &((encl_type *)0)->mbr), \ + * ((encl_type *) \ + * ((char *)(mbr_ptr) - offsetof(enclosing_type, mbr)))) + */ +#if defined(HAVE_TYPEOF) && HAVE_TYPEOF +#define ccan_check_type(expr, type) \ + ((typeof(expr) *)0 != (type *)0) + +#define ccan_check_types_match(expr1, expr2) \ + ((typeof(expr1) *)0 != (typeof(expr2) *)0) +#else +#include "ccan/build_assert/build_assert.h" +/* Without typeof, we can only test the sizes. */ +#define ccan_check_type(expr, type) \ + CCAN_BUILD_ASSERT_OR_ZERO(sizeof(expr) == sizeof(type)) + +#define ccan_check_types_match(expr1, expr2) \ + CCAN_BUILD_ASSERT_OR_ZERO(sizeof(expr1) == sizeof(expr2)) +#endif /* HAVE_TYPEOF */ + +#endif /* CCAN_CHECK_TYPE_H */ diff --git a/ccan/container_of/container_of.h b/ccan/container_of/container_of.h new file mode 100644 index 0000000..872bb6e --- /dev/null +++ b/ccan/container_of/container_of.h @@ -0,0 +1,142 @@ +/* CC0 (Public domain) - see ccan/licenses/CC0 file for details */ +#ifndef CCAN_CONTAINER_OF_H +#define CCAN_CONTAINER_OF_H +#include "ccan/check_type/check_type.h" + +/** + * ccan_container_of - get pointer to enclosing structure + * @member_ptr: pointer to the structure member + * @containing_type: the type this member is within + * @member: the name of this member within the structure. + * + * Given a pointer to a member of a structure, this macro does pointer + * subtraction to return the pointer to the enclosing type. + * + * Example: + * struct foo { + * int fielda, fieldb; + * // ... + * }; + * struct info { + * int some_other_field; + * struct foo my_foo; + * }; + * + * static struct info *foo_to_info(struct foo *foo) + * { + * return ccan_container_of(foo, struct info, my_foo); + * } + */ +#define ccan_container_of(member_ptr, containing_type, member) \ + ((containing_type *) \ + ((char *)(member_ptr) \ + - ccan_container_off(containing_type, member)) \ + + ccan_check_types_match(*(member_ptr), ((containing_type *)0)->member)) + + +/** + * ccan_container_of_or_null - get pointer to enclosing structure, or NULL + * @member_ptr: pointer to the structure member + * @containing_type: the type this member is within + * @member: the name of this member within the structure. + * + * Given a pointer to a member of a structure, this macro does pointer + * subtraction to return the pointer to the enclosing type, unless it + * is given NULL, in which case it also returns NULL. + * + * Example: + * struct foo { + * int fielda, fieldb; + * // ... + * }; + * struct info { + * int some_other_field; + * struct foo my_foo; + * }; + * + * static struct info *foo_to_info_allowing_null(struct foo *foo) + * { + * return ccan_container_of_or_null(foo, struct info, my_foo); + * } + */ +static inline char *container_of_or_null_(void *member_ptr, size_t offset) +{ + return member_ptr ? (char *)member_ptr - offset : NULL; +} +#define ccan_container_of_or_null(member_ptr, containing_type, member) \ + ((containing_type *) \ + ccan_container_of_or_null_(member_ptr, \ + ccan_container_off(containing_type, member)) \ + + ccan_check_types_match(*(member_ptr), ((containing_type *)0)->member)) + +/** + * ccan_container_off - get offset to enclosing structure + * @containing_type: the type this member is within + * @member: the name of this member within the structure. + * + * Given a pointer to a member of a structure, this macro does + * typechecking and figures out the offset to the enclosing type. + * + * Example: + * struct foo { + * int fielda, fieldb; + * // ... + * }; + * struct info { + * int some_other_field; + * struct foo my_foo; + * }; + * + * static struct info *foo_to_info(struct foo *foo) + * { + * size_t off = ccan_container_off(struct info, my_foo); + * return (void *)((char *)foo - off); + * } + */ +#define ccan_container_off(containing_type, member) \ + offsetof(containing_type, member) + +/** + * ccan_container_of_var - get pointer to enclosing structure using a variable + * @member_ptr: pointer to the structure member + * @container_var: a pointer of same type as this member's container + * @member: the name of this member within the structure. + * + * Given a pointer to a member of a structure, this macro does pointer + * subtraction to return the pointer to the enclosing type. + * + * Example: + * static struct info *foo_to_i(struct foo *foo) + * { + * struct info *i = ccan_container_of_var(foo, i, my_foo); + * return i; + * } + */ +#if defined(HAVE_TYPEOF) && HAVE_TYPEOF +#define ccan_container_of_var(member_ptr, container_var, member) \ + ccan_container_of(member_ptr, typeof(*container_var), member) +#else +#define ccan_container_of_var(member_ptr, container_var, member) \ + ((void *)((char *)(member_ptr) - \ + ccan_container_off_var(container_var, member))) +#endif + +/** + * ccan_container_off_var - get offset of a field in enclosing structure + * @container_var: a pointer to a container structure + * @member: the name of a member within the structure. + * + * Given (any) pointer to a structure and a its member name, this + * macro does pointer subtraction to return offset of member in a + * structure memory layout. + * + */ +#if defined(HAVE_TYPEOF) && HAVE_TYPEOF +#define ccan_container_off_var(var, member) \ + ccan_container_off(typeof(*var), member) +#else +#define ccan_container_off_var(var, member) \ + ((const char *)&(var)->member - (const char *)(var)) +#endif + +#endif /* CCAN_CONTAINER_OF_H */ diff --git a/ccan/list/list.h b/ccan/list/list.h new file mode 100644 index 0000000..bf692a6 --- /dev/null +++ b/ccan/list/list.h @@ -0,0 +1,791 @@ +/* Licensed under BSD-MIT - see ccan/licenses/BSD-MIT file for details */ +#ifndef CCAN_LIST_H +#define CCAN_LIST_H +#include +#include "ccan/str/str.h" +#include "ccan/container_of/container_of.h" +#include "ccan/check_type/check_type.h" + +/** + * struct ccan_list_node - an entry in a doubly-linked list + * @next: next entry (self if empty) + * @prev: previous entry (self if empty) + * + * This is used as an entry in a linked list. + * Example: + * struct child { + * const char *name; + * // Linked list of all us children. + * struct ccan_list_node list; + * }; + */ +struct ccan_list_node +{ + struct ccan_list_node *next, *prev; +}; + +/** + * struct ccan_list_head - the head of a doubly-linked list + * @h: the ccan_list_head (containing next and prev pointers) + * + * This is used as the head of a linked list. + * Example: + * struct parent { + * const char *name; + * struct ccan_list_head children; + * unsigned int num_children; + * }; + */ +struct ccan_list_head +{ + struct ccan_list_node n; +}; + +#define CCAN_LIST_LOC __FILE__ ":" ccan_stringify(__LINE__) +#define ccan_list_debug(h, loc) ((void)loc, h) +#define ccan_list_debug_node(n, loc) ((void)loc, n) + +/** + * CCAN_LIST_HEAD_INIT - initializer for an empty ccan_list_head + * @name: the name of the list. + * + * Explicit initializer for an empty list. + * + * See also: + * CCAN_LIST_HEAD, ccan_list_head_init() + * + * Example: + * static struct ccan_list_head my_list = CCAN_LIST_HEAD_INIT(my_list); + */ +#define CCAN_LIST_HEAD_INIT(name) { { &(name).n, &(name).n } } + +/** + * CCAN_LIST_HEAD - define and initialize an empty ccan_list_head + * @name: the name of the list. + * + * The CCAN_LIST_HEAD macro defines a ccan_list_head and initializes it to an empty + * list. It can be prepended by "static" to define a static ccan_list_head. + * + * See also: + * CCAN_LIST_HEAD_INIT, ccan_list_head_init() + * + * Example: + * static CCAN_LIST_HEAD(my_global_list); + */ +#define CCAN_LIST_HEAD(name) \ + struct ccan_list_head name = CCAN_LIST_HEAD_INIT(name) + +/** + * ccan_list_head_init - initialize a ccan_list_head + * @h: the ccan_list_head to set to the empty list + * + * Example: + * ... + * struct parent *parent = malloc(sizeof(*parent)); + * + * ccan_list_head_init(&parent->children); + * parent->num_children = 0; + */ +static inline void ccan_list_head_init(struct ccan_list_head *h) +{ + h->n.next = h->n.prev = &h->n; +} + +/** + * ccan_list_node_init - initialize a ccan_list_node + * @n: the ccan_list_node to link to itself. + * + * You don't need to use this normally! But it lets you ccan_list_del(@n) + * safely. + */ +static inline void ccan_list_node_init(struct ccan_list_node *n) +{ + n->next = n->prev = n; +} + +/** + * ccan_list_add_after - add an entry after an existing node in a linked list + * @h: the ccan_list_head to add the node to (for debugging) + * @p: the existing ccan_list_node to add the node after + * @n: the new ccan_list_node to add to the list. + * + * The existing ccan_list_node must already be a member of the list. + * The new ccan_list_node does not need to be initialized; it will be overwritten. + * + * Example: + * struct child c1, c2, c3; + * CCAN_LIST_HEAD(h); + * + * ccan_list_add_tail(&h, &c1.list); + * ccan_list_add_tail(&h, &c3.list); + * ccan_list_add_after(&h, &c1.list, &c2.list); + */ +#define ccan_list_add_after(h, p, n) ccan_list_add_after_(h, p, n, CCAN_LIST_LOC) +static inline void ccan_list_add_after_(struct ccan_list_head *h, + struct ccan_list_node *p, + struct ccan_list_node *n, + const char *abortstr) +{ + n->next = p->next; + n->prev = p; + p->next->prev = n; + p->next = n; + (void)ccan_list_debug(h, abortstr); +} + +/** + * ccan_list_add - add an entry at the start of a linked list. + * @h: the ccan_list_head to add the node to + * @n: the ccan_list_node to add to the list. + * + * The ccan_list_node does not need to be initialized; it will be overwritten. + * Example: + * struct child *child = malloc(sizeof(*child)); + * + * child->name = "marvin"; + * ccan_list_add(&parent->children, &child->list); + * parent->num_children++; + */ +#define ccan_list_add(h, n) ccan_list_add_(h, n, CCAN_LIST_LOC) +static inline void ccan_list_add_(struct ccan_list_head *h, + struct ccan_list_node *n, + const char *abortstr) +{ + ccan_list_add_after_(h, &h->n, n, abortstr); +} + +/** + * ccan_list_add_before - add an entry before an existing node in a linked list + * @h: the ccan_list_head to add the node to (for debugging) + * @p: the existing ccan_list_node to add the node before + * @n: the new ccan_list_node to add to the list. + * + * The existing ccan_list_node must already be a member of the list. + * The new ccan_list_node does not need to be initialized; it will be overwritten. + * + * Example: + * ccan_list_head_init(&h); + * ccan_list_add_tail(&h, &c1.list); + * ccan_list_add_tail(&h, &c3.list); + * ccan_list_add_before(&h, &c3.list, &c2.list); + */ +#define ccan_list_add_before(h, p, n) ccan_list_add_before_(h, p, n, CCAN_LIST_LOC) +static inline void ccan_list_add_before_(struct ccan_list_head *h, + struct ccan_list_node *p, + struct ccan_list_node *n, + const char *abortstr) +{ + n->next = p; + n->prev = p->prev; + p->prev->next = n; + p->prev = n; + (void)ccan_list_debug(h, abortstr); +} + +/** + * ccan_list_add_tail - add an entry at the end of a linked list. + * @h: the ccan_list_head to add the node to + * @n: the ccan_list_node to add to the list. + * + * The ccan_list_node does not need to be initialized; it will be overwritten. + * Example: + * ccan_list_add_tail(&parent->children, &child->list); + * parent->num_children++; + */ +#define ccan_list_add_tail(h, n) ccan_list_add_tail_(h, n, CCAN_LIST_LOC) +static inline void ccan_list_add_tail_(struct ccan_list_head *h, + struct ccan_list_node *n, + const char *abortstr) +{ + ccan_list_add_before_(h, &h->n, n, abortstr); +} + +/** + * ccan_list_empty - is a list empty? + * @h: the ccan_list_head + * + * If the list is empty, returns true. + * + * Example: + * assert(ccan_list_empty(&parent->children) == (parent->num_children == 0)); + */ +#define ccan_list_empty(h) ccan_list_empty_(h, CCAN_LIST_LOC) +static inline int ccan_list_empty_(const struct ccan_list_head *h, const char* abortstr) +{ + (void)ccan_list_debug(h, abortstr); + return h->n.next == &h->n; +} + +/** + * ccan_list_empty_nodebug - is a list empty (and don't perform debug checks)? + * @h: the ccan_list_head + * + * If the list is empty, returns true. + * This differs from list_empty() in that if CCAN_LIST_DEBUG is set it + * will NOT perform debug checks. Only use this function if you REALLY + * know what you're doing. + * + * Example: + * assert(ccan_list_empty_nodebug(&parent->children) == (parent->num_children == 0)); + */ +#ifndef CCAN_LIST_DEBUG +#define ccan_list_empty_nodebug(h) ccan_list_empty(h) +#else +static inline int ccan_list_empty_nodebug(const struct ccan_list_head *h) +{ + return h->n.next == &h->n; +} +#endif + +/** + * ccan_list_empty_nocheck - is a list empty? + * @h: the ccan_list_head + * + * If the list is empty, returns true. This doesn't perform any + * debug check for list consistency, so it can be called without + * locks, racing with the list being modified. This is ok for + * checks where an incorrect result is not an issue (optimized + * bail out path for example). + */ +static inline bool ccan_list_empty_nocheck(const struct ccan_list_head *h) +{ + return h->n.next == &h->n; +} + +/** + * ccan_list_del - delete an entry from an (unknown) linked list. + * @n: the ccan_list_node to delete from the list. + * + * Note that this leaves @n in an undefined state; it can be added to + * another list, but not deleted again. + * + * See also: + * ccan_list_del_from(), ccan_list_del_init() + * + * Example: + * ccan_list_del(&child->list); + * parent->num_children--; + */ +#define ccan_list_del(n) ccan_list_del_(n, CCAN_LIST_LOC) +static inline void ccan_list_del_(struct ccan_list_node *n, const char* abortstr) +{ + (void)ccan_list_debug_node(n, abortstr); + n->next->prev = n->prev; + n->prev->next = n->next; +#ifdef CCAN_LIST_DEBUG + /* Catch use-after-del. */ + n->next = n->prev = NULL; +#endif +} + +/** + * ccan_list_del_init - delete a node, and reset it so it can be deleted again. + * @n: the ccan_list_node to be deleted. + * + * ccan_list_del(@n) or ccan_list_del_init() again after this will be safe, + * which can be useful in some cases. + * + * See also: + * ccan_list_del_from(), ccan_list_del() + * + * Example: + * ccan_list_del_init(&child->list); + * parent->num_children--; + */ +#define ccan_list_del_init(n) ccan_list_del_init_(n, CCAN_LIST_LOC) +static inline void ccan_list_del_init_(struct ccan_list_node *n, const char *abortstr) +{ + ccan_list_del_(n, abortstr); + ccan_list_node_init(n); +} + +/** + * ccan_list_del_from - delete an entry from a known linked list. + * @h: the ccan_list_head the node is in. + * @n: the ccan_list_node to delete from the list. + * + * This explicitly indicates which list a node is expected to be in, + * which is better documentation and can catch more bugs. + * + * See also: ccan_list_del() + * + * Example: + * ccan_list_del_from(&parent->children, &child->list); + * parent->num_children--; + */ +static inline void ccan_list_del_from(struct ccan_list_head *h, struct ccan_list_node *n) +{ +#ifdef CCAN_LIST_DEBUG + { + /* Thorough check: make sure it was in list! */ + struct ccan_list_node *i; + for (i = h->n.next; i != n; i = i->next) + assert(i != &h->n); + } +#endif /* CCAN_LIST_DEBUG */ + + /* Quick test that catches a surprising number of bugs. */ + assert(!ccan_list_empty(h)); + ccan_list_del(n); +} + +/** + * ccan_list_swap - swap out an entry from an (unknown) linked list for a new one. + * @o: the ccan_list_node to replace from the list. + * @n: the ccan_list_node to insert in place of the old one. + * + * Note that this leaves @o in an undefined state; it can be added to + * another list, but not deleted/swapped again. + * + * See also: + * ccan_list_del() + * + * Example: + * struct child x1, x2; + * CCAN_LIST_HEAD(xh); + * + * ccan_list_add(&xh, &x1.list); + * ccan_list_swap(&x1.list, &x2.list); + */ +#define ccan_list_swap(o, n) ccan_list_swap_(o, n, CCAN_LIST_LOC) +static inline void ccan_list_swap_(struct ccan_list_node *o, + struct ccan_list_node *n, + const char* abortstr) +{ + (void)ccan_list_debug_node(o, abortstr); + *n = *o; + n->next->prev = n; + n->prev->next = n; +#ifdef CCAN_LIST_DEBUG + /* Catch use-after-del. */ + o->next = o->prev = NULL; +#endif +} + +/** + * ccan_list_entry - convert a ccan_list_node back into the structure containing it. + * @n: the ccan_list_node + * @type: the type of the entry + * @member: the ccan_list_node member of the type + * + * Example: + * // First list entry is children.next; convert back to child. + * child = ccan_list_entry(parent->children.n.next, struct child, list); + * + * See Also: + * ccan_list_top(), ccan_list_for_each() + */ +#define ccan_list_entry(n, type, member) ccan_container_of(n, type, member) + +/** + * ccan_list_top - get the first entry in a list + * @h: the ccan_list_head + * @type: the type of the entry + * @member: the ccan_list_node member of the type + * + * If the list is empty, returns NULL. + * + * Example: + * struct child *first; + * first = ccan_list_top(&parent->children, struct child, list); + * if (!first) + * printf("Empty list!\n"); + */ +#define ccan_list_top(h, type, member) \ + ((type *)ccan_list_top_((h), ccan_list_off_(type, member))) + +static inline const void *ccan_list_top_(const struct ccan_list_head *h, size_t off) +{ + if (ccan_list_empty(h)) + return NULL; + return (const char *)h->n.next - off; +} + +/** + * ccan_list_pop - remove the first entry in a list + * @h: the ccan_list_head + * @type: the type of the entry + * @member: the ccan_list_node member of the type + * + * If the list is empty, returns NULL. + * + * Example: + * struct child *one; + * one = ccan_list_pop(&parent->children, struct child, list); + * if (!one) + * printf("Empty list!\n"); + */ +#define ccan_list_pop(h, type, member) \ + ((type *)ccan_list_pop_((h), ccan_list_off_(type, member))) + +static inline const void *ccan_list_pop_(const struct ccan_list_head *h, size_t off) +{ + struct ccan_list_node *n; + + if (ccan_list_empty(h)) + return NULL; + n = h->n.next; + ccan_list_del(n); + return (const char *)n - off; +} + +/** + * ccan_list_tail - get the last entry in a list + * @h: the ccan_list_head + * @type: the type of the entry + * @member: the ccan_list_node member of the type + * + * If the list is empty, returns NULL. + * + * Example: + * struct child *last; + * last = ccan_list_tail(&parent->children, struct child, list); + * if (!last) + * printf("Empty list!\n"); + */ +#define ccan_list_tail(h, type, member) \ + ((type *)ccan_list_tail_((h), ccan_list_off_(type, member))) + +static inline const void *ccan_list_tail_(const struct ccan_list_head *h, size_t off) +{ + if (ccan_list_empty(h)) + return NULL; + return (const char *)h->n.prev - off; +} + +/** + * ccan_list_for_each - iterate through a list. + * @h: the ccan_list_head (warning: evaluated multiple times!) + * @i: the structure containing the ccan_list_node + * @member: the ccan_list_node member of the structure + * + * This is a convenient wrapper to iterate @i over the entire list. It's + * a for loop, so you can break and continue as normal. + * + * Example: + * ccan_list_for_each(&parent->children, child, list) + * printf("Name: %s\n", child->name); + */ +#define ccan_list_for_each(h, i, member) \ + ccan_list_for_each_off(h, i, ccan_list_off_var_(i, member)) + +/** + * ccan_list_for_each_rev - iterate through a list backwards. + * @h: the ccan_list_head + * @i: the structure containing the ccan_list_node + * @member: the ccan_list_node member of the structure + * + * This is a convenient wrapper to iterate @i over the entire list. It's + * a for loop, so you can break and continue as normal. + * + * Example: + * ccan_list_for_each_rev(&parent->children, child, list) + * printf("Name: %s\n", child->name); + */ +#define ccan_list_for_each_rev(h, i, member) \ + ccan_list_for_each_rev_off(h, i, ccan_list_off_var_(i, member)) + +/** + * ccan_list_for_each_rev_safe - iterate through a list backwards, + * maybe during deletion + * @h: the ccan_list_head + * @i: the structure containing the ccan_list_node + * @nxt: the structure containing the ccan_list_node + * @member: the ccan_list_node member of the structure + * + * This is a convenient wrapper to iterate @i over the entire list backwards. + * It's a for loop, so you can break and continue as normal. The extra + * variable * @nxt is used to hold the next element, so you can delete @i + * from the list. + * + * Example: + * struct child *next; + * ccan_list_for_each_rev_safe(&parent->children, child, next, list) { + * printf("Name: %s\n", child->name); + * } + */ +#define ccan_list_for_each_rev_safe(h, i, nxt, member) \ + ccan_list_for_each_rev_safe_off(h, i, nxt, ccan_list_off_var_(i, member)) + +/** + * ccan_list_for_each_safe - iterate through a list, maybe during deletion + * @h: the ccan_list_head + * @i: the structure containing the ccan_list_node + * @nxt: the structure containing the ccan_list_node + * @member: the ccan_list_node member of the structure + * + * This is a convenient wrapper to iterate @i over the entire list. It's + * a for loop, so you can break and continue as normal. The extra variable + * @nxt is used to hold the next element, so you can delete @i from the list. + * + * Example: + * ccan_list_for_each_safe(&parent->children, child, next, list) { + * ccan_list_del(&child->list); + * parent->num_children--; + * } + */ +#define ccan_list_for_each_safe(h, i, nxt, member) \ + ccan_list_for_each_safe_off(h, i, nxt, ccan_list_off_var_(i, member)) + +/** + * ccan_list_next - get the next entry in a list + * @h: the ccan_list_head + * @i: a pointer to an entry in the list. + * @member: the ccan_list_node member of the structure + * + * If @i was the last entry in the list, returns NULL. + * + * Example: + * struct child *second; + * second = ccan_list_next(&parent->children, first, list); + * if (!second) + * printf("No second child!\n"); + */ +#define ccan_list_next(h, i, member) \ + ((ccan_list_typeof(i))ccan_list_entry_or_null(ccan_list_debug(h, \ + __FILE__ ":" ccan_stringify(__LINE__)), \ + (i)->member.next, \ + ccan_list_off_var_((i), member))) + +/** + * ccan_list_prev - get the previous entry in a list + * @h: the ccan_list_head + * @i: a pointer to an entry in the list. + * @member: the ccan_list_node member of the structure + * + * If @i was the first entry in the list, returns NULL. + * + * Example: + * first = ccan_list_prev(&parent->children, second, list); + * if (!first) + * printf("Can't go back to first child?!\n"); + */ +#define ccan_list_prev(h, i, member) \ + ((ccan_list_typeof(i))ccan_list_entry_or_null(ccan_list_debug(h, \ + __FILE__ ":" ccan_stringify(__LINE__)), \ + (i)->member.prev, \ + ccan_list_off_var_((i), member))) + +/** + * ccan_list_append_list - empty one list onto the end of another. + * @to: the list to append into + * @from: the list to empty. + * + * This takes the entire contents of @from and moves it to the end of + * @to. After this @from will be empty. + * + * Example: + * struct ccan_list_head adopter; + * + * ccan_list_append_list(&adopter, &parent->children); + * assert(ccan_list_empty(&parent->children)); + * parent->num_children = 0; + */ +#define ccan_list_append_list(t, f) ccan_list_append_list_(t, f, \ + __FILE__ ":" ccan_stringify(__LINE__)) +static inline void ccan_list_append_list_(struct ccan_list_head *to, + struct ccan_list_head *from, + const char *abortstr) +{ + struct ccan_list_node *from_tail = ccan_list_debug(from, abortstr)->n.prev; + struct ccan_list_node *to_tail = ccan_list_debug(to, abortstr)->n.prev; + + /* Sew in head and entire list. */ + to->n.prev = from_tail; + from_tail->next = &to->n; + to_tail->next = &from->n; + from->n.prev = to_tail; + + /* Now remove head. */ + ccan_list_del(&from->n); + ccan_list_head_init(from); +} + +/** + * ccan_list_prepend_list - empty one list into the start of another. + * @to: the list to prepend into + * @from: the list to empty. + * + * This takes the entire contents of @from and moves it to the start + * of @to. After this @from will be empty. + * + * Example: + * ccan_list_prepend_list(&adopter, &parent->children); + * assert(ccan_list_empty(&parent->children)); + * parent->num_children = 0; + */ +#define ccan_list_prepend_list(t, f) ccan_list_prepend_list_(t, f, CCAN_LIST_LOC) +static inline void ccan_list_prepend_list_(struct ccan_list_head *to, + struct ccan_list_head *from, + const char *abortstr) +{ + struct ccan_list_node *from_tail = ccan_list_debug(from, abortstr)->n.prev; + struct ccan_list_node *to_head = ccan_list_debug(to, abortstr)->n.next; + + /* Sew in head and entire list. */ + to->n.next = &from->n; + from->n.prev = &to->n; + to_head->prev = from_tail; + from_tail->next = to_head; + + /* Now remove head. */ + ccan_list_del(&from->n); + ccan_list_head_init(from); +} + +/* internal macros, do not use directly */ +#define ccan_list_for_each_off_dir_(h, i, off, dir) \ + for (i = 0, \ + i = ccan_list_node_to_off_(ccan_list_debug(h, CCAN_LIST_LOC)->n.dir, \ + (off)); \ + ccan_list_node_from_off_((void *)i, (off)) != &(h)->n; \ + i = ccan_list_node_to_off_(ccan_list_node_from_off_((void *)i, (off))->dir, \ + (off))) + +#define ccan_list_for_each_safe_off_dir_(h, i, nxt, off, dir) \ + for (i = 0, \ + i = ccan_list_node_to_off_(ccan_list_debug(h, CCAN_LIST_LOC)->n.dir, \ + (off)), \ + nxt = ccan_list_node_to_off_(ccan_list_node_from_off_(i, (off))->dir, \ + (off)); \ + ccan_list_node_from_off_(i, (off)) != &(h)->n; \ + i = nxt, \ + nxt = ccan_list_node_to_off_(ccan_list_node_from_off_(i, (off))->dir, \ + (off))) + +/** + * ccan_list_for_each_off - iterate through a list of memory regions. + * @h: the ccan_list_head + * @i: the pointer to a memory region which contains list node data. + * @off: offset(relative to @i) at which list node data resides. + * + * This is a low-level wrapper to iterate @i over the entire list, used to + * implement all other, more high-level, for-each constructs. It's a for loop, + * so you can break and continue as normal. + * + * WARNING! Being the low-level macro that it is, this wrapper doesn't know + * nor care about the type of @i. The only assumption made is that @i points + * to a chunk of memory that at some @offset, relative to @i, contains a + * properly filled `struct ccan_list_node' which in turn contains pointers to + * memory chunks and it's turtles all the way down. With all that in mind + * remember that given the wrong pointer/offset couple this macro will + * happily churn all you memory until SEGFAULT stops it, in other words + * caveat emptor. + * + * It is worth mentioning that one of legitimate use-cases for that wrapper + * is operation on opaque types with known offset for `struct ccan_list_node' + * member(preferably 0), because it allows you not to disclose the type of + * @i. + * + * Example: + * ccan_list_for_each_off(&parent->children, child, + * offsetof(struct child, list)) + * printf("Name: %s\n", child->name); + */ +#define ccan_list_for_each_off(h, i, off) \ + ccan_list_for_each_off_dir_((h),(i),(off),next) + +/** + * ccan_list_for_each_rev_off - iterate through a list of memory regions backwards + * @h: the ccan_list_head + * @i: the pointer to a memory region which contains list node data. + * @off: offset(relative to @i) at which list node data resides. + * + * See ccan_list_for_each_off for details + */ +#define ccan_list_for_each_rev_off(h, i, off) \ + ccan_list_for_each_off_dir_((h),(i),(off),prev) + +/** + * ccan_list_for_each_safe_off - iterate through a list of memory regions, maybe + * during deletion + * @h: the ccan_list_head + * @i: the pointer to a memory region which contains list node data. + * @nxt: the structure containing the ccan_list_node + * @off: offset(relative to @i) at which list node data resides. + * + * For details see `ccan_list_for_each_off' and `ccan_list_for_each_safe' + * descriptions. + * + * Example: + * ccan_list_for_each_safe_off(&parent->children, child, + * next, offsetof(struct child, list)) + * printf("Name: %s\n", child->name); + */ +#define ccan_list_for_each_safe_off(h, i, nxt, off) \ + ccan_list_for_each_safe_off_dir_((h),(i),(nxt),(off),next) + +/** + * ccan_list_for_each_rev_safe_off - iterate backwards through a list of + * memory regions, maybe during deletion + * @h: the ccan_list_head + * @i: the pointer to a memory region which contains list node data. + * @nxt: the structure containing the ccan_list_node + * @off: offset(relative to @i) at which list node data resides. + * + * For details see `ccan_list_for_each_rev_off' and `ccan_list_for_each_rev_safe' + * descriptions. + * + * Example: + * ccan_list_for_each_rev_safe_off(&parent->children, child, + * next, offsetof(struct child, list)) + * printf("Name: %s\n", child->name); + */ +#define ccan_list_for_each_rev_safe_off(h, i, nxt, off) \ + ccan_list_for_each_safe_off_dir_((h),(i),(nxt),(off),prev) + +/* Other -off variants. */ +#define ccan_list_entry_off(n, type, off) \ + ((type *)ccan_list_node_from_off_((n), (off))) + +#define ccan_list_head_off(h, type, off) \ + ((type *)ccan_list_head_off((h), (off))) + +#define ccan_list_tail_off(h, type, off) \ + ((type *)ccan_list_tail_((h), (off))) + +#define ccan_list_add_off(h, n, off) \ + ccan_list_add((h), ccan_list_node_from_off_((n), (off))) + +#define ccan_list_del_off(n, off) \ + ccan_list_del(ccan_list_node_from_off_((n), (off))) + +#define ccan_list_del_from_off(h, n, off) \ + ccan_list_del_from(h, ccan_list_node_from_off_((n), (off))) + +/* Offset helper functions so we only single-evaluate. */ +static inline void *ccan_list_node_to_off_(struct ccan_list_node *node, size_t off) +{ + return (void *)((char *)node - off); +} +static inline struct ccan_list_node *ccan_list_node_from_off_(void *ptr, size_t off) +{ + return (struct ccan_list_node *)((char *)ptr + off); +} + +/* Get the offset of the member, but make sure it's a ccan_list_node. */ +#define ccan_list_off_(type, member) \ + (ccan_container_off(type, member) + \ + ccan_check_type(((type *)0)->member, struct ccan_list_node)) + +#define ccan_list_off_var_(var, member) \ + (ccan_container_off_var(var, member) + \ + ccan_check_type(var->member, struct ccan_list_node)) + +#if defined(HAVE_TYPEOF) && HAVE_TYPEOF +#define ccan_list_typeof(var) typeof(var) +#else +#define ccan_list_typeof(var) void * +#endif + +/* Returns member, or NULL if at end of list. */ +static inline void *ccan_list_entry_or_null(const struct ccan_list_head *h, + const struct ccan_list_node *n, + size_t off) +{ + if (n == &h->n) + return NULL; + return (char *)n - off; +} + +#endif /* CCAN_LIST_H */ diff --git a/ccan/str/str.h b/ccan/str/str.h new file mode 100644 index 0000000..6d4cf62 --- /dev/null +++ b/ccan/str/str.h @@ -0,0 +1,17 @@ +/* CC0 (Public domain) - see ccan/licenses/CC0 file for details */ +#ifndef CCAN_STR_H +#define CCAN_STR_H +/** + * ccan_stringify - Turn expression into a string literal + * @expr: any C expression + * + * Example: + * #define PRINT_COND_IF_FALSE(cond) \ + * ((cond) || printf("%s is false!", ccan_stringify(cond))) + */ +#define stringify(expr) ccan_stringify_1(expr) +#define ccan_stringify(expr) ccan_stringify_1(expr) +/* Double-indirection required to stringify expansions */ +#define ccan_stringify_1(expr) #expr + +#endif /* CCAN_STR_H */ diff --git a/darray.h b/darray.h new file mode 100644 index 0000000..baaf350 --- /dev/null +++ b/darray.h @@ -0,0 +1,207 @@ +#ifndef RUBY_DARRAY_H +#define RUBY_DARRAY_H + +#include +#include +#include + +// Type for a dynamic array. Use to declare a dynamic array. +// It is a pointer so it fits in st_table nicely. Designed +// to be fairly type-safe. +// +// NULL is a valid empty dynamic array. +// +// Example: +// rb_darray(char) char_array = NULL; +// rb_darray_append(&char_array, 'e'); +// printf("pushed %c\n", *rb_darray_ref(char_array, 0)); +// rb_darray_free(char_array); +// +#define rb_darray(T) struct { rb_darray_meta_t meta; T data[]; } * + +// Copy an element out of the array. Warning: not bounds checked. +// +// T rb_darray_get(rb_darray(T) ary, size_t idx); +// +#define rb_darray_get(ary, idx) ((ary)->data[(idx)]) + +// Assign to an element. Warning: not bounds checked. +// +// void rb_darray_set(rb_darray(T) ary, size_t idx, T element); +// +#define rb_darray_set(ary, idx, element) ((ary)->data[(idx)] = (element)) + +// Get a pointer to an element. Warning: not bounds checked. +// +// T *rb_darray_ref(rb_darray(T) ary, size_t idx); +// +#define rb_darray_ref(ary, idx) (&((ary)->data[(idx)])) + +/* Copy a new element into the array. ptr_to_ary is evaluated multiple times. + * + * void rb_darray_append(rb_darray(T) *ptr_to_ary, T element); + */ +#define rb_darray_append(ptr_to_ary, element) do { \ + rb_darray_ensure_space((ptr_to_ary), \ + sizeof(**(ptr_to_ary)), \ + sizeof((*(ptr_to_ary))->data[0])); \ + rb_darray_set(*(ptr_to_ary), \ + (*(ptr_to_ary))->meta.size, \ + (element)); \ + (*(ptr_to_ary))->meta.size++; \ +} while (0) + +#define rb_darray_insert(ptr_to_ary, idx, element) do { \ + rb_darray_ensure_space((ptr_to_ary), \ + sizeof(**(ptr_to_ary)), \ + sizeof((*(ptr_to_ary))->data[0])); \ + MEMMOVE( \ + rb_darray_ref(*(ptr_to_ary), idx + 1), \ + rb_darray_ref(*(ptr_to_ary), idx), \ + sizeof((*(ptr_to_ary))->data[0]), \ + rb_darray_size(*(ptr_to_ary)) - idx); \ + rb_darray_set(*(ptr_to_ary), idx, element); \ + (*(ptr_to_ary))->meta.size++; \ +} while (0) + +// Iterate over items of the array in a for loop +// +#define rb_darray_foreach(ary, idx_name, elem_ptr_var) \ + for (size_t idx_name = 0; idx_name < rb_darray_size(ary) && ((elem_ptr_var) = rb_darray_ref(ary, idx_name)); ++idx_name) + +// Iterate over valid indices in the array in a for loop +// +#define rb_darray_for(ary, idx_name) \ + for (size_t idx_name = 0; idx_name < rb_darray_size(ary); ++idx_name) + +/* Make a dynamic array of a certain size. All bytes backing the elements are set to zero. + * Return 1 on success and 0 on failure. + * + * Note that NULL is a valid empty dynamic array. + * + * void rb_darray_make(rb_darray(T) *ptr_to_ary, size_t size); + */ +#define rb_darray_make(ptr_to_ary, size) \ + rb_darray_make_impl((ptr_to_ary), size, sizeof(**(ptr_to_ary)), sizeof((*(ptr_to_ary))->data[0])) + +/* Resize the darray to a new capacity. The new capacity must be greater than + * or equal to the size of the darray. + * + * void rb_darray_resize_capa(rb_darray(T) *ptr_to_ary, size_t capa); + */ +#define rb_darray_resize_capa(ptr_to_ary, capa) \ + rb_darray_resize_capa_impl((ptr_to_ary), capa, sizeof(**(ptr_to_ary)), sizeof((*(ptr_to_ary))->data[0])) + +#define rb_darray_data_ptr(ary) ((ary)->data) + +typedef struct rb_darray_meta { + size_t size; + size_t capa; +} rb_darray_meta_t; + +/* Set the size of the array to zero without freeing the backing memory. + * Allows reusing the same array. */ +static inline void +rb_darray_clear(void *ary) +{ + rb_darray_meta_t *meta = ary; + if (meta) { + meta->size = 0; + } +} + +// Get the size of the dynamic array. +// +static inline size_t +rb_darray_size(const void *ary) +{ + const rb_darray_meta_t *meta = ary; + return meta ? meta->size : 0; +} + + +static inline void +rb_darray_pop(void *ary, size_t count) +{ + rb_darray_meta_t *meta = ary; + meta->size -= count; +} + +// Get the capacity of the dynamic array. +// +static inline size_t +rb_darray_capa(const void *ary) +{ + const rb_darray_meta_t *meta = ary; + return meta ? meta->capa : 0; +} + +/* Free the dynamic array. */ +static inline void +rb_darray_free(void *ary) +{ + xfree(ary); +} + +/* Internal function. Resizes the capacity of a darray. The new capacity must + * be greater than or equal to the size of the darray. */ +static inline void +rb_darray_resize_capa_impl(void *ptr_to_ary, size_t new_capa, size_t header_size, size_t element_size) +{ + rb_darray_meta_t **ptr_to_ptr_to_meta = ptr_to_ary; + rb_darray_meta_t *meta = *ptr_to_ptr_to_meta; + + rb_darray_meta_t *new_ary = xrealloc(meta, new_capa * element_size + header_size); + + if (meta == NULL) { + /* First allocation. Initialize size. On subsequence allocations + * realloc takes care of carrying over the size. */ + new_ary->size = 0; + } + + RUBY_ASSERT(new_ary->size <= new_capa); + + new_ary->capa = new_capa; + + // We don't have access to the type of the dynamic array in function context. + // Write out result with memcpy to avoid strict aliasing issue. + memcpy(ptr_to_ary, &new_ary, sizeof(new_ary)); +} + +// Internal function +// Ensure there is space for one more element. +// Note: header_size can be bigger than sizeof(rb_darray_meta_t) when T is __int128_t, for example. +static inline void +rb_darray_ensure_space(void *ptr_to_ary, size_t header_size, size_t element_size) +{ + rb_darray_meta_t **ptr_to_ptr_to_meta = ptr_to_ary; + rb_darray_meta_t *meta = *ptr_to_ptr_to_meta; + size_t current_capa = rb_darray_capa(meta); + if (rb_darray_size(meta) < current_capa) return; + + // Double the capacity + size_t new_capa = current_capa == 0 ? 1 : current_capa * 2; + + rb_darray_resize_capa_impl(ptr_to_ary, new_capa, header_size, element_size); +} + +static inline void +rb_darray_make_impl(void *ptr_to_ary, size_t array_size, size_t header_size, size_t element_size) +{ + rb_darray_meta_t **ptr_to_ptr_to_meta = ptr_to_ary; + if (array_size == 0) { + *ptr_to_ptr_to_meta = NULL; + return; + } + + rb_darray_meta_t *meta = xcalloc(array_size * element_size + header_size, 1); + + meta->size = array_size; + meta->capa = array_size; + + // We don't have access to the type of the dynamic array in function context. + // Write out result with memcpy to avoid strict aliasing issue. + memcpy(ptr_to_ary, &meta, sizeof(meta)); +} + +#endif /* RUBY_DARRAY_H */ diff --git a/gc/extconf_base.rb b/gc/extconf_base.rb new file mode 100644 index 0000000..0cbb09d --- /dev/null +++ b/gc/extconf_base.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "mkmf" + +srcdir = File.join(__dir__, "..") +$INCFLAGS << " -I#{srcdir}" + +$CPPFLAGS << " -DBUILDING_MODULAR_GC" + +append_cflags("-fPIC") + +def create_gc_makefile(name) + create_makefile("librubygc.#{name}") +end diff --git a/gc/gc.h b/gc/gc.h new file mode 100644 index 0000000..1379878 --- /dev/null +++ b/gc/gc.h @@ -0,0 +1,230 @@ +#ifndef GC_GC_H +#define GC_GC_H +/** + * @author Ruby developers + * @copyright This file is a part of the programming language Ruby. + * Permission is hereby granted, to either redistribute and/or + * modify this file, provided that the conditions mentioned in the + * file COPYING are met. Consult the file for details. + * @brief Private header for the default GC and other GC implementations + * first introduced for [Feature #20470]. + */ +#include "ruby/ruby.h" + +#if USE_MODULAR_GC +#include "ruby/thread_native.h" + +struct rb_gc_vm_context { + rb_nativethread_lock_t lock; + + struct rb_execution_context_struct *ec; +}; + +typedef int (*vm_table_foreach_callback_func)(VALUE value, void *data); +typedef int (*vm_table_update_callback_func)(VALUE *value, void *data); + + +enum rb_gc_vm_weak_tables { + RB_GC_VM_CI_TABLE, + RB_GC_VM_OVERLOADED_CME_TABLE, + RB_GC_VM_GLOBAL_SYMBOLS_TABLE, + RB_GC_VM_GENERIC_IV_TABLE, + RB_GC_VM_FROZEN_STRINGS_TABLE, + RB_GC_VM_WEAK_TABLE_COUNT +}; +#endif + +RUBY_SYMBOL_EXPORT_BEGIN +unsigned int rb_gc_vm_lock(void); +void rb_gc_vm_unlock(unsigned int lev); +unsigned int rb_gc_cr_lock(void); +void rb_gc_cr_unlock(unsigned int lev); +unsigned int rb_gc_vm_lock_no_barrier(void); +void rb_gc_vm_unlock_no_barrier(unsigned int lev); +void rb_gc_vm_barrier(void); +size_t rb_gc_obj_optimal_size(VALUE obj); +void rb_gc_mark_children(void *objspace, VALUE obj); +void rb_gc_update_object_references(void *objspace, VALUE obj); +void rb_gc_update_vm_references(void *objspace); +void rb_gc_event_hook(VALUE obj, rb_event_flag_t event); +void *rb_gc_get_objspace(void); +size_t rb_size_mul_or_raise(size_t x, size_t y, VALUE exc); +void rb_gc_run_obj_finalizer(VALUE objid, long count, VALUE (*callback)(long i, void *data), void *data); +void rb_gc_set_pending_interrupt(void); +void rb_gc_unset_pending_interrupt(void); +void rb_gc_obj_free_vm_weak_references(VALUE obj); +bool rb_gc_obj_free(void *objspace, VALUE obj); +void rb_gc_save_machine_context(void); +void rb_gc_mark_roots(void *objspace, const char **categoryp); +void rb_gc_ractor_newobj_cache_foreach(void (*func)(void *cache, void *data), void *data); +bool rb_gc_multi_ractor_p(void); +void rb_objspace_reachable_objects_from_root(void (func)(const char *category, VALUE, void *), void *passing_data); +void rb_objspace_reachable_objects_from(VALUE obj, void (func)(VALUE, void *), void *data); +void rb_obj_info_dump(VALUE obj); +const char *rb_obj_info(VALUE obj); +bool rb_gc_shutdown_call_finalizer_p(VALUE obj); +uint32_t rb_gc_get_shape(VALUE obj); +void rb_gc_set_shape(VALUE obj, uint32_t shape_id); +uint32_t rb_gc_rebuild_shape(VALUE obj, size_t heap_id); +size_t rb_obj_memsize_of(VALUE obj); +void rb_gc_prepare_heap_process_object(VALUE obj); +bool ruby_free_at_exit_p(void); + +#if USE_MODULAR_GC +bool rb_gc_event_hook_required_p(rb_event_flag_t event); +void *rb_gc_get_ractor_newobj_cache(void); +void rb_gc_initialize_vm_context(struct rb_gc_vm_context *context); +void rb_gc_worker_thread_set_vm_context(struct rb_gc_vm_context *context); +void rb_gc_worker_thread_unset_vm_context(struct rb_gc_vm_context *context); +void rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback, vm_table_update_callback_func update_callback, void *data, enum rb_gc_vm_weak_tables table); +#endif +RUBY_SYMBOL_EXPORT_END + +void rb_ractor_finish_marking(void); + +// -------------------Private section begin------------------------ +// Functions in this section are private to the default GC and gc.c + +#ifdef BUILDING_MODULAR_GC +RBIMPL_WARNING_PUSH() +RBIMPL_WARNING_IGNORED(-Wunused-function) +#endif + +/* RGENGC_CHECK_MODE + * 0: disable all assertions + * 1: enable assertions (to debug RGenGC) + * 2: enable internal consistency check at each GC (for debugging) + * 3: enable internal consistency check at each GC steps (for debugging) + * 4: enable liveness check + * 5: show all references + */ +#ifndef RGENGC_CHECK_MODE +# define RGENGC_CHECK_MODE 0 +#endif + +#ifndef GC_ASSERT +# define GC_ASSERT(expr) RUBY_ASSERT_MESG_WHEN(RGENGC_CHECK_MODE > 0, expr, #expr) +#endif + +static int +hash_foreach_replace_value(st_data_t key, st_data_t value, st_data_t argp, int error) +{ + if (rb_gc_location((VALUE)value) != (VALUE)value) { + return ST_REPLACE; + } + return ST_CONTINUE; +} + +static int +hash_replace_ref_value(st_data_t *key, st_data_t *value, st_data_t argp, int existing) +{ + *value = rb_gc_location((VALUE)*value); + + return ST_CONTINUE; +} + +static void +gc_ref_update_table_values_only(st_table *tbl) +{ + if (!tbl || tbl->num_entries == 0) return; + + if (st_foreach_with_replace(tbl, hash_foreach_replace_value, hash_replace_ref_value, 0)) { + rb_raise(rb_eRuntimeError, "hash modified during iteration"); + } +} + +static int +gc_mark_tbl_no_pin_i(st_data_t key, st_data_t value, st_data_t data) +{ + rb_gc_mark_movable((VALUE)value); + + return ST_CONTINUE; +} + +static int +hash_foreach_replace(st_data_t key, st_data_t value, st_data_t argp, int error) +{ + if (rb_gc_location((VALUE)key) != (VALUE)key) { + return ST_REPLACE; + } + + if (rb_gc_location((VALUE)value) != (VALUE)value) { + return ST_REPLACE; + } + + return ST_CONTINUE; +} + +static int +hash_replace_ref(st_data_t *key, st_data_t *value, st_data_t argp, int existing) +{ + if (rb_gc_location((VALUE)*key) != (VALUE)*key) { + *key = rb_gc_location((VALUE)*key); + } + + if (rb_gc_location((VALUE)*value) != (VALUE)*value) { + *value = rb_gc_location((VALUE)*value); + } + + return ST_CONTINUE; +} + +static void +gc_update_table_refs(st_table *tbl) +{ + if (!tbl || tbl->num_entries == 0) return; + + if (st_foreach_with_replace(tbl, hash_foreach_replace, hash_replace_ref, 0)) { + rb_raise(rb_eRuntimeError, "hash modified during iteration"); + } +} + +static inline size_t +xmalloc2_size(const size_t count, const size_t elsize) +{ + return rb_size_mul_or_raise(count, elsize, rb_eArgError); +} + +static VALUE +type_sym(size_t type) +{ + switch (type) { +#define COUNT_TYPE(t) case (t): return ID2SYM(rb_intern(#t)); break; + COUNT_TYPE(T_NONE); + COUNT_TYPE(T_OBJECT); + COUNT_TYPE(T_CLASS); + COUNT_TYPE(T_MODULE); + COUNT_TYPE(T_FLOAT); + COUNT_TYPE(T_STRING); + COUNT_TYPE(T_REGEXP); + COUNT_TYPE(T_ARRAY); + COUNT_TYPE(T_HASH); + COUNT_TYPE(T_STRUCT); + COUNT_TYPE(T_BIGNUM); + COUNT_TYPE(T_FILE); + COUNT_TYPE(T_DATA); + COUNT_TYPE(T_MATCH); + COUNT_TYPE(T_COMPLEX); + COUNT_TYPE(T_RATIONAL); + COUNT_TYPE(T_NIL); + COUNT_TYPE(T_TRUE); + COUNT_TYPE(T_FALSE); + COUNT_TYPE(T_SYMBOL); + COUNT_TYPE(T_FIXNUM); + COUNT_TYPE(T_IMEMO); + COUNT_TYPE(T_UNDEF); + COUNT_TYPE(T_NODE); + COUNT_TYPE(T_ICLASS); + COUNT_TYPE(T_ZOMBIE); + COUNT_TYPE(T_MOVED); +#undef COUNT_TYPE + default: return SIZET2NUM(type); break; + } +} + +#ifdef BUILDING_MODULAR_GC +RBIMPL_WARNING_POP() +#endif +// -------------------Private section end------------------------ + +#endif diff --git a/gc/gc_impl.h b/gc/gc_impl.h new file mode 100644 index 0000000..322ce2b --- /dev/null +++ b/gc/gc_impl.h @@ -0,0 +1,118 @@ +#ifndef GC_GC_IMPL_H +#define GC_GC_IMPL_H +/** + * @author Ruby developers + * @copyright This file is a part of the programming language Ruby. + * Permission is hereby granted, to either redistribute and/or + * modify this file, provided that the conditions mentioned in the + * file COPYING are met. Consult the file for details. + * @brief Header for GC implementations introduced in [Feature #20470]. + */ +#include "ruby/ruby.h" + +#ifdef BUILDING_MODULAR_GC +# define GC_IMPL_FN +#else +// `GC_IMPL_FN` is an implementation detail of `!USE_MODULAR_GC` builds +// to have the default GC in the same translation unit as gc.c for +// the sake of optimizer visibility. It expands to nothing unless +// you're the default GC. +// +// For the default GC, do not copy-paste this when implementing +// these functions. This takes advantage of internal linkage winning +// when appearing first. See C99 6.2.2p4. +# define GC_IMPL_FN static +#endif + +// Bootup +GC_IMPL_FN void *rb_gc_impl_objspace_alloc(void); +GC_IMPL_FN void rb_gc_impl_objspace_init(void *objspace_ptr); +GC_IMPL_FN void rb_gc_impl_objspace_free(void *objspace_ptr); +GC_IMPL_FN void *rb_gc_impl_ractor_cache_alloc(void *objspace_ptr, void *ractor); +GC_IMPL_FN void rb_gc_impl_ractor_cache_free(void *objspace_ptr, void *cache); +GC_IMPL_FN void rb_gc_impl_set_params(void *objspace_ptr); +GC_IMPL_FN void rb_gc_impl_init(void); +GC_IMPL_FN size_t *rb_gc_impl_heap_sizes(void *objspace_ptr); +// Shutdown +GC_IMPL_FN void rb_gc_impl_shutdown_free_objects(void *objspace_ptr); +// GC +GC_IMPL_FN void rb_gc_impl_start(void *objspace_ptr, bool full_mark, bool immediate_mark, bool immediate_sweep, bool compact); +GC_IMPL_FN bool rb_gc_impl_during_gc_p(void *objspace_ptr); +GC_IMPL_FN void rb_gc_impl_prepare_heap(void *objspace_ptr); +GC_IMPL_FN void rb_gc_impl_gc_enable(void *objspace_ptr); +GC_IMPL_FN void rb_gc_impl_gc_disable(void *objspace_ptr, bool finish_current_gc); +GC_IMPL_FN bool rb_gc_impl_gc_enabled_p(void *objspace_ptr); +GC_IMPL_FN void rb_gc_impl_stress_set(void *objspace_ptr, VALUE flag); +GC_IMPL_FN VALUE rb_gc_impl_stress_get(void *objspace_ptr); +GC_IMPL_FN VALUE rb_gc_impl_config_get(void *objspace_ptr); +GC_IMPL_FN void rb_gc_impl_config_set(void *objspace_ptr, VALUE hash); +// Object allocation +GC_IMPL_FN VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, bool wb_protected, size_t alloc_size); +GC_IMPL_FN size_t rb_gc_impl_obj_slot_size(VALUE obj); +GC_IMPL_FN size_t rb_gc_impl_heap_id_for_size(void *objspace_ptr, size_t size); +GC_IMPL_FN bool rb_gc_impl_size_allocatable_p(size_t size); +// Malloc +/* + * BEWARE: These functions may or may not run under GVL. + * + * You might want to make them thread-safe. + * Garbage collecting inside is possible if and only if you + * already have GVL. Also raising exceptions without one is a + * total disaster. + * + * When you absolutely cannot allocate the requested amount of + * memory just return NULL (with appropriate errno set). + * The caller side takes care of that situation. + */ +GC_IMPL_FN void *rb_gc_impl_malloc(void *objspace_ptr, size_t size); +GC_IMPL_FN void *rb_gc_impl_calloc(void *objspace_ptr, size_t size); +GC_IMPL_FN void *rb_gc_impl_realloc(void *objspace_ptr, void *ptr, size_t new_size, size_t old_size); +GC_IMPL_FN void rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size); +GC_IMPL_FN void rb_gc_impl_adjust_memory_usage(void *objspace_ptr, ssize_t diff); +// Marking +GC_IMPL_FN void rb_gc_impl_mark(void *objspace_ptr, VALUE obj); +GC_IMPL_FN void rb_gc_impl_mark_and_move(void *objspace_ptr, VALUE *ptr); +GC_IMPL_FN void rb_gc_impl_mark_and_pin(void *objspace_ptr, VALUE obj); +GC_IMPL_FN void rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj); +GC_IMPL_FN void rb_gc_impl_mark_weak(void *objspace_ptr, VALUE *ptr); +GC_IMPL_FN void rb_gc_impl_remove_weak(void *objspace_ptr, VALUE parent_obj, VALUE *ptr); +// Compaction +GC_IMPL_FN bool rb_gc_impl_object_moved_p(void *objspace_ptr, VALUE obj); +GC_IMPL_FN VALUE rb_gc_impl_location(void *objspace_ptr, VALUE value); +// Write barriers +GC_IMPL_FN void rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b); +GC_IMPL_FN void rb_gc_impl_writebarrier_unprotect(void *objspace_ptr, VALUE obj); +GC_IMPL_FN void rb_gc_impl_writebarrier_remember(void *objspace_ptr, VALUE obj); +// Heap walking +GC_IMPL_FN void rb_gc_impl_each_objects(void *objspace_ptr, int (*callback)(void *, void *, size_t, void *), void *data); +GC_IMPL_FN void rb_gc_impl_each_object(void *objspace_ptr, void (*func)(VALUE obj, void *data), void *data); +// Finalizers +GC_IMPL_FN void rb_gc_impl_make_zombie(void *objspace_ptr, VALUE obj, void (*dfree)(void *), void *data); +GC_IMPL_FN VALUE rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block); +GC_IMPL_FN void rb_gc_impl_undefine_finalizer(void *objspace_ptr, VALUE obj); +GC_IMPL_FN void rb_gc_impl_copy_finalizer(void *objspace_ptr, VALUE dest, VALUE obj); +GC_IMPL_FN void rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr); +// Object ID +GC_IMPL_FN VALUE rb_gc_impl_object_id(void *objspace_ptr, VALUE obj); +GC_IMPL_FN VALUE rb_gc_impl_object_id_to_ref(void *objspace_ptr, VALUE object_id); +// Forking +GC_IMPL_FN void rb_gc_impl_before_fork(void *objspace_ptr); +GC_IMPL_FN void rb_gc_impl_after_fork(void *objspace_ptr, rb_pid_t pid); +// Statistics +GC_IMPL_FN void rb_gc_impl_set_measure_total_time(void *objspace_ptr, VALUE flag); +GC_IMPL_FN bool rb_gc_impl_get_measure_total_time(void *objspace_ptr); +GC_IMPL_FN unsigned long long rb_gc_impl_get_total_time(void *objspace_ptr); +GC_IMPL_FN size_t rb_gc_impl_gc_count(void *objspace_ptr); +GC_IMPL_FN VALUE rb_gc_impl_latest_gc_info(void *objspace_ptr, VALUE key); +GC_IMPL_FN VALUE rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym); +GC_IMPL_FN VALUE rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym); +// Miscellaneous +GC_IMPL_FN size_t rb_gc_impl_obj_flags(void *objspace_ptr, VALUE obj, ID* flags, size_t max); +GC_IMPL_FN bool rb_gc_impl_pointer_to_heap_p(void *objspace_ptr, const void *ptr); +GC_IMPL_FN bool rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE obj); +GC_IMPL_FN void rb_gc_impl_set_event_hook(void *objspace_ptr, const rb_event_flag_t event); +GC_IMPL_FN void rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj); + +#undef GC_IMPL_FN + +#endif diff --git a/tool/mmtk_support.rb b/tool/mmtk_support.rb deleted file mode 100644 index 11c3afa..0000000 --- a/tool/mmtk_support.rb +++ /dev/null @@ -1,53 +0,0 @@ -class MMTkSupport - def build - clone_ruby_repo - cp_mmtk_gc - configure_ruby - make_ruby - make_shared_gc_mmtk - end - - private - - def clone_ruby_repo - FileUtils.mkdir_p("res/ruby") - unless Dir.exist?("res/ruby/.git") - system("git clone https://github.com/ruby/ruby.git --depth 1 res/ruby") or - raise "Failed to clone Ruby repository" - end - end - - def cp_mmtk_gc - FileUtils.rm_rf("res/ruby/gc/mmtk") - FileUtils.cp_r("#{FileUtils.pwd}/gc/mmtk", "res/ruby/gc/mmtk") - end - - def configure_ruby - base_dir = FileUtils.pwd - - unless File.exist?("res/ruby/Makefile") - FileUtils.chdir("res/ruby") do - system("./autogen.sh") - system(<<~EOF) - ./configure --prefix=#{FileUtils.pwd}/install \ - --disable-install-doc \ - --with-shared-gc=#{base_dir}/mod_gc - EOF - end - end - end - - def make_ruby - unless File.exist?("res/ruby/ruby") - FileUtils.chdir("res/ruby") do - system("make -j#{Etc.nprocessors}") - end - end - end - - def make_shared_gc_mmtk - FileUtils.chdir("res/ruby") do - system("make shared-gc SHARED_GC=mmtk") - end - end -end \ No newline at end of file