summaryrefslogtreecommitdiff
path: root/imemo.c
diff options
context:
space:
mode:
authorPeter Zhu <peter@peterzhu.ca>2024-02-21 15:48:40 -0500
committerPeter Zhu <peter@peterzhu.ca>2024-02-22 11:35:09 -0500
commite65315a7253c58ad2d3bbf5482723976ff3d6640 (patch)
treeaea284b9ac2c7a0ff7683f0a855342ad0bfc4963 /imemo.c
parente04146129ec6898dd6a9739dad2983c6e9b68056 (diff)
Extract imemo functions from gc.c into imemo.c
Diffstat (limited to 'imemo.c')
-rw-r--r--imemo.c593
1 files changed, 593 insertions, 0 deletions
diff --git a/imemo.c b/imemo.c
new file mode 100644
index 0000000000..e549763d9a
--- /dev/null
+++ b/imemo.c
@@ -0,0 +1,593 @@
+
+#include "constant.h"
+#include "id_table.h"
+#include "internal.h"
+#include "internal/imemo.h"
+#include "vm_callinfo.h"
+
+size_t rb_iseq_memsize(const rb_iseq_t *iseq);
+void rb_iseq_mark_and_move(rb_iseq_t *iseq, bool reference_updating);
+void rb_iseq_free(const rb_iseq_t *iseq);
+
+const char *
+rb_imemo_name(enum imemo_type type)
+{
+ // put no default case to get a warning if an imemo type is missing
+ switch (type) {
+#define IMEMO_NAME(x) case imemo_##x: return #x;
+ IMEMO_NAME(ast);
+ IMEMO_NAME(callcache);
+ IMEMO_NAME(callinfo);
+ IMEMO_NAME(constcache);
+ IMEMO_NAME(cref);
+ IMEMO_NAME(env);
+ IMEMO_NAME(ifunc);
+ IMEMO_NAME(iseq);
+ IMEMO_NAME(memo);
+ IMEMO_NAME(ment);
+ IMEMO_NAME(parser_strterm);
+ IMEMO_NAME(svar);
+ IMEMO_NAME(throw_data);
+ IMEMO_NAME(tmpbuf);
+#undef IMEMO_NAME
+ default:
+ rb_bug("unreachable");
+ }
+}
+
+/* =========================================================================
+ * allocation
+ * ========================================================================= */
+
+VALUE
+rb_imemo_new(enum imemo_type type, VALUE v0)
+{
+ size_t size = RVALUE_SIZE;
+ VALUE flags = T_IMEMO | FL_WB_PROTECTED | (type << FL_USHIFT);
+ NEWOBJ_OF(obj, void, v0, flags, size, 0);
+
+ return (VALUE)obj;
+}
+
+static rb_imemo_tmpbuf_t *
+rb_imemo_tmpbuf_new(void)
+{
+ size_t size = sizeof(struct rb_imemo_tmpbuf_struct);
+ VALUE flags = T_IMEMO | (imemo_tmpbuf << FL_USHIFT);
+ NEWOBJ_OF(obj, struct rb_imemo_tmpbuf_struct, 0, flags, size, 0);
+
+ return obj;
+}
+
+void *
+rb_alloc_tmp_buffer_with_count(volatile VALUE *store, size_t size, size_t cnt)
+{
+ void *ptr;
+ rb_imemo_tmpbuf_t *tmpbuf;
+
+ /* Keep the order; allocate an empty imemo first then xmalloc, to
+ * get rid of potential memory leak */
+ tmpbuf = rb_imemo_tmpbuf_new();
+ *store = (VALUE)tmpbuf;
+ ptr = ruby_xmalloc(size);
+ tmpbuf->ptr = ptr;
+ tmpbuf->cnt = cnt;
+
+ return ptr;
+}
+
+void *
+rb_alloc_tmp_buffer(volatile VALUE *store, long len)
+{
+ long cnt;
+
+ if (len < 0 || (cnt = (long)roomof(len, sizeof(VALUE))) < 0) {
+ rb_raise(rb_eArgError, "negative buffer size (or size too big)");
+ }
+
+ return rb_alloc_tmp_buffer_with_count(store, len, cnt);
+}
+
+void
+rb_free_tmp_buffer(volatile VALUE *store)
+{
+ rb_imemo_tmpbuf_t *s = (rb_imemo_tmpbuf_t*)ATOMIC_VALUE_EXCHANGE(*store, 0);
+ if (s) {
+ void *ptr = ATOMIC_PTR_EXCHANGE(s->ptr, 0);
+ s->cnt = 0;
+ ruby_xfree(ptr);
+ }
+}
+
+rb_imemo_tmpbuf_t *
+rb_imemo_tmpbuf_parser_heap(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt)
+{
+ rb_imemo_tmpbuf_t *tmpbuf = rb_imemo_tmpbuf_new();
+ tmpbuf->ptr = buf;
+ tmpbuf->next = old_heap;
+ tmpbuf->cnt = cnt;
+
+ return tmpbuf;
+}
+
+#if IMEMO_DEBUG
+VALUE
+rb_imemo_new_debug(enum imemo_type type, VALUE v0, const char *file, int line)
+{
+ VALUE memo = rb_imemo_new(type, v0);
+ fprintf(stderr, "memo %p (type: %d) @ %s:%d\n", (void *)memo, imemo_type(memo), file, line);
+ return memo;
+}
+#endif
+
+/* =========================================================================
+ * memsize
+ * ========================================================================= */
+
+size_t
+rb_imemo_memsize(VALUE obj)
+{
+ size_t size = 0;
+ switch (imemo_type(obj)) {
+ case imemo_ast:
+ size += rb_ast_memsize((rb_ast_t *)obj);
+
+ break;
+ case imemo_callcache:
+ break;
+ case imemo_callinfo:
+ break;
+ case imemo_constcache:
+ break;
+ case imemo_cref:
+ break;
+ case imemo_env:
+ size += ((rb_env_t *)obj)->env_size * sizeof(VALUE);
+
+ break;
+ case imemo_ifunc:
+ break;
+ case imemo_iseq:
+ size += rb_iseq_memsize((rb_iseq_t *)obj);
+
+ break;
+ case imemo_memo:
+ break;
+ case imemo_ment:
+ size += sizeof(((rb_method_entry_t *)obj)->def);
+
+ break;
+ case imemo_parser_strterm:
+ break;
+ case imemo_svar:
+ break;
+ case imemo_throw_data:
+ break;
+ case imemo_tmpbuf:
+ size += ((rb_imemo_tmpbuf_t *)obj)->cnt * sizeof(VALUE);
+
+ break;
+ default:
+ rb_bug("unreachable");
+ }
+
+ return size;
+}
+
+/* =========================================================================
+ * mark
+ * ========================================================================= */
+
+static enum rb_id_table_iterator_result
+cc_table_mark_i(ID id, VALUE ccs_ptr, void *data)
+{
+ struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_ptr;
+ VALUE klass = (VALUE)data;
+ VM_ASSERT(vm_ccs_p(ccs));
+ VM_ASSERT(id == ccs->cme->called_id);
+
+ if (METHOD_ENTRY_INVALIDATED(ccs->cme)) {
+ rb_vm_ccs_free(ccs);
+ return ID_TABLE_DELETE;
+ }
+ else {
+ rb_gc_mark_movable((VALUE)ccs->cme);
+
+ for (int i=0; i<ccs->len; i++) {
+ VM_ASSERT(klass == ccs->entries[i].cc->klass);
+ VM_ASSERT(vm_cc_check_cme(ccs->entries[i].cc, ccs->cme));
+
+ rb_gc_mark_movable((VALUE)ccs->entries[i].ci);
+ rb_gc_mark_movable((VALUE)ccs->entries[i].cc);
+ }
+ return ID_TABLE_CONTINUE;
+ }
+}
+
+void
+rb_cc_table_mark(VALUE klass)
+{
+ struct rb_id_table *cc_tbl = RCLASS_CC_TBL(klass);
+ if (cc_tbl) {
+ rb_id_table_foreach(cc_tbl, cc_table_mark_i, (void *)klass);
+ }
+}
+
+static bool
+moved_or_living_object_strictly_p(VALUE obj)
+{
+ return obj &&
+ rb_gc_is_ptr_to_obj((void *)obj) &&
+ (rb_objspace_markable_object_p(obj) || BUILTIN_TYPE(obj) == T_MOVED);
+}
+
+static void
+mark_and_move_method_entry(rb_method_entry_t *ment, bool reference_updating)
+{
+ rb_method_definition_t *def = ment->def;
+
+ rb_gc_mark_and_move(&ment->owner);
+ rb_gc_mark_and_move(&ment->defined_class);
+
+ if (def) {
+ switch (def->type) {
+ case VM_METHOD_TYPE_ISEQ:
+ if (def->body.iseq.iseqptr) {
+ rb_gc_mark_and_move_ptr(&def->body.iseq.iseqptr);
+ }
+ rb_gc_mark_and_move_ptr(&def->body.iseq.cref);
+
+ if (!reference_updating) {
+ if (def->iseq_overload && ment->defined_class) {
+ // it can be a key of "overloaded_cme" table
+ // so it should be pinned.
+ rb_gc_mark((VALUE)ment);
+ }
+ }
+ break;
+ case VM_METHOD_TYPE_ATTRSET:
+ case VM_METHOD_TYPE_IVAR:
+ rb_gc_mark_and_move(&def->body.attr.location);
+ break;
+ case VM_METHOD_TYPE_BMETHOD:
+ rb_gc_mark_and_move(&def->body.bmethod.proc);
+ if (!reference_updating) {
+ if (def->body.bmethod.hooks) rb_hook_list_mark(def->body.bmethod.hooks);
+ }
+ break;
+ case VM_METHOD_TYPE_ALIAS:
+ rb_gc_mark_and_move_ptr(&def->body.alias.original_me);
+ return;
+ case VM_METHOD_TYPE_REFINED:
+ rb_gc_mark_and_move_ptr(&def->body.refined.orig_me);
+ break;
+ case VM_METHOD_TYPE_CFUNC:
+ case VM_METHOD_TYPE_ZSUPER:
+ case VM_METHOD_TYPE_MISSING:
+ case VM_METHOD_TYPE_OPTIMIZED:
+ case VM_METHOD_TYPE_UNDEF:
+ case VM_METHOD_TYPE_NOTIMPLEMENTED:
+ break;
+ }
+ }
+}
+
+void
+rb_imemo_mark_and_move(VALUE obj, bool reference_updating)
+{
+ switch (imemo_type(obj)) {
+ case imemo_ast:
+ rb_ast_mark_and_move((rb_ast_t *)obj, reference_updating);
+
+ break;
+ case imemo_callcache: {
+ /* cc is callcache.
+ *
+ * cc->klass (klass) should not be marked because if the klass is
+ * free'ed, the cc->klass will be cleared by `vm_cc_invalidate()`.
+ *
+ * cc->cme (cme) should not be marked because if cc is invalidated
+ * when cme is free'ed.
+ * - klass marks cme if klass uses cme.
+ * - caller classe's ccs->cme marks cc->cme.
+ * - if cc is invalidated (klass doesn't refer the cc),
+ * cc is invalidated by `vm_cc_invalidate()` and cc->cme is
+ * not be accessed.
+ * - On the multi-Ractors, cme will be collected with global GC
+ * so that it is safe if GC is not interleaving while accessing
+ * cc and cme.
+ * - However, cc_type_super and cc_type_refinement are not chained
+ * from ccs so cc->cme should be marked; the cme might be
+ * reachable only through cc in these cases.
+ */
+ struct rb_callcache *cc = (struct rb_callcache *)obj;
+ if (reference_updating) {
+ if (!cc->klass) {
+ // already invalidated
+ }
+ else {
+ if (moved_or_living_object_strictly_p(cc->klass) &&
+ moved_or_living_object_strictly_p((VALUE)cc->cme_)) {
+ *((VALUE *)&cc->klass) = rb_gc_location(cc->klass);
+ *((struct rb_callable_method_entry_struct **)&cc->cme_) =
+ (struct rb_callable_method_entry_struct *)rb_gc_location((VALUE)cc->cme_);
+ }
+ else {
+ vm_cc_invalidate(cc);
+ }
+ }
+ }
+ else {
+ if (vm_cc_super_p(cc) || vm_cc_refinement_p(cc)) {
+ rb_gc_mark_movable((VALUE)cc->cme_);
+ }
+ }
+
+ break;
+ }
+ case imemo_callinfo:
+ break;
+ case imemo_constcache: {
+ struct iseq_inline_constant_cache_entry *ice = (struct iseq_inline_constant_cache_entry *)obj;
+
+ rb_gc_mark_and_move(&ice->value);
+
+ break;
+ }
+ case imemo_cref: {
+ rb_cref_t *cref = (rb_cref_t *)obj;
+
+ rb_gc_mark_and_move(&cref->klass_or_self);
+ rb_gc_mark_and_move_ptr(&cref->next);
+ rb_gc_mark_and_move(&cref->refinements);
+
+ break;
+ }
+ case imemo_env: {
+ rb_env_t *env = (rb_env_t *)obj;
+
+ if (LIKELY(env->ep)) {
+ // just after newobj() can be NULL here.
+ RUBY_ASSERT(rb_gc_location(env->ep[VM_ENV_DATA_INDEX_ENV]) == rb_gc_location(obj));
+ RUBY_ASSERT(reference_updating || VM_ENV_ESCAPED_P(env->ep));
+
+ for (unsigned int i = 0; i < env->env_size; i++) {
+ rb_gc_mark_and_move((VALUE *)&env->env[i]);
+ }
+
+ rb_gc_mark_and_move_ptr(&env->iseq);
+
+ if (reference_updating) {
+ ((VALUE *)env->ep)[VM_ENV_DATA_INDEX_ENV] = rb_gc_location(env->ep[VM_ENV_DATA_INDEX_ENV]);
+ }
+ else {
+ VM_ENV_FLAGS_SET(env->ep, VM_ENV_FLAG_WB_REQUIRED);
+ rb_gc_mark_movable( (VALUE)rb_vm_env_prev_env(env));
+ }
+ }
+
+ break;
+ }
+ case imemo_ifunc: {
+ struct vm_ifunc *ifunc = (struct vm_ifunc *)obj;
+
+ if (!reference_updating) {
+ rb_gc_mark_maybe((VALUE)ifunc->data);
+ }
+
+ break;
+ }
+ case imemo_iseq:
+ rb_iseq_mark_and_move((rb_iseq_t *)obj, reference_updating);
+ break;
+ case imemo_memo: {
+ struct MEMO *memo = (struct MEMO *)obj;
+
+ rb_gc_mark_and_move((VALUE *)&memo->v1);
+ rb_gc_mark_and_move((VALUE *)&memo->v2);
+ if (!reference_updating) {
+ rb_gc_mark_maybe(memo->u3.value);
+ }
+
+ break;
+ }
+ case imemo_ment:
+ mark_and_move_method_entry((rb_method_entry_t *)obj, reference_updating);
+ break;
+ case imemo_parser_strterm:
+ break;
+ case imemo_svar: {
+ struct vm_svar *svar = (struct vm_svar *)obj;
+
+ rb_gc_mark_and_move((VALUE *)&svar->cref_or_me);
+ rb_gc_mark_and_move((VALUE *)&svar->lastline);
+ rb_gc_mark_and_move((VALUE *)&svar->backref);
+ rb_gc_mark_and_move((VALUE *)&svar->others);
+
+ break;
+ }
+ case imemo_throw_data: {
+ struct vm_throw_data *throw_data = (struct vm_throw_data *)obj;
+
+ rb_gc_mark_and_move((VALUE *)&throw_data->throw_obj);
+
+ break;
+ }
+ case imemo_tmpbuf: {
+ const rb_imemo_tmpbuf_t *m = (const rb_imemo_tmpbuf_t *)obj;
+
+ if (!reference_updating) {
+ do {
+ rb_gc_mark_locations(m->ptr, m->ptr + m->cnt);
+ } while ((m = m->next) != NULL);
+ }
+
+ break;
+ }
+ default:
+ rb_bug("unreachable");
+ }
+}
+
+/* =========================================================================
+ * free
+ * ========================================================================= */
+
+static enum rb_id_table_iterator_result
+free_const_entry_i(VALUE value, void *data)
+{
+ rb_const_entry_t *ce = (rb_const_entry_t *)value;
+ xfree(ce);
+ return ID_TABLE_CONTINUE;
+}
+
+void
+rb_free_const_table(struct rb_id_table *tbl)
+{
+ rb_id_table_foreach_values(tbl, free_const_entry_i, 0);
+ rb_id_table_free(tbl);
+}
+
+// alive: if false, target pointers can be freed already.
+static void
+vm_ccs_free(struct rb_class_cc_entries *ccs, int alive, VALUE klass)
+{
+ if (ccs->entries) {
+ for (int i=0; i<ccs->len; i++) {
+ const struct rb_callcache *cc = ccs->entries[i].cc;
+ if (!alive) {
+ void *ptr = asan_unpoison_object_temporary((VALUE)cc);
+ // ccs can be free'ed.
+ if (rb_gc_is_ptr_to_obj(cc) &&
+ IMEMO_TYPE_P(cc, imemo_callcache) &&
+ cc->klass == klass) {
+ // OK. maybe target cc.
+ }
+ else {
+ if (ptr) {
+ asan_poison_object((VALUE)cc);
+ }
+ continue;
+ }
+ if (ptr) {
+ asan_poison_object((VALUE)cc);
+ }
+ }
+
+ VM_ASSERT(!vm_cc_super_p(cc) && !vm_cc_refinement_p(cc));
+ vm_cc_invalidate(cc);
+ }
+ ruby_xfree(ccs->entries);
+ }
+ ruby_xfree(ccs);
+}
+
+void
+rb_vm_ccs_free(struct rb_class_cc_entries *ccs)
+{
+ RB_DEBUG_COUNTER_INC(ccs_free);
+ vm_ccs_free(ccs, true, Qundef);
+}
+
+static enum rb_id_table_iterator_result
+cc_table_free_i(VALUE ccs_ptr, void *data)
+{
+ struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_ptr;
+ VALUE klass = (VALUE)data;
+ VM_ASSERT(vm_ccs_p(ccs));
+
+ vm_ccs_free(ccs, false, klass);
+
+ return ID_TABLE_CONTINUE;
+}
+
+void
+rb_cc_table_free(VALUE klass)
+{
+ struct rb_id_table *cc_tbl = RCLASS_CC_TBL(klass);
+
+ if (cc_tbl) {
+ rb_id_table_foreach_values(cc_tbl, cc_table_free_i, (void *)klass);
+ rb_id_table_free(cc_tbl);
+ }
+}
+
+void
+rb_imemo_free(VALUE obj)
+{
+ switch (imemo_type(obj)) {
+ case imemo_ast:
+ rb_ast_free((rb_ast_t *)obj);
+ RB_DEBUG_COUNTER_INC(obj_imemo_ast);
+
+ break;
+ case imemo_callcache:
+ RB_DEBUG_COUNTER_INC(obj_imemo_callcache);
+
+ break;
+ case imemo_callinfo:{
+ const struct rb_callinfo *ci = ((const struct rb_callinfo *)obj);
+
+ rb_vm_ci_free(ci);
+ if (ci->kwarg) {
+ ((struct rb_callinfo_kwarg *)ci->kwarg)->references--;
+ if (ci->kwarg->references == 0) xfree((void *)ci->kwarg);
+ }
+ RB_DEBUG_COUNTER_INC(obj_imemo_callinfo);
+
+ break;
+ }
+ case imemo_constcache:
+ RB_DEBUG_COUNTER_INC(obj_imemo_constcache);
+
+ break;
+ case imemo_cref:
+ RB_DEBUG_COUNTER_INC(obj_imemo_cref);
+
+ break;
+ case imemo_env: {
+ rb_env_t *env = (rb_env_t *)obj;
+
+ RUBY_ASSERT(VM_ENV_ESCAPED_P(env->ep));
+ xfree((VALUE *)env->env);
+ RB_DEBUG_COUNTER_INC(obj_imemo_env);
+
+ break;
+ }
+ case imemo_ifunc:
+ RB_DEBUG_COUNTER_INC(obj_imemo_ifunc);
+ break;
+ case imemo_iseq:
+ rb_iseq_free((rb_iseq_t *)obj);
+ RB_DEBUG_COUNTER_INC(obj_imemo_iseq);
+
+ break;
+ case imemo_memo:
+ RB_DEBUG_COUNTER_INC(obj_imemo_memo);
+
+ break;
+ case imemo_ment:
+ rb_free_method_entry((rb_method_entry_t *)obj);
+ RB_DEBUG_COUNTER_INC(obj_imemo_ment);
+
+ break;
+ case imemo_parser_strterm:
+ RB_DEBUG_COUNTER_INC(obj_imemo_parser_strterm);
+
+ break;
+ case imemo_svar:
+ RB_DEBUG_COUNTER_INC(obj_imemo_svar);
+ break;
+ case imemo_throw_data:
+ RB_DEBUG_COUNTER_INC(obj_imemo_throw_data);
+
+ break;
+ case imemo_tmpbuf:
+ xfree(((rb_imemo_tmpbuf_t *)obj)->ptr);
+ RB_DEBUG_COUNTER_INC(obj_imemo_tmpbuf);
+
+ break;
+ default:
+ rb_bug("unreachable");
+ }
+}