Merge branch 'master' into cell_lsp

This commit is contained in:
2026-02-17 01:20:11 -06:00
11 changed files with 119 additions and 32 deletions

View File

@@ -187,9 +187,11 @@ JSC_CCALL(vector_normalize,
double y = js2number(js, argv[1]);
double len = sqrt(x*x + y*y);
if (len > 0) {
JSValue result = JS_NewObject(js);
JS_FRAME(js);
JS_LOCAL(result, JS_NewObject(js));
JS_SetPropertyStr(js, result, "x", number2js(js, x/len));
JS_SetPropertyStr(js, result, "y", number2js(js, y/len));
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame);
ret = result;
}
)
@@ -258,6 +260,81 @@ mypackage/
The module file (`rtree.c`) includes the library header and uses `cell.h` as usual. The support files are plain C — they don't need any cell macros.
## GC Safety
ƿit uses a Cheney copying garbage collector. Any JS allocation — `JS_NewObject`, `JS_NewString`, `JS_SetPropertyStr`, etc. — can trigger GC, which **moves** heap objects to new addresses. C locals holding JSValue become stale after any allocating call.
### When you need rooting
If a function creates **one** heap object and returns it immediately, no rooting is needed:
```c
JSC_CCALL(mymod_name,
ret = JS_NewString(js, "hello");
)
```
If a function holds a heap object across further allocating calls, you must root it:
```c
JSC_CCALL(vector_normalize,
double x = js2number(js, argv[0]);
double y = js2number(js, argv[1]);
double len = sqrt(x*x + y*y);
if (len > 0) {
JS_FRAME(js);
JS_LOCAL(result, JS_NewObject(js));
// result is rooted — GC can update it through these calls:
JS_SetPropertyStr(js, result, "x", number2js(js, x/len));
JS_SetPropertyStr(js, result, "y", number2js(js, y/len));
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame);
ret = result;
}
)
```
### Macros
| Macro | Purpose |
|-------|---------|
| `JS_FRAME(js)` | Save the GC and local frames. Required before any `JS_LOCAL`. |
| `JS_LOCAL(name, init)` | Declare and root a JSValue. GC updates it through its address. |
| `JS_RETURN(val)` | Restore the frame and return a value. |
| `JS_RETURN_NULL()` | Restore the frame and return `JS_NULL`. |
| `JS_RETURN_EX()` | Restore the frame and return `JS_EXCEPTION`. |
| `JS_RestoreFrame(...)` | Manual frame restore (for `JSC_CCALL` bodies that use `ret =`). |
### Rules of thumb
1. **One allocation, immediate return** — no rooting needed.
2. **Object + property sets** — root the object with `JS_LOCAL`.
3. **Array + loop** — root the array; if loop body creates objects, root the loop variable too with a manual `JSLocalRef`.
4. **`CELL_USE_INIT` bodies** — always use `JS_FRAME` / `JS_LOCAL` / `JS_RETURN`.
### Migrating from gc_mark
The old mark-and-sweep GC had a `gc_mark` callback in `JSClassDef` for C structs that held JSValue fields. This no longer exists. The copying GC needs to know the **address** of every pointer to update it when objects move.
If your C struct holds a JSValue that must survive across GC points, root it for the duration it's alive:
```c
typedef struct {
JSValue callback;
JSLocalRef callback_lr;
} MyWidget;
// When storing:
widget->callback = value;
widget->callback_lr.ptr = &widget->callback;
JS_PushLocalRef(js, &widget->callback_lr);
// When done (before freeing the struct):
// The local ref is cleaned up when the frame is restored,
// or manage it manually.
```
In practice, most C wrappers hold only opaque C pointers (like `SDL_Window*`) and never store JSValues in the struct — these need no migration.
## Static Declarations
Keep internal functions and variables `static`:

View File

@@ -248,7 +248,7 @@ is_integer(42) // true
is_logical(true) // true
is_null(null) // true
is_number(3.14) // true
is_object({}) // true
is_object({}) // true (records only)
is_text("hello") // true
```

View File

@@ -512,12 +512,13 @@ var a = nil?(null) ? "yes" : "no" // "yes"
is_number(42) // true
is_text("hi") // true
is_logical(true) // true
is_object({}) // true
is_object({}) // true (records only)
is_array([]) // true
is_function(function(){}) // true
is_null(null) // true
is_object([]) // false (array is not object)
is_array({}) // false (object is not array)
is_object([]) // false (arrays are not records)
is_object("hello") // false (text is not a record)
is_array({}) // false (records are not arrays)
```
### Truthiness