feat: implement FBIP (Functional But In-Place) reuse analysis

When rc=1 at update sites, mutate in-place instead of allocating new:

List.reverse:
- Swap element pointers in-place instead of creating new list

List.take:
- Truncate list in-place, decref dropped elements

List.drop:
- Shift elements to front in-place, decref dropped elements

List.map:
- Mutate elements in-place, decref old values before storing new

List.filter:
- Filter in-place by shifting kept elements, decref filtered-out elements

All operations check LUX_RC_HEADER(list)->rc == 1 at runtime and
fall back to allocation when rc > 1 (list is shared).

This completes Phase B performance optimizations:
- B1: Last-use optimization (ownership transfer) 
- B2: Reuse analysis (FBIP) 
- B3: Drop specialization 

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 14:37:15 -05:00
parent 3a22ae089f
commit 4d5a975b79
2 changed files with 104 additions and 13 deletions

View File

@@ -33,7 +33,7 @@ The RC system is now functional for lists and boxed values.
```
### What's NOT Yet Implemented
- Reuse analysis (FBIP) - mutate in-place when rc=1
- Drop fusion - combining consecutive drops (minor optimization)
## The Problem
@@ -345,7 +345,7 @@ void lux_check_leaks() {
| Conditionals | Yes | Yes ✅ |
| Last-use opt | Yes | Yes ✅ (ownership transfer) |
| Drop special | Yes | Yes ✅ |
| Reuse (FBIP) | Yes | No |
| Reuse (FBIP) | Yes | Yes ✅ |
| Drop fusion | Yes | No |
---
@@ -438,11 +438,12 @@ Rust's ownership system is fundamentally different:
- Source variable unregistered from RC tracking
- No double-free, no unnecessary incref/decref
2. **Reuse analysis (FBIP)** - NOT YET IMPLEMENTED
- Detect `rc=1` at update sites
- Mutate in-place instead of copy
- Major change to list operations
- ~300 lines
2. ~~**Reuse analysis (FBIP)**~~ ✅ DONE
- Runtime check `LUX_RC_HEADER(list)->rc == 1`
- List.map: mutate elements in-place, decref old values
- List.filter: filter in-place, decref removed elements
- List.reverse: swap pointers in-place
- List.take/drop: truncate/shift in-place
3. ~~**Drop specialization**~~ ✅ DONE
- Specialized decref functions: `lux_decref_list`, `lux_decref_closure`, etc.
@@ -458,12 +459,13 @@ Rust's ownership system is fundamentally different:
| A3 | Early returns | ~30 | P1 | ✅ Done |
| A4 | Conditionals | ~50 | P2 | ✅ Done |
| B1 | Last-use opt | ~80 | P3 | ✅ Done |
| B2 | Reuse (FBIP) | ~300 | P3 - Performance | Pending |
| B2 | Reuse (FBIP) | ~150 | P3 | ✅ Done |
| B3 | Drop special | ~100 | P3 | ✅ Done |
**Phase A: COMPLETE** ✅ - All leak prevention implemented
**Phase B: 2/3 DONE** ✅ - Major performance optimizations implemented
**Remaining: FBIP (~300 lines)** - In-place mutation when rc=1
**Phase B: COMPLETE** ✅ - All major performance optimizations implemented
Only remaining: Drop fusion (minor optimization, low priority)
### Cycle Detection

View File

@@ -1039,6 +1039,18 @@ impl CBackend {
self.writeln("}");
self.writeln("");
self.writeln("static LuxList* lux_list_reverse(LuxList* list) {");
self.writeln(" // FBIP: If rc=1, reverse in-place instead of copying");
self.writeln(" if (LUX_RC_HEADER(list)->rc == 1) {");
self.writeln(" // In-place reversal - just swap element pointers");
self.writeln(" int64_t n = list->length;");
self.writeln(" for (int64_t i = 0; i < n / 2; i++) {");
self.writeln(" void* tmp = list->elements[i];");
self.writeln(" list->elements[i] = list->elements[n - 1 - i];");
self.writeln(" list->elements[n - 1 - i] = tmp;");
self.writeln(" }");
self.writeln(" return list; // Reuse same list");
self.writeln(" }");
self.writeln(" // rc > 1: Allocate new list (standard path)");
self.writeln(" LuxList* result = lux_list_new(list->length);");
self.writeln(" for (int64_t i = 0; i < list->length; i++) {");
self.writeln(" lux_incref(list->elements[list->length - 1 - i]);");
@@ -1050,7 +1062,20 @@ impl CBackend {
self.writeln("");
self.writeln("static LuxList* lux_list_take(LuxList* list, int64_t n) {");
self.writeln(" if (n <= 0) return lux_list_new(0);");
self.writeln(" if (n > list->length) n = list->length;");
self.writeln(" if (n >= list->length) {");
self.writeln(" lux_incref(list); // Return same list");
self.writeln(" return list;");
self.writeln(" }");
self.writeln(" // FBIP: If rc=1, truncate in-place");
self.writeln(" if (LUX_RC_HEADER(list)->rc == 1) {");
self.writeln(" // Decref elements we're dropping");
self.writeln(" for (int64_t i = n; i < list->length; i++) {");
self.writeln(" lux_decref(list->elements[i]);");
self.writeln(" }");
self.writeln(" list->length = n;");
self.writeln(" return list; // Reuse same list");
self.writeln(" }");
self.writeln(" // rc > 1: Allocate new list");
self.writeln(" LuxList* result = lux_list_new(n);");
self.writeln(" for (int64_t i = 0; i < n; i++) {");
self.writeln(" lux_incref(list->elements[i]);");
@@ -1067,6 +1092,20 @@ impl CBackend {
self.writeln(" return list;");
self.writeln(" }");
self.writeln(" int64_t new_len = list->length - n;");
self.writeln(" // FBIP: If rc=1, shift elements in-place");
self.writeln(" if (LUX_RC_HEADER(list)->rc == 1) {");
self.writeln(" // Decref elements we're dropping");
self.writeln(" for (int64_t i = 0; i < n; i++) {");
self.writeln(" lux_decref(list->elements[i]);");
self.writeln(" }");
self.writeln(" // Shift remaining elements to front");
self.writeln(" for (int64_t i = 0; i < new_len; i++) {");
self.writeln(" list->elements[i] = list->elements[n + i];");
self.writeln(" }");
self.writeln(" list->length = new_len;");
self.writeln(" return list; // Reuse same list");
self.writeln(" }");
self.writeln(" // rc > 1: Allocate new list");
self.writeln(" LuxList* result = lux_list_new(new_len);");
self.writeln(" for (int64_t i = 0; i < new_len; i++) {");
self.writeln(" lux_incref(list->elements[n + i]);");
@@ -2432,7 +2471,26 @@ impl CBackend {
let fn_var = format!("_fn_{}", id);
let mapped_var = format!("_mapped_{}", id);
self.writeln(&format!("LuxList* {} = lux_list_new({}->length);", result_var, list));
// FBIP: Check if we can reuse the list in-place
self.writeln(&format!("LuxList* {};", result_var));
self.writeln(&format!("if (LUX_RC_HEADER({})->rc == 1) {{", list));
self.indent += 1;
self.writeln(&format!("// FBIP: Reuse list in-place"));
self.writeln(&format!("{} = {};", result_var, list));
self.writeln(&format!("for (int64_t {} = 0; {} < {}->length; {}++) {{", i_var, i_var, list, i_var));
self.indent += 1;
self.writeln(&format!("void* {} = {}->elements[{}];", elem_var, list, i_var));
self.writeln(&format!("LuxClosure* {} = (LuxClosure*){};", fn_var, closure));
self.writeln(&format!("LuxInt {} = ((LuxInt(*)(void*, LuxInt)){}->fn_ptr)({}->env, lux_unbox_int({}));", mapped_var, fn_var, fn_var, elem_var));
self.writeln(&format!("lux_decref({}); // Decref old element", elem_var));
self.writeln(&format!("{}->elements[{}] = lux_box_int({});", result_var, i_var, mapped_var));
self.indent -= 1;
self.writeln("}");
self.indent -= 1;
self.writeln("} else {");
self.indent += 1;
self.writeln(&format!("// Allocate new list"));
self.writeln(&format!("{} = lux_list_new({}->length);", result_var, list));
self.writeln(&format!("for (int64_t {} = 0; {} < {}->length; {}++) {{", i_var, i_var, list, i_var));
self.indent += 1;
self.writeln(&format!("void* {} = {}->elements[{}];", elem_var, list, i_var));
@@ -2442,6 +2500,8 @@ impl CBackend {
self.indent -= 1;
self.writeln("}");
self.writeln(&format!("{}->length = {}->length;", result_var, list));
self.indent -= 1;
self.writeln("}");
// Decref the closure if it was a temporary (inline lambda)
if closure.starts_with("_closure_") {
self.writeln(&format!("lux_decref_closure({});", closure));
@@ -2463,8 +2523,35 @@ impl CBackend {
let fn_var = format!("_fn_{}", id);
let keep_var = format!("_keep_{}", id);
self.writeln(&format!("LuxList* {} = lux_list_new({}->length);", result_var, list));
// FBIP: Check if we can filter in-place
self.writeln(&format!("LuxList* {};", result_var));
self.writeln(&format!("int64_t {} = 0;", count_var));
self.writeln(&format!("if (LUX_RC_HEADER({})->rc == 1) {{", list));
self.indent += 1;
self.writeln(&format!("// FBIP: Filter in-place"));
self.writeln(&format!("{} = {};", result_var, list));
self.writeln(&format!("for (int64_t {} = 0; {} < {}->length; {}++) {{", i_var, i_var, list, i_var));
self.indent += 1;
self.writeln(&format!("void* {} = {}->elements[{}];", elem_var, list, i_var));
self.writeln(&format!("LuxClosure* {} = (LuxClosure*){};", fn_var, closure));
self.writeln(&format!("LuxBool {} = ((LuxBool(*)(void*, LuxInt)){}->fn_ptr)({}->env, lux_unbox_int({}));", keep_var, fn_var, fn_var, elem_var));
self.writeln(&format!("if ({}) {{", keep_var));
self.indent += 1;
self.writeln(&format!("{}->elements[{}++] = {};", result_var, count_var, elem_var));
self.indent -= 1;
self.writeln("} else {");
self.indent += 1;
self.writeln(&format!("lux_decref({}); // Decref filtered-out element", elem_var));
self.indent -= 1;
self.writeln("}");
self.indent -= 1;
self.writeln("}");
self.writeln(&format!("{}->length = {};", result_var, count_var));
self.indent -= 1;
self.writeln("} else {");
self.indent += 1;
self.writeln(&format!("// Allocate new list"));
self.writeln(&format!("{} = lux_list_new({}->length);", result_var, list));
self.writeln(&format!("for (int64_t {} = 0; {} < {}->length; {}++) {{", i_var, i_var, list, i_var));
self.indent += 1;
self.writeln(&format!("void* {} = {}->elements[{}];", elem_var, list, i_var));
@@ -2480,6 +2567,8 @@ impl CBackend {
self.indent -= 1;
self.writeln("}");
self.writeln(&format!("{}->length = {};", result_var, count_var));
self.indent -= 1;
self.writeln("}");
// Decref the closure if it was a temporary (inline lambda)
if closure.starts_with("_closure_") {
self.writeln(&format!("lux_decref_closure({});", closure));