Skip to content

Fix memory leak#716

Merged
jserv merged 4 commits intosysprog21:masterfrom
fourcolor:fix-memory-leak
Mar 6, 2026
Merged

Fix memory leak#716
jserv merged 4 commits intosysprog21:masterfrom
fourcolor:fix-memory-leak

Conversation

@fourcolor
Copy link
Copy Markdown
Collaborator

@fourcolor fourcolor commented Feb 9, 2026

This PR fixes several ASan-reported memory leaks:

  • Dispose tk/utk builders returned from instruction handlers in t2c_trace_ebb().
  • Dispose messages returned by LLVMGetHostCPUName() / LLVMGetHostCPUFeatures().
  • Free branch_tables for all cached blocks before teardown.
  • Free fd_map and mem in rv_delete().
  • Free all live cache entries in cache_free().

Summary by cubic

Fixes ASan-reported memory leaks in the JIT and teardown for a clean shutdown and lower memory use. Disposes temporary LLVM builders/messages and frees branch tables on eviction/teardown, plus fd_map/mem and cache entries.

  • Bug Fixes
    • t2c: dispose tk/utk builders; dispose LLVMGetHostCPUName/Features messages.
    • eviction/teardown: free ir->branch_table when replacing/evicting blocks and for all remaining blocks before freeing the cache; delete fd_map and mem in rv_delete().
    • cache: free all live and ghost cache entries in cache_free().

Written for commit 2138efe. Summary will update on new commits.

cubic-dev-ai[bot]

This comment was marked as resolved.

jserv

This comment was marked as outdated.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as outdated.

@sysprog21 sysprog21 deleted a comment from cubic-dev-ai bot Mar 4, 2026
@sysprog21 sysprog21 deleted a comment from cubic-dev-ai bot Mar 4, 2026
cubic-dev-ai[bot]

This comment was marked as resolved.

The tk/utk builders passed back from instruction handlers are never
disposed in t2c_trace_ebb. This change make sure them deposed at
the end.
Fix memory leak by disposing messages from LLVMGetHostCPUName/Features
- Free branch_tables for all cached blocks
- Free fd_map/mem in rv_delete()
- Free all live cache entries in cache_free()
Copy link
Copy Markdown
Contributor

@jserv jserv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

All five leak categories are correctly fixed. No double-free, no use-after-free, no race conditions, no regressions. Cross-validated with Gemini and Codex (GPT) — all three reviewers agree the fixes are sound.

Per-commit assessment

  1. 970f619 Dispose LLVM builders in t2c_trace_ebb — The tk != *builder / utk != *builder guard is correct; avoids double-disposing the caller's builder.

  2. 72f3e75 Free LLVMGetHostCPU* return strings — Textbook fix. LLVM C API returns malloc'd strings; LLVMDisposeMessage is the right cleanup.

  3. 3cbd3f0 Free fd_map/mem in JIT teardown + cache entriesfd_map/mem were only freed in the non-JIT #else path. The cache_free() entry cleanup is also necessary (list nodes were previously leaked).

  4. 2138efe Free branch_table on runtime block eviction — Covers all three eviction loops. free(NULL) is a no-op per C standard, so calling unconditionally on every IR node is safe and simple.

Ordering correctness

The teardown sequence in rv_delete is the only correct ordering: clear_cache_hot(free_block_branch_tables)jit_state_exitcache_freempool_destroy. Reversing free_block_branch_tables after mpool_destroy would be use-after-free on IR nodes. The current code is correct.

Pre-existing leak (not introduced by this PR)

In src/t2c_template.c, the hand-written cbeqz (line 1214-1218) and cbnez handlers do not call LLVMDisposeBuilder(builder2/builder3) in their else branches, unlike the macro version (line 343/355) which correctly disposes them. Worth a follow-up fix.

if (tk && tk != *builder)
LLVMDisposeBuilder(tk);
if (utk && utk != *builder)
LLVMDisposeBuilder(utk);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good. The tk != *builder and utk != *builder guards are necessary and correct — when the branch is taken/untaken within the EBB, tk/utk alias *builder and must not be double-disposed. When they point to distinct builders created by instruction handlers, they are properly cleaned up here.

One subtlety: tk/utk are initialized to NULL and only set by branch instruction handlers. The tk && / utk && null checks handle the case where no branch was encountered. Clean.

LLVMDisposeTargetMachine(tm);
LLVMDisposeMessage(triple);
LLVMDisposeMessage(cpu_name);
LLVMDisposeMessage(cpu_features);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. LLVMGetHostCPUName() and LLVMGetHostCPUFeatures() return malloc'd C strings per LLVM C API contract. LLVMDisposeMessage (which calls free) is the documented cleanup. Previously these were passed as temporaries directly to LLVMCreateTargetMachine, leaking on every T2C compilation.

ir = next_ir) {
next_ir = ir->next;

free(ir->branch_table);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. This is the runtime eviction path in block_find_or_translate — when the cache is full and a block is replaced. Without this, blocks with indirect branches (JALR dispatch tables) leaked their branch_table allocation on every eviction cycle. free(NULL) is a no-op for the majority of IR nodes that have no branch table.

clear_cache_hot(rv->block_cache, t2c_dispose_block_engine);
#endif
/* Free branch tables for all remaining blocks before freeing cache */
clear_cache_hot(rv->block_cache, free_block_branch_tables);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must remain before mpool_destroy(rv->block_ir_mp) at line 1114. free_block_branch_tables dereferences ir->branch_table through IR nodes allocated from that pool — destroying the pool first would be use-after-free.

Consider adding a brief comment noting this ordering constraint, e.g.:

/* Must precede mpool_destroy(block_ir_mp) -- IR nodes live in that pool */

mpool_destroy(rv->block_ir_mp);
mpool_destroy(rv->block_mp);
mpool_destroy(rv->fuse_mp);
map_delete(attr->fd_map);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. These were previously only freed in the #if !RV32_HAS(JIT) path (line 1081-1082), meaning every JIT-enabled build leaked fd_map and mem on exit.

#else
list_for_each_entry_safe (entry, safe, &cache->list, list, cache_entry_t)
#endif
free(entry);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The #ifdef __HAVE_TYPEOF / #else pattern appears twice (lines 401-406 for live, 408-414 for ghost). Consider factoring into a helper macro or static function that takes a struct list_head * to reduce duplication. Not a blocker.

/* Free IRs that main thread skipped during deferred eviction */
for (rv_insn_t *ir = block->ir_head, *next_ir; ir; ir = next_ir) {
next_ir = ir->next;
free(ir->branch_table);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. This deferred eviction path runs under cache_lock (acquired at line 600). The block was evicted by the main thread while T2C was compiling it — now we properly free branch_table before returning the IR node to the pool. Same pattern correctly applied at line 635 for the successful-compilation-but-evicted case.

@jserv jserv merged commit 960ed8b into sysprog21:master Mar 6, 2026
17 checks passed
@jserv
Copy link
Copy Markdown
Contributor

jserv commented Mar 6, 2026

Thank @fourcolor for contributing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants