remove dupavlue and freevalue
This commit is contained in:
@@ -318,7 +318,6 @@ JSValue js_reader_list(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
|
||||
JSValue filename = JS_NewString(js, file_stat.m_filename);
|
||||
if (JS_IsException(filename)) {
|
||||
JS_FreeValue(js, arr);
|
||||
return filename;
|
||||
}
|
||||
JS_SetPropertyNumber(js, arr, arr_index++, filename);
|
||||
|
||||
403
gc_plan.md
403
gc_plan.md
@@ -1,403 +0,0 @@
|
||||
# Plan: Complete Copying GC Implementation
|
||||
|
||||
## Overview
|
||||
|
||||
Remove reference counting (DupValue/FreeValue) entirely and complete the Cheney copying garbage collector. Each JSContext will use bump allocation from a heap block, and when out of memory, request a new heap from JSRuntime's buddy allocator and copy live objects to the new heap.
|
||||
|
||||
## Target Architecture (from docs/memory.md)
|
||||
|
||||
### Object Types (simplified from current):
|
||||
|
||||
**Type 0 - Array**: `{ header, length, elements[] }`
|
||||
**Type 1 - Blob**: `{ header, length, bits[] }`
|
||||
**Type 2 - Text**: `{ header, length_or_hash, packed_chars[] }`
|
||||
**Type 3 - Record**: `{ header, prototype, length, key_value_pairs[] }`
|
||||
**Type 4 - Function**: `{ header, code_ptr, outer_frame_ptr }` - 3 words only, always stone
|
||||
**Type 5 - Frame**: `{ header, function_ptr, caller_ptr, ret_addr, args[], closure_vars[], local_vars[], temps[] }`
|
||||
**Type 6 - Code**: Lives in immutable memory only, never copied
|
||||
**Type 7 - Forward**: Object has moved; cap56 contains new address
|
||||
|
||||
### Key Design Points:
|
||||
- **JSFunction** is just a pointer to code and a pointer to the frame that created it (3 words)
|
||||
- **Closure variables live in frames** - when a function returns, its frame is "reduced" to just the closure variables
|
||||
- **Code objects are immutable** - stored in stone memory, never copied during GC
|
||||
- **Frame reduction**: When a function returns, `caller` is set to zero, signaling the frame can be shrunk
|
||||
|
||||
## Current State (needs refactoring)
|
||||
|
||||
1. **Partial Cheney GC exists** at `source/quickjs.c:1844-2030`: `ctx_gc`, `gc_copy_value`, `gc_scan_object`
|
||||
2. **744 calls to JS_DupValue/JS_FreeValue** scattered throughout (currently undefined, causing compilation errors)
|
||||
3. **Current JSFunction** is bloated (has kind, name, union of cfunc/bytecode/bound) - needs simplification
|
||||
4. **Current JSVarRef** is a separate object - should be eliminated, closures live in frames
|
||||
5. **Bump allocator** in `js_malloc` (line 1495) with `heap_base`/`heap_free`/`heap_end`
|
||||
6. **Buddy allocator** for memory blocks (lines 1727-1837)
|
||||
7. **Header offset inconsistency** - some structs have header at offset 0, some at offset 8
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Define No-Op DupValue/FreeValue (To Enable Compilation)
|
||||
|
||||
Add these near line 100 in `source/quickjs.c`:
|
||||
|
||||
```c
|
||||
/* Copying GC - no reference counting needed */
|
||||
#define JS_DupValue(ctx, v) (v)
|
||||
#define JS_FreeValue(ctx, v) ((void)0)
|
||||
#define JS_DupValueRT(rt, v) (v)
|
||||
#define JS_FreeValueRT(rt, v) ((void)0)
|
||||
```
|
||||
|
||||
This makes the code compile while keeping existing call sites (they become no-ops).
|
||||
|
||||
### Phase 2: Standardize Object Headers (offset 0)
|
||||
|
||||
Remove `JSGCObjectHeader` (ref counting remnant) and put `objhdr_t` at offset 0:
|
||||
|
||||
```c
|
||||
typedef struct JSArray {
|
||||
objhdr_t hdr; // offset 0
|
||||
word_t length;
|
||||
JSValue values[];
|
||||
} JSArray;
|
||||
|
||||
typedef struct JSRecord {
|
||||
objhdr_t hdr; // offset 0
|
||||
JSRecord *proto;
|
||||
word_t length;
|
||||
slot slots[];
|
||||
} JSRecord;
|
||||
|
||||
typedef struct JSText {
|
||||
objhdr_t hdr; // offset 0
|
||||
word_t length; // pretext: length, text: hash
|
||||
word_t packed[];
|
||||
} JSText;
|
||||
|
||||
typedef struct JSBlob {
|
||||
objhdr_t hdr; // offset 0
|
||||
word_t length;
|
||||
uint8_t bits[];
|
||||
} JSBlob;
|
||||
|
||||
/* Simplified JSFunction per memory.md - 3 words */
|
||||
typedef struct JSFunction {
|
||||
objhdr_t hdr; // offset 0, always stone
|
||||
JSCode *code; // pointer to immutable code object
|
||||
struct JSFrame *outer; // frame that created this function
|
||||
} JSFunction;
|
||||
|
||||
/* JSFrame per memory.md */
|
||||
typedef struct JSFrame {
|
||||
objhdr_t hdr; // offset 0
|
||||
JSFunction *function; // function being executed
|
||||
struct JSFrame *caller; // calling frame (NULL = reduced/returned)
|
||||
word_t ret_addr; // return instruction address
|
||||
JSValue slots[]; // args, closure vars, locals, temps
|
||||
} JSFrame;
|
||||
|
||||
/* JSCode - always in immutable (stone) memory */
|
||||
typedef struct JSCode {
|
||||
objhdr_t hdr; // offset 0, always stone
|
||||
word_t arity; // max number of inputs
|
||||
word_t frame_size; // capacity of activation frame
|
||||
word_t closure_size; // reduced capacity for returned frames
|
||||
word_t entry_point; // address to begin execution
|
||||
word_t disruption_point;// address of disruption clause
|
||||
uint8_t bytecode[]; // actual bytecode
|
||||
} JSCode;
|
||||
```
|
||||
|
||||
### Phase 3: Complete gc_object_size for All Types
|
||||
|
||||
Update `gc_object_size` (line 1850) to read header at offset 0:
|
||||
|
||||
```c
|
||||
static size_t gc_object_size(void *ptr) {
|
||||
objhdr_t hdr = *(objhdr_t*)ptr; // Header at offset 0
|
||||
uint8_t type = objhdr_type(hdr);
|
||||
uint64_t cap = objhdr_cap56(hdr);
|
||||
|
||||
switch (type) {
|
||||
case OBJ_ARRAY:
|
||||
return sizeof(JSArray) + cap * sizeof(JSValue);
|
||||
case OBJ_BLOB:
|
||||
return sizeof(JSBlob) + (cap + 7) / 8; // cap is bits
|
||||
case OBJ_TEXT:
|
||||
return sizeof(JSText) + ((cap + 1) / 2) * sizeof(uint64_t);
|
||||
case OBJ_RECORD:
|
||||
return sizeof(JSRecord) + (cap + 1) * sizeof(slot); // cap is mask
|
||||
case OBJ_FUNCTION:
|
||||
return sizeof(JSFunction); // 3 words
|
||||
case OBJ_FRAME:
|
||||
return sizeof(JSFrame) + cap * sizeof(JSValue); // cap is slot count
|
||||
case OBJ_CODE:
|
||||
return 0; // Code is never copied (immutable)
|
||||
default:
|
||||
return 64; // Conservative fallback
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Complete gc_scan_object for All Types
|
||||
|
||||
Update `gc_scan_object` (line 1924):
|
||||
|
||||
```c
|
||||
static void gc_scan_object(JSContext *ctx, void *ptr, uint8_t **to_free, uint8_t *to_end) {
|
||||
objhdr_t hdr = *(objhdr_t*)ptr;
|
||||
uint8_t type = objhdr_type(hdr);
|
||||
|
||||
switch (type) {
|
||||
case OBJ_ARRAY: {
|
||||
JSArray *arr = (JSArray*)ptr;
|
||||
for (uint32_t i = 0; i < arr->length; i++) {
|
||||
arr->values[i] = gc_copy_value(ctx, arr->values[i], to_free, to_end);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_RECORD: {
|
||||
JSRecord *rec = (JSRecord*)ptr;
|
||||
// Copy prototype
|
||||
if (rec->proto) {
|
||||
JSValue proto_val = JS_MKPTR(rec->proto);
|
||||
proto_val = gc_copy_value(ctx, proto_val, to_free, to_end);
|
||||
rec->proto = (JSRecord*)JS_VALUE_GET_PTR(proto_val);
|
||||
}
|
||||
// Copy table entries
|
||||
uint32_t mask = objhdr_cap56(rec->hdr);
|
||||
for (uint32_t i = 1; i <= mask; i++) { // Skip slot 0
|
||||
JSValue k = rec->slots[i].key;
|
||||
if (!rec_key_is_empty(k) && !rec_key_is_tomb(k)) {
|
||||
rec->slots[i].key = gc_copy_value(ctx, k, to_free, to_end);
|
||||
rec->slots[i].value = gc_copy_value(ctx, rec->slots[i].value, to_free, to_end);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_FUNCTION: {
|
||||
JSFunction *func = (JSFunction*)ptr;
|
||||
// Code is immutable, don't copy - but outer frame needs copying
|
||||
if (func->outer) {
|
||||
JSValue outer_val = JS_MKPTR(func->outer);
|
||||
outer_val = gc_copy_value(ctx, outer_val, to_free, to_end);
|
||||
func->outer = (JSFrame*)JS_VALUE_GET_PTR(outer_val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_FRAME: {
|
||||
JSFrame *frame = (JSFrame*)ptr;
|
||||
// Copy function pointer
|
||||
if (frame->function) {
|
||||
JSValue func_val = JS_MKPTR(frame->function);
|
||||
func_val = gc_copy_value(ctx, func_val, to_free, to_end);
|
||||
frame->function = (JSFunction*)JS_VALUE_GET_PTR(func_val);
|
||||
}
|
||||
// Copy caller (unless NULL = reduced frame)
|
||||
if (frame->caller) {
|
||||
JSValue caller_val = JS_MKPTR(frame->caller);
|
||||
caller_val = gc_copy_value(ctx, caller_val, to_free, to_end);
|
||||
frame->caller = (JSFrame*)JS_VALUE_GET_PTR(caller_val);
|
||||
}
|
||||
// Copy all slots (args, closure vars, locals, temps)
|
||||
uint32_t slot_count = objhdr_cap56(frame->hdr);
|
||||
for (uint32_t i = 0; i < slot_count; i++) {
|
||||
frame->slots[i] = gc_copy_value(ctx, frame->slots[i], to_free, to_end);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_TEXT:
|
||||
case OBJ_BLOB:
|
||||
case OBJ_CODE:
|
||||
// No internal references to scan
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Fix gc_copy_value Forwarding
|
||||
|
||||
Update `gc_copy_value` (line 1883) for offset 0 headers:
|
||||
|
||||
```c
|
||||
static JSValue gc_copy_value(JSContext *ctx, JSValue v, uint8_t **to_free, uint8_t *to_end) {
|
||||
if (!JS_IsPtr(v)) return v; // Immediate value
|
||||
|
||||
void *ptr = JS_VALUE_GET_PTR(v);
|
||||
|
||||
// Stone memory - don't copy (includes Code objects)
|
||||
objhdr_t hdr = *(objhdr_t*)ptr;
|
||||
if (objhdr_s(hdr)) return v;
|
||||
|
||||
// Check if in current heap
|
||||
if ((uint8_t*)ptr < ctx->heap_base || (uint8_t*)ptr >= ctx->heap_end)
|
||||
return v; // External allocation
|
||||
|
||||
// Already forwarded?
|
||||
if (objhdr_type(hdr) == OBJ_FORWARD) {
|
||||
void *new_ptr = (void*)(uintptr_t)objhdr_cap56(hdr);
|
||||
return JS_MKPTR(new_ptr);
|
||||
}
|
||||
|
||||
// Copy object to new space
|
||||
size_t size = gc_object_size(ptr);
|
||||
void *new_ptr = *to_free;
|
||||
*to_free += size;
|
||||
memcpy(new_ptr, ptr, size);
|
||||
|
||||
// Leave forwarding pointer in old location
|
||||
*(objhdr_t*)ptr = objhdr_make((uint64_t)(uintptr_t)new_ptr, OBJ_FORWARD, 0, 0, 0, 0);
|
||||
|
||||
return JS_MKPTR(new_ptr);
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 6: Complete GC Root Tracing
|
||||
|
||||
Update `ctx_gc` (line 1966) to trace all roots including JSGCRef:
|
||||
|
||||
```c
|
||||
static int ctx_gc(JSContext *ctx) {
|
||||
// ... existing setup code ...
|
||||
|
||||
// Copy roots: global object, class prototypes, etc. (existing)
|
||||
ctx->global_obj = gc_copy_value(ctx, ctx->global_obj, &to_free, to_end);
|
||||
ctx->global_var_obj = gc_copy_value(ctx, ctx->global_var_obj, &to_free, to_end);
|
||||
// ... other existing root copying ...
|
||||
|
||||
// Copy GC root stack (JS_PUSH_VALUE/JS_POP_VALUE)
|
||||
for (JSGCRef *ref = ctx->top_gc_ref; ref; ref = ref->prev) {
|
||||
ref->val = gc_copy_value(ctx, ref->val, &to_free, to_end);
|
||||
}
|
||||
|
||||
// Copy GC root list (JS_AddGCRef/JS_DeleteGCRef)
|
||||
for (JSGCRef *ref = ctx->last_gc_ref; ref; ref = ref->prev) {
|
||||
ref->val = gc_copy_value(ctx, ref->val, &to_free, to_end);
|
||||
}
|
||||
|
||||
// Copy current exception
|
||||
ctx->current_exception = gc_copy_value(ctx, ctx->current_exception, &to_free, to_end);
|
||||
|
||||
// Cheney scan (existing)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 7: Trigger GC on Allocation Failure
|
||||
|
||||
Update `js_malloc` (line 1495):
|
||||
|
||||
```c
|
||||
void *js_malloc(JSContext *ctx, size_t size) {
|
||||
size = (size + 7) & ~7; // Align to 8 bytes
|
||||
|
||||
if ((uint8_t*)ctx->heap_free + size > (uint8_t*)ctx->heap_end) {
|
||||
if (ctx_gc(ctx) < 0) {
|
||||
JS_ThrowOutOfMemory(ctx);
|
||||
return NULL;
|
||||
}
|
||||
// Retry after GC
|
||||
if ((uint8_t*)ctx->heap_free + size > (uint8_t*)ctx->heap_end) {
|
||||
JS_ThrowOutOfMemory(ctx);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void *ptr = ctx->heap_free;
|
||||
ctx->heap_free = (uint8_t*)ctx->heap_free + size;
|
||||
return ptr;
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 8: Frame Reduction (for closures)
|
||||
|
||||
When a function returns, "reduce" its frame to just closure variables:
|
||||
|
||||
```c
|
||||
static void reduce_frame(JSContext *ctx, JSFrame *frame) {
|
||||
if (frame->caller == NULL) return; // Already reduced
|
||||
|
||||
JSCode *code = frame->function->code;
|
||||
uint32_t closure_size = code->closure_size;
|
||||
|
||||
// Shrink capacity to just closure variables
|
||||
frame->hdr = objhdr_make(closure_size, OBJ_FRAME, 0, 0, 0, 0);
|
||||
frame->caller = NULL; // Signal: frame is reduced
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 9: Remove Unused Reference Counting Code
|
||||
|
||||
Delete:
|
||||
- `gc_decref`, `gc_decref_child` functions
|
||||
- `gc_scan_incref_child`, `gc_scan_incref_child2` functions
|
||||
- `JS_GCPhaseEnum`, `gc_phase` fields
|
||||
- `JSGCObjectHeader` struct (merge into objhdr_t)
|
||||
- `ref_count` fields from any remaining structs
|
||||
- `mark_function_children_decref` function
|
||||
- All `free_*` functions that rely on ref counting
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. **source/quickjs.c** - Main implementation:
|
||||
- Add DupValue/FreeValue no-op macros (~line 100)
|
||||
- Restructure JSArray, JSBlob, JSText, JSRecord (lines 468-499)
|
||||
- Simplify JSFunction to 3-word struct (line 1205)
|
||||
- Add JSFrame as heap object (new)
|
||||
- Restructure JSCode/JSFunctionBytecode (line 1293)
|
||||
- Fix gc_object_size (line 1850)
|
||||
- Fix gc_copy_value (line 1883)
|
||||
- Complete gc_scan_object (line 1924)
|
||||
- Update ctx_gc for all roots (line 1966)
|
||||
- Update js_malloc to trigger GC (line 1495)
|
||||
- Delete ref counting code throughout
|
||||
|
||||
2. **source/quickjs.h** - Public API:
|
||||
- Remove JSGCObjectHeader
|
||||
- Update JSValue type checks if needed
|
||||
- Ensure JS_IsStone works with offset 0 headers
|
||||
|
||||
## Execution Order
|
||||
|
||||
1. **First**: Add DupValue/FreeValue macros (enables compilation)
|
||||
2. **Second**: Standardize struct layouts (header at offset 0)
|
||||
3. **Third**: Fix gc_object_size and gc_copy_value
|
||||
4. **Fourth**: Complete gc_scan_object for all types
|
||||
5. **Fifth**: Update ctx_gc with complete root tracing
|
||||
6. **Sixth**: Wire js_malloc to trigger GC
|
||||
7. **Seventh**: Add frame reduction for closures
|
||||
8. **Finally**: Remove ref counting dead code
|
||||
|
||||
## Verification
|
||||
|
||||
1. **Compile test**: `make` should succeed without errors
|
||||
2. **Basic test**: Run simple scripts:
|
||||
```js
|
||||
var a = [1, 2, 3]
|
||||
log.console(a[1])
|
||||
```
|
||||
3. **Stress test**: Allocate many objects to trigger GC:
|
||||
```js
|
||||
for (var i = 0; i < 100000; i++) {
|
||||
var x = { value: i }
|
||||
}
|
||||
log.console("done")
|
||||
```
|
||||
4. **Closure test**: Test functions with closures survive GC:
|
||||
```js
|
||||
fn make_counter() {
|
||||
var count = 0
|
||||
fn inc() { count = count + 1; return count }
|
||||
return inc
|
||||
}
|
||||
var c = make_counter()
|
||||
log.console(c()) // 1
|
||||
log.console(c()) // 2
|
||||
```
|
||||
5. **GC stress with closures**: Create many closures, trigger GC, verify they still work
|
||||
|
||||
## Key Design Decisions (Resolved)
|
||||
|
||||
1. **JSCode storage**: Lives in stone (immutable) memory, never copied during GC ✓
|
||||
2. **Header offset**: Standardized to offset 0 for all heap objects ✓
|
||||
3. **Closure variables**: Live in JSFrame objects; frames are "reduced" when functions return ✓
|
||||
4. **JSVarRef**: Eliminated - closures reference their outer frame directly ✓
|
||||
@@ -18,7 +18,6 @@ static void js_enet_peer_finalizer(JSRuntime *rt, JSValue val)
|
||||
{
|
||||
ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id);
|
||||
if (peer && peer->data) {
|
||||
JS_FreeValueRT(rt, *(JSValue*)peer->data);
|
||||
free(peer->data);
|
||||
}
|
||||
}
|
||||
@@ -59,7 +58,6 @@ static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int ar
|
||||
JSValue config_obj = argv[0];
|
||||
JSValue addr_val = JS_GetPropertyStr(ctx, config_obj, "address");
|
||||
const char *addr_str = JS_IsText(addr_val) ? JS_ToCString(ctx, addr_val) : NULL;
|
||||
JS_FreeValue(ctx, addr_val);
|
||||
|
||||
if (!addr_str)
|
||||
send = NULL;
|
||||
@@ -67,7 +65,6 @@ static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int ar
|
||||
JSValue port_val = JS_GetPropertyStr(ctx, config_obj, "port");
|
||||
int32_t port32 = 0;
|
||||
JS_ToInt32(ctx, &port32, port_val);
|
||||
JS_FreeValue(ctx, port_val);
|
||||
|
||||
if (strcmp(addr_str, "any") == 0)
|
||||
address.host = ENET_HOST_ANY;
|
||||
@@ -86,15 +83,12 @@ static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int ar
|
||||
|
||||
JSValue chan_val = JS_GetPropertyStr(ctx, config_obj, "channels");
|
||||
JS_ToUint32(ctx, &channel_limit, chan_val);
|
||||
JS_FreeValue(ctx, chan_val);
|
||||
|
||||
JSValue in_bw_val = JS_GetPropertyStr(ctx, config_obj, "incoming_bandwidth");
|
||||
JS_ToUint32(ctx, &incoming_bandwidth, in_bw_val);
|
||||
JS_FreeValue(ctx, in_bw_val);
|
||||
|
||||
JSValue out_bw_val = JS_GetPropertyStr(ctx, config_obj, "outgoing_bandwidth");
|
||||
JS_ToUint32(ctx, &outgoing_bandwidth, out_bw_val);
|
||||
JS_FreeValue(ctx, out_bw_val);
|
||||
|
||||
host = enet_host_create(send, peer_count, channel_limit, incoming_bandwidth, outgoing_bandwidth);
|
||||
if (!host) return JS_RaiseDisrupt(ctx, "Failed to create ENet host");
|
||||
@@ -117,7 +111,7 @@ static JSValue peer_get_value(JSContext *ctx, ENetPeer *peer)
|
||||
*(JSValue*)peer->data = JS_NewObjectClass(ctx, enet_peer_class_id);
|
||||
JS_SetOpaque(*(JSValue*)peer->data, peer);
|
||||
}
|
||||
return JS_DupValue(ctx, *(JSValue*)peer->data);
|
||||
return *(JSValue*)peer->data;
|
||||
}
|
||||
|
||||
// Poll for and process any available network events from this host,
|
||||
@@ -182,7 +176,7 @@ static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val, int a
|
||||
}
|
||||
}
|
||||
|
||||
JS_FreeValue(ctx, JS_Call(ctx, argv[0], JS_NULL, 1, &event_ref.val));
|
||||
JS_Call(ctx, argv[0], JS_NULL, 1, &event_ref.val);
|
||||
}
|
||||
|
||||
JS_RETURN_NULL();
|
||||
|
||||
@@ -223,12 +223,10 @@ JSC_SCALL(fd_rmdir,
|
||||
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
JSValue args[2] = { JS_NewString(js, full_path), JS_TRUE };
|
||||
JSValue result = js_fd_rmdir(js, JS_NULL, 2, args);
|
||||
JS_FreeValue(js, args[0]);
|
||||
if (JS_IsException(result)) {
|
||||
FindClose(hFind);
|
||||
return result;
|
||||
}
|
||||
JS_FreeValue(js, result);
|
||||
} else {
|
||||
if (unlink(full_path) != 0) {
|
||||
FindClose(hFind);
|
||||
@@ -252,12 +250,10 @@ JSC_SCALL(fd_rmdir,
|
||||
if (lstat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||
JSValue args[2] = { JS_NewString(js, full_path), JS_TRUE };
|
||||
JSValue result = js_fd_rmdir(js, JS_NULL, 2, args);
|
||||
JS_FreeValue(js, args[0]);
|
||||
if (JS_IsException(result)) {
|
||||
closedir(dir);
|
||||
return result;
|
||||
}
|
||||
JS_FreeValue(js, result);
|
||||
} else {
|
||||
if (unlink(full_path) != 0) {
|
||||
closedir(dir);
|
||||
|
||||
@@ -326,7 +326,6 @@ JSC_SCALL(fd_readdir,
|
||||
|
||||
if (pd_file->listfiles(str, listfiles_cb, &ctx, 0) != 0) {
|
||||
const char* err = pd_file->geterr();
|
||||
JS_FreeValue(js, ret_arr);
|
||||
return JS_RaiseDisrupt(js, "listfiles failed: %s", err ? err : "unknown error");
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ typedef struct NotaEncodeContext {
|
||||
static void nota_stack_push (NotaEncodeContext *enc, JSValueConst val) {
|
||||
NotaVisitedNode *node = (NotaVisitedNode *)sys_malloc (sizeof (NotaVisitedNode));
|
||||
JS_PushGCRef (enc->ctx, &node->ref);
|
||||
node->ref.val = JS_DupValue (enc->ctx, val);
|
||||
node->ref.val = val;
|
||||
node->next = enc->visited_list;
|
||||
enc->visited_list = node;
|
||||
}
|
||||
@@ -32,7 +32,6 @@ static void nota_stack_push (NotaEncodeContext *enc, JSValueConst val) {
|
||||
static void nota_stack_pop (NotaEncodeContext *enc) {
|
||||
NotaVisitedNode *node = enc->visited_list;
|
||||
enc->visited_list = node->next;
|
||||
JS_FreeValue (enc->ctx, node->ref.val);
|
||||
JS_PopGCRef (enc->ctx, &node->ref);
|
||||
sys_free (node);
|
||||
}
|
||||
@@ -48,14 +47,12 @@ static int nota_stack_has (NotaEncodeContext *enc, JSValueConst val) {
|
||||
}
|
||||
|
||||
static JSValue nota_apply_replacer (NotaEncodeContext *enc, JSValueConst holder, JSValueConst key, JSValueConst val) {
|
||||
if (!enc->replacer_ref || JS_IsNull (enc->replacer_ref->val)) return JS_DupValue (enc->ctx, val);
|
||||
if (!enc->replacer_ref || JS_IsNull (enc->replacer_ref->val)) return val;
|
||||
|
||||
JSValue args[2] = { JS_DupValue (enc->ctx, key), JS_DupValue (enc->ctx, val) };
|
||||
JSValue args[2] = { key, val };
|
||||
JSValue result = JS_Call (enc->ctx, enc->replacer_ref->val, holder, 2, args);
|
||||
JS_FreeValue (enc->ctx, args[0]);
|
||||
JS_FreeValue (enc->ctx, args[1]);
|
||||
|
||||
if (JS_IsException (result)) return JS_DupValue (enc->ctx, val);
|
||||
if (JS_IsException (result)) return val;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -140,15 +137,11 @@ static char *js_do_nota_decode (JSContext *js, JSValue *tmp, char *nota, JSValue
|
||||
}
|
||||
|
||||
if (!JS_IsNull (reviver)) {
|
||||
JSValue args[2] = { JS_DupValue (js, key), JS_DupValue (js, *tmp) };
|
||||
JSValue args[2] = { key, *tmp };
|
||||
JSValue revived = JS_Call (js, reviver, holder, 2, args);
|
||||
JS_FreeValue (js, args[0]);
|
||||
JS_FreeValue (js, args[1]);
|
||||
if (!JS_IsException (revived)) {
|
||||
JS_FreeValue (js, *tmp);
|
||||
*tmp = revived;
|
||||
} else {
|
||||
JS_FreeValue (js, revived);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,10 +222,8 @@ static void nota_encode_value (NotaEncodeContext *enc, JSValueConst val, JSValue
|
||||
if (!JS_IsNull (adata)) {
|
||||
nota_write_sym (&enc->nb, NOTA_PRIVATE);
|
||||
nota_encode_value (enc, adata, replaced_ref.val, JS_NULL);
|
||||
JS_FreeValue (ctx, adata);
|
||||
break;
|
||||
}
|
||||
JS_FreeValue (ctx, adata);
|
||||
if (nota_stack_has (enc, replaced_ref.val)) {
|
||||
enc->cycle = 1;
|
||||
break;
|
||||
|
||||
@@ -41,13 +41,11 @@ static void wota_stack_free (WotaEncodeContext *enc) {
|
||||
}
|
||||
|
||||
static JSValue wota_apply_replacer (WotaEncodeContext *enc, JSValueConst holder, JSValue key, JSValueConst val) {
|
||||
if (JS_IsNull (enc->replacer)) return JS_DupValue (enc->ctx, val);
|
||||
JSValue key_val = JS_IsNull (key) ? JS_NULL : JS_DupValue (enc->ctx, key);
|
||||
JSValue args[2] = { key_val, JS_DupValue (enc->ctx, val) };
|
||||
if (JS_IsNull (enc->replacer)) return val;
|
||||
JSValue key_val = JS_IsNull (key) ? JS_NULL : key;
|
||||
JSValue args[2] = { key_val, val };
|
||||
JSValue result = JS_Call (enc->ctx, enc->replacer, holder, 2, args);
|
||||
JS_FreeValue (enc->ctx, args[0]);
|
||||
JS_FreeValue (enc->ctx, args[1]);
|
||||
if (JS_IsException (result)) return JS_DupValue (enc->ctx, val);
|
||||
if (JS_IsException (result)) return val;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -60,20 +58,17 @@ static void encode_object_properties (WotaEncodeContext *enc, JSValueConst val,
|
||||
JSGCRef val_ref, keys_ref;
|
||||
JS_PushGCRef (ctx, &val_ref);
|
||||
JS_PushGCRef (ctx, &keys_ref);
|
||||
val_ref.val = JS_DupValue (ctx, val);
|
||||
val_ref.val = val;
|
||||
|
||||
keys_ref.val = JS_GetOwnPropertyNames (ctx, val_ref.val);
|
||||
if (JS_IsException (keys_ref.val)) {
|
||||
wota_write_sym (&enc->wb, WOTA_NULL);
|
||||
JS_FreeValue (ctx, val_ref.val);
|
||||
JS_PopGCRef (ctx, &keys_ref);
|
||||
JS_PopGCRef (ctx, &val_ref);
|
||||
return;
|
||||
}
|
||||
int64_t plen64;
|
||||
if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) {
|
||||
JS_FreeValue (ctx, keys_ref.val);
|
||||
JS_FreeValue (ctx, val_ref.val);
|
||||
wota_write_sym (&enc->wb, WOTA_NULL);
|
||||
JS_PopGCRef (ctx, &keys_ref);
|
||||
JS_PopGCRef (ctx, &val_ref);
|
||||
@@ -105,12 +100,9 @@ static void encode_object_properties (WotaEncodeContext *enc, JSValueConst val,
|
||||
prop_refs[non_function_count].val = prop_val;
|
||||
non_function_count++;
|
||||
} else {
|
||||
JS_FreeValue (ctx, prop_val);
|
||||
JS_FreeValue (ctx, key_refs[i].val);
|
||||
key_refs[i].val = JS_NULL;
|
||||
}
|
||||
}
|
||||
JS_FreeValue (ctx, keys_ref.val);
|
||||
wota_write_record (&enc->wb, non_function_count);
|
||||
for (uint32_t i = 0; i < non_function_count; i++) {
|
||||
size_t klen;
|
||||
@@ -118,8 +110,6 @@ static void encode_object_properties (WotaEncodeContext *enc, JSValueConst val,
|
||||
wota_write_text_len (&enc->wb, prop_name ? prop_name : "", prop_name ? klen : 0);
|
||||
wota_encode_value (enc, prop_refs[i].val, val_ref.val, key_refs[i].val);
|
||||
JS_FreeCString (ctx, prop_name);
|
||||
JS_FreeValue (ctx, prop_refs[i].val);
|
||||
JS_FreeValue (ctx, key_refs[i].val);
|
||||
}
|
||||
/* Pop all GC refs in reverse order */
|
||||
for (int i = plen - 1; i >= 0; i--) {
|
||||
@@ -128,7 +118,6 @@ static void encode_object_properties (WotaEncodeContext *enc, JSValueConst val,
|
||||
}
|
||||
sys_free (prop_refs);
|
||||
sys_free (key_refs);
|
||||
JS_FreeValue (ctx, val_ref.val);
|
||||
JS_PopGCRef (ctx, &keys_ref);
|
||||
JS_PopGCRef (ctx, &val_ref);
|
||||
}
|
||||
@@ -139,7 +128,7 @@ static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValue
|
||||
if (!JS_IsNull (enc->replacer) && !JS_IsNull (key))
|
||||
replaced = wota_apply_replacer (enc, holder, key, val);
|
||||
else
|
||||
replaced = JS_DupValue (enc->ctx, val);
|
||||
replaced = val;
|
||||
|
||||
int tag = JS_VALUE_GET_TAG (replaced);
|
||||
switch (tag) {
|
||||
@@ -183,7 +172,6 @@ static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValue
|
||||
size_t buf_len;
|
||||
void *buf_data = js_get_blob_data (ctx, &buf_len, replaced);
|
||||
if (buf_data == (void *)-1) {
|
||||
JS_FreeValue (ctx, replaced);
|
||||
return;
|
||||
}
|
||||
if (buf_len == 0) {
|
||||
@@ -205,7 +193,6 @@ static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValue
|
||||
for (int64_t i = 0; i < arr_len; i++) {
|
||||
JSValue elem_val = JS_GetPropertyNumber (ctx, replaced, i);
|
||||
wota_encode_value (enc, elem_val, replaced, JS_NewInt32 (ctx, (int32_t)i));
|
||||
JS_FreeValue (ctx, elem_val);
|
||||
}
|
||||
wota_stack_pop (enc);
|
||||
break;
|
||||
@@ -218,10 +205,8 @@ static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValue
|
||||
if (!JS_IsNull (adata)) {
|
||||
wota_write_sym (&enc->wb, WOTA_PRIVATE);
|
||||
wota_encode_value (enc, adata, replaced, JS_NULL);
|
||||
JS_FreeValue (ctx, adata);
|
||||
break;
|
||||
}
|
||||
JS_FreeValue (ctx, adata);
|
||||
if (wota_stack_has (enc, replaced)) {
|
||||
enc->cycle = 1;
|
||||
break;
|
||||
@@ -230,16 +215,13 @@ static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValue
|
||||
JSValue to_json = JS_GetPropertyStr (ctx, replaced, "toJSON");
|
||||
if (JS_IsFunction (to_json)) {
|
||||
JSValue result = JS_Call (ctx, to_json, replaced, 0, NULL);
|
||||
JS_FreeValue (ctx, to_json);
|
||||
if (!JS_IsException (result)) {
|
||||
wota_encode_value (enc, result, holder, key);
|
||||
JS_FreeValue (ctx, result);
|
||||
} else
|
||||
wota_write_sym (&enc->wb, WOTA_NULL);
|
||||
wota_stack_pop (enc);
|
||||
break;
|
||||
}
|
||||
JS_FreeValue (ctx, to_json);
|
||||
encode_object_properties (enc, replaced, holder);
|
||||
wota_stack_pop (enc);
|
||||
break;
|
||||
@@ -248,7 +230,6 @@ static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValue
|
||||
wota_write_sym (&enc->wb, WOTA_NULL);
|
||||
break;
|
||||
}
|
||||
JS_FreeValue (ctx, replaced);
|
||||
}
|
||||
|
||||
static char *decode_wota_value (JSContext *ctx, char *data_ptr, JSValue *out_val, JSValue holder, JSValue key, JSValue reviver) {
|
||||
@@ -355,16 +336,12 @@ static char *decode_wota_value (JSContext *ctx, char *data_ptr, JSValue *out_val
|
||||
break;
|
||||
}
|
||||
if (!JS_IsNull (reviver)) {
|
||||
JSValue key_val = JS_IsNull (key) ? JS_NULL : JS_DupValue (ctx, key);
|
||||
JSValue args[2] = { key_val, JS_DupValue (ctx, *out_val) };
|
||||
JSValue key_val = JS_IsNull (key) ? JS_NULL : key;
|
||||
JSValue args[2] = { key_val, *out_val };
|
||||
JSValue revived = JS_Call (ctx, reviver, holder, 2, args);
|
||||
JS_FreeValue (ctx, args[0]);
|
||||
JS_FreeValue (ctx, args[1]);
|
||||
if (!JS_IsException (revived)) {
|
||||
JS_FreeValue (ctx, *out_val);
|
||||
*out_val = revived;
|
||||
} else
|
||||
JS_FreeValue (ctx, revived);
|
||||
}
|
||||
}
|
||||
return data_ptr;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,6 @@ JSC_CCALL(socket_getaddrinfo,
|
||||
else if (strcmp(family, "AF_INET6") == 0) hints.ai_family = AF_INET6;
|
||||
JS_FreeCString(js, family);
|
||||
}
|
||||
JS_FreeValue(js, val);
|
||||
|
||||
val = JS_GetPropertyStr(js, argv[2], "socktype");
|
||||
if (!JS_IsNull(val)) {
|
||||
@@ -73,19 +72,16 @@ JSC_CCALL(socket_getaddrinfo,
|
||||
else if (strcmp(socktype, "SOCK_DGRAM") == 0) hints.ai_socktype = SOCK_DGRAM;
|
||||
JS_FreeCString(js, socktype);
|
||||
}
|
||||
JS_FreeValue(js, val);
|
||||
|
||||
val = JS_GetPropertyStr(js, argv[2], "flags");
|
||||
if (!JS_IsNull(val)) {
|
||||
hints.ai_flags = js2number(js, val);
|
||||
}
|
||||
JS_FreeValue(js, val);
|
||||
|
||||
val = JS_GetPropertyStr(js, argv[2], "passive");
|
||||
if (JS_ToBool(js, val)) {
|
||||
hints.ai_flags |= AI_PASSIVE;
|
||||
}
|
||||
JS_FreeValue(js, val);
|
||||
}
|
||||
|
||||
int status = getaddrinfo(node, service, &hints, &res);
|
||||
|
||||
338
plan.md
338
plan.md
@@ -1,338 +0,0 @@
|
||||
# Cell/QuickJS Refactoring Plan: Remove Atoms, Shapes, and Dual-Encoding
|
||||
|
||||
## Overview
|
||||
|
||||
Refactor `source/quickjs.c` to match `docs/memory.md` specification:
|
||||
- Remove JSAtom system (171 references → ~41 remaining)
|
||||
- Remove JSShape system (94 references) ✓
|
||||
- Remove IC caches (shape-based inline caches) ✓
|
||||
- Remove `is_wide_char` dual-encoding (18 locations) ✓
|
||||
- Use JSValue texts directly as property keys
|
||||
- Reference: `mquickjs.c` shows the target pattern
|
||||
|
||||
## Completed Phases
|
||||
|
||||
### Phase 1: Remove is_wide_char Remnants ✓
|
||||
### Phase 2: Remove IC Caches ✓
|
||||
### Phase 3: Remove JSShape System ✓
|
||||
### Phase 4: Complete Property Access with JSValue Keys ✓
|
||||
|
||||
Completed:
|
||||
- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_get_field
|
||||
- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_put_field
|
||||
- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_define_field
|
||||
- Created emit_key() function that adds JSValue to cpool and emits index
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Convert JSAtom to JSValue Text (IN PROGRESS)
|
||||
|
||||
This is the core transformation. All identifier handling moves from atoms to JSValue.
|
||||
|
||||
### Completed Items
|
||||
|
||||
**Token and Parser Infrastructure:**
|
||||
- [x] Change JSToken.u.ident.atom to JSToken.u.ident.str (JSValue)
|
||||
- [x] Change parse_ident() to return JSValue
|
||||
- [x] Create emit_key() function (cpool-based)
|
||||
- [x] Create JS_KEY_* macros for common names (lines ~279-335 in quickjs.c)
|
||||
- [x] Update all token.u.ident.atom references to .str
|
||||
- [x] Create keyword lookup table (js_keywords[]) with string comparison
|
||||
- [x] Rewrite update_token_ident() to use js_keyword_lookup()
|
||||
- [x] Rewrite is_strict_future_keyword() to use JSValue
|
||||
- [x] Update token_is_pseudo_keyword() to use JSValue and js_key_equal()
|
||||
|
||||
**Function Declaration Parsing:**
|
||||
- [x] Update js_parse_function_decl() signature to use JSValue func_name
|
||||
- [x] Update js_parse_function_decl2() to use JSValue func_name throughout
|
||||
- [x] Update js_parse_function_check_names() to use JSValue
|
||||
- [x] Convert JS_DupAtom/JS_FreeAtom to JS_DupValue/JS_FreeValue in function parsing
|
||||
|
||||
**Variable Definition and Lookup:**
|
||||
- [x] Update find_global_var() to use JSValue and js_key_equal()
|
||||
- [x] Update find_lexical_global_var() to use JSValue
|
||||
- [x] Update find_lexical_decl() to use JSValue and js_key_equal()
|
||||
- [x] Update js_define_var() to use JSValue
|
||||
- [x] Update js_parse_check_duplicate_parameter() to use JSValue and js_key_equal()
|
||||
- [x] Update js_parse_destructuring_var() to return JSValue
|
||||
- [x] Update js_parse_var() to use JSValue for variable names
|
||||
|
||||
**Comparison Helpers:**
|
||||
- [x] Create js_key_equal_str() for comparing JSValue with C string literals
|
||||
- [x] Update is_var_in_arg_scope() to use js_key_equal/js_key_equal_str
|
||||
- [x] Update has_with_scope() to use js_key_equal_str
|
||||
- [x] Update closure variable comparisons (cv->var_name) to use js_key_equal_str
|
||||
|
||||
**Property Access:**
|
||||
- [x] Fix JS_GetPropertyStr to create proper JSValue keys
|
||||
- [x] Fix JS_SetPropertyInternal callers to use JS_KEY_* instead of JS_ATOM_*
|
||||
|
||||
### JS_KEY_* Macros Added
|
||||
|
||||
Compile-time immediate ASCII string constants (≤7 chars):
|
||||
```c
|
||||
JS_KEY_empty, JS_KEY_name, JS_KEY_message, JS_KEY_stack,
|
||||
JS_KEY_errors, JS_KEY_Error, JS_KEY_cause, JS_KEY_length,
|
||||
JS_KEY_value, JS_KEY_get, JS_KEY_set, JS_KEY_raw,
|
||||
JS_KEY_flags, JS_KEY_source, JS_KEY_exec, JS_KEY_toJSON,
|
||||
JS_KEY_eval, JS_KEY_this, JS_KEY_true, JS_KEY_false,
|
||||
JS_KEY_null, JS_KEY_NaN, JS_KEY_default, JS_KEY_index,
|
||||
JS_KEY_input, JS_KEY_groups, JS_KEY_indices, JS_KEY_let,
|
||||
JS_KEY_var, JS_KEY_new, JS_KEY_of, JS_KEY_yield,
|
||||
JS_KEY_async, JS_KEY_target, JS_KEY_from, JS_KEY_meta,
|
||||
JS_KEY_as, JS_KEY_with
|
||||
```
|
||||
|
||||
Runtime macro for strings >7 chars:
|
||||
```c
|
||||
#define JS_KEY_STR(ctx, str) JS_NewStringLen((ctx), (str), sizeof(str) - 1)
|
||||
```
|
||||
|
||||
Helper function for comparing JSValue with C string literals:
|
||||
```c
|
||||
static JS_BOOL js_key_equal_str(JSValue a, const char *str);
|
||||
```
|
||||
|
||||
### Remaining Work
|
||||
|
||||
#### 5.3 Update js_parse_property_name() ✓
|
||||
- [x] Change return type from JSAtom* to JSValue*
|
||||
- [x] Update all callers (js_parse_object_literal, etc.)
|
||||
- [x] Updated get_lvalue(), put_lvalue(), js_parse_destructuring_element()
|
||||
|
||||
#### 5.4 Replace remaining emit_atom() calls with emit_key() ✓
|
||||
- [x] Removed emit_atom wrapper function
|
||||
- [x] Changed last emit_atom(JS_ATOM_this) to emit_key(JS_KEY_this)
|
||||
|
||||
#### 5.5 Update Variable Opcode Format in quickjs-opcode.h
|
||||
- [ ] Change `atom` format opcodes to `key` format
|
||||
- [ ] Change `atom_u8` and `atom_u16` to `key_u8` and `key_u16`
|
||||
|
||||
#### 5.6 Update VM Opcode Handlers ✓
|
||||
These now read cpool indices and look up JSValue:
|
||||
- [x] OP_check_var, OP_get_var_undef, OP_get_var
|
||||
- [x] OP_put_var, OP_put_var_init, OP_put_var_strict
|
||||
- [x] OP_set_name, OP_make_var_ref, OP_delete_var
|
||||
- [x] OP_define_var, OP_define_func, OP_throw_error
|
||||
- [x] OP_make_loc_ref, OP_make_arg_ref
|
||||
- [x] OP_define_method, OP_define_method_computed
|
||||
|
||||
#### 5.7 Update resolve_scope_var() ✓
|
||||
- [x] Changed signature to use JSValue var_name
|
||||
- [x] Updated all comparisons to use js_key_equal()/js_key_equal_str()
|
||||
- [x] Updated var_object_test() to use JSValue
|
||||
- [x] Updated optimize_scope_make_global_ref() to use JSValue
|
||||
- [x] Updated resolve_variables() callers to read from cpool
|
||||
|
||||
#### 5.8 Convert Remaining JS_ATOM_* Usages
|
||||
Categories remaining:
|
||||
- Some debug/print functions still use JSAtom
|
||||
- Some function signatures not yet converted
|
||||
- Will be addressed in Phase 7 cleanup
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Update Bytecode Serialization ✓
|
||||
|
||||
### 6.1 JS_WriteObjectTag Changes ✓
|
||||
- [x] Changed JS_WriteObjectTag to use bc_put_key() directly for property keys
|
||||
- [x] Removed JS_ValueToAtom/bc_put_atom path (was broken anyway)
|
||||
- [x] cpool values serialized via JS_WriteObjectRec()
|
||||
|
||||
### 6.2 JS_ReadObject Changes ✓
|
||||
- [x] Changed JS_ReadObjectTag to use bc_get_key() for property keys
|
||||
- [x] Uses JS_SetPropertyInternal with JSValue keys
|
||||
|
||||
### 6.3 Opcode Format Updates ✓
|
||||
- [x] Added OP_FMT_key_u8, OP_FMT_key_u16, OP_FMT_key_label_u16 formats
|
||||
- [x] Updated variable opcodes to use key formats instead of atom formats
|
||||
- [x] Updated bc_byte_swap() to handle new key formats
|
||||
- [x] Updated JS_WriteFunctionBytecode() to skip key format opcodes
|
||||
- [x] Updated JS_ReadFunctionBytecode() to skip key format opcodes
|
||||
|
||||
### 6.4 Version Bump ✓
|
||||
- [x] Incremented BC_VERSION from 5 to 6
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Final Cleanup ✓
|
||||
|
||||
### 7.1 Additional Parser/Compiler Fixes ✓
|
||||
- [x] Fixed TOK_IDENT case to use JSValue name, JS_DupValue, emit_key
|
||||
- [x] Fixed TOK_TRY catch clause to use JSValue name
|
||||
- [x] Fixed js_parse_statement_or_decl label_name to use JSValue
|
||||
- [x] Fixed OP_scope_get_var "eval" check to use js_key_equal_str
|
||||
- [x] Fixed js_parse_delete to use JSValue for name comparison
|
||||
- [x] Fixed JSON parsing to use js_key_from_string for property names
|
||||
- [x] Added js_key_from_string() helper function
|
||||
- [x] Added JS_KEY__ret_, JS_KEY__eval_, JS_KEY__var_ for internal names
|
||||
- [x] Updated add_closure_var, get_closure_var2, get_closure_var to use JSValue var_name
|
||||
- [x] Updated set_closure_from_var to use JS_DupValue
|
||||
- [x] Updated add_closure_variables to use JS_DupValue
|
||||
- [x] Updated instantiate_hoisted_definitions to use fd_cpool_add for keys
|
||||
- [x] Updated resolve_variables to use js_key_equal and fd_cpool_add
|
||||
|
||||
### 7.1.1 Property Access and Runtime Fixes ✓
|
||||
- [x] Fixed JS_GetPropertyValue to use js_key_from_string instead of JS_ValueToAtom
|
||||
- [x] Fixed JS_GetPropertyKey to use js_key_from_string for string keys
|
||||
- [x] Fixed JS_SetPropertyKey to use js_key_from_string for string keys
|
||||
- [x] Fixed JS_HasPropertyKey to use js_key_from_string for string keys
|
||||
- [x] Fixed JS_DeletePropertyKey to use js_key_from_string for string keys
|
||||
- [x] Updated JS_HasProperty signature to take JSValue prop
|
||||
- [x] Fixed OP_get_ref_value handler to use JSValue key
|
||||
- [x] Fixed OP_put_ref_value handler to use JSValue key
|
||||
- [x] Updated free_func_def to use JS_FreeValue for JSValue fields
|
||||
|
||||
### 7.2 Remove JSAtom Type and Functions ✓
|
||||
- [x] Removed most JS_ATOM_* constants (kept JS_ATOM_NULL, JS_ATOM_END for BC compat)
|
||||
- [x] JS_NewAtomString now returns JSValue using js_key_new
|
||||
- [x] JS_FreeAtom, JS_DupAtom are stubs (no-op for backward compat)
|
||||
- [x] JS_AtomToValue, JS_ValueToAtom are stubs (minimal BC compat)
|
||||
- [x] Replaced JS_ATOM_* usages with JS_KEY_* or JS_GetPropertyStr
|
||||
|
||||
### 7.3 Additional Runtime Fixes ✓
|
||||
- [x] Fixed free_function_bytecode to use JS_FreeValueRT for JSValue fields
|
||||
- [x] Fixed JS_SetPropertyFunctionList to use JSValue keys via find_key()
|
||||
- [x] Fixed JS_InstantiateFunctionListItem to use JSValue keys
|
||||
- [x] Fixed internalize_json_property to use JSValue names
|
||||
- [x] Fixed emit_break and push_break_entry to use JSValue label_name
|
||||
- [x] Implemented JS_Invoke to use JSValue method key
|
||||
|
||||
### 7.4 Remaining Stubs (kept for bytecode backward compatibility)
|
||||
- JSAtom typedef (uint32_t) - used in BC serialization
|
||||
- JS_ATOM_NULL, JS_ATOM_END - bytecode format markers
|
||||
- JS_FreeAtom, JS_DupAtom - no-op stubs
|
||||
- JS_FreeAtomRT, JS_DupAtomRT - no-op stubs
|
||||
- Legacy BC reader (idx_to_atom array) - for reading old bytecode
|
||||
|
||||
---
|
||||
|
||||
## Current Build Status
|
||||
|
||||
**Build: SUCCEEDS** with warnings (unused variables, labels)
|
||||
|
||||
**Statistics:**
|
||||
- JS_ATOM_* usages: Minimal (only BC serialization compat)
|
||||
- Property access uses JS_KEY_* macros or JS_GetPropertyStr
|
||||
- BC_VERSION: 6 (updated for new key-based format)
|
||||
|
||||
**What Works:**
|
||||
- All property access via JSValue keys
|
||||
- Keyword detection via string comparison
|
||||
- Function declaration parsing with JSValue names
|
||||
- Variable definition with JSValue names
|
||||
- Closure variable tracking with JSValue names
|
||||
- VM opcode handlers read cpool indices and look up JSValue
|
||||
- resolve_scope_var() uses JSValue throughout
|
||||
- js_parse_property_name() returns JSValue
|
||||
- Bytecode serialization uses bc_put_key/bc_get_key for property keys
|
||||
- Variable opcodes use key format (cpool indices)
|
||||
- JSON parsing uses JSValue keys
|
||||
- Internal variable names use JS_KEY__ret_, JS_KEY__eval_, JS_KEY__var_
|
||||
- JS_SetPropertyFunctionList uses JSValue keys
|
||||
- JS_Invoke uses JSValue method keys
|
||||
- break/continue labels use JSValue
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Migrate to New Tagging System (IN PROGRESS)
|
||||
|
||||
**Problem**: `JS_VALUE_GET_TAG` returns `JS_TAG_PTR` for all pointers, but ~200 places check for obsolete tags like `JS_TAG_OBJECT`, `JS_TAG_STRING`, `JS_TAG_FUNCTION`, etc., which are never returned. This causes crashes.
|
||||
|
||||
**Target Design** (from memory.md):
|
||||
- JSValue tags: Only `JS_TAG_INT`, `JS_TAG_PTR`, `JS_TAG_SHORT_FLOAT`, `JS_TAG_SPECIAL`
|
||||
- Pointer types determined by `objhdr_t` header at offset 8 in heap objects
|
||||
- mist_obj_type: `OBJ_ARRAY(0)`, `OBJ_BLOB(1)`, `OBJ_TEXT(2)`, `OBJ_RECORD(3)`, `OBJ_FUNCTION(4)`, etc.
|
||||
|
||||
### 8.1 Unified Heap Object Layout ✓
|
||||
- [x] Updated mist_text structure to have objhdr_t at offset 8:
|
||||
```c
|
||||
typedef struct mist_text {
|
||||
JSRefCountHeader _dummy_header; /* unused, for offset alignment */
|
||||
uint32_t _pad; /* padding to align objhdr_t to offset 8 */
|
||||
objhdr_t hdr; /* NOW at offset 8, like JSString */
|
||||
word_t length;
|
||||
word_t packed[];
|
||||
} mist_text;
|
||||
```
|
||||
- [x] JSString already has objhdr_t at offset 8
|
||||
|
||||
### 8.2 Type-Checking Helper Functions ✓
|
||||
Added lowercase internal helpers (to avoid conflict with quickjs.h declarations):
|
||||
```c
|
||||
static inline JS_BOOL js_is_gc_object(JSValue v) { return JS_IsPtr(v); }
|
||||
static inline JSGCObjectTypeEnum js_get_gc_type(JSValue v) {
|
||||
return ((JSGCObjectHeader *)JS_VALUE_GET_PTR(v))->gc_obj_type;
|
||||
}
|
||||
static inline JS_BOOL js_is_record(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_RECORD;
|
||||
}
|
||||
static inline JS_BOOL js_is_array(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_ARRAY;
|
||||
}
|
||||
static inline JS_BOOL js_is_function(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_FUNCTION;
|
||||
}
|
||||
static inline JS_BOOL js_is_object(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
JSGCObjectTypeEnum t = js_get_gc_type(v);
|
||||
return t == JS_GC_OBJ_TYPE_RECORD || t == JS_GC_OBJ_TYPE_ARRAY;
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 Updated Core Functions ✓
|
||||
- [x] Updated JS_IsString to read objhdr_t from offset 8
|
||||
- [x] Updated js_key_hash to read objhdr_t from offset 8
|
||||
- [x] Updated js_key_equal to read objhdr_t from offset 8
|
||||
- [x] Updated __JS_FreeValueRT to use objhdr_type for type dispatch
|
||||
- [x] Updated JS_MarkValue, JS_MarkValueEdgeEx for GC
|
||||
- [x] Added JS_SetPropertyValue function
|
||||
- [x] Changed quickjs.h JS_IsFunction/JS_IsObject from inline to extern declarations
|
||||
|
||||
### 8.4 Tag Check Migration (PARTIAL)
|
||||
Updated some critical tag checks:
|
||||
- [x] Some JS_TAG_OBJECT checks → js_is_object() or js_is_record()
|
||||
- [ ] Many more JS_TAG_OBJECT checks remain (~200 total)
|
||||
- [ ] JS_TAG_FUNCTION checks → js_is_function()
|
||||
- [ ] JS_TAG_STRING checks (some already use JS_IsString)
|
||||
|
||||
### 8.5 Remaining Work
|
||||
- [ ] Fix ASAN memory corruption error (attempting free on address not malloc'd)
|
||||
- Crash occurs in js_def_realloc during js_realloc_array
|
||||
- Address is 112 bytes inside a JSFunctionDef allocation
|
||||
- [ ] Complete remaining ~200 tag check migrations
|
||||
- [ ] Add mist_hdr to JSFunction (optional, gc_obj_type already works)
|
||||
- [ ] Remove obsolete tag definitions from quickjs.h:
|
||||
- JS_TAG_STRING = -8
|
||||
- JS_TAG_ARRAY = -6
|
||||
- JS_TAG_FUNCTION = -5
|
||||
- JS_TAG_FUNCTION_BYTECODE = -2
|
||||
- JS_TAG_OBJECT = -1
|
||||
|
||||
### Current Status
|
||||
|
||||
**Build: SUCCEEDS** with warnings
|
||||
|
||||
**Runtime: CRASHES** with ASAN error:
|
||||
```
|
||||
==16122==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed
|
||||
```
|
||||
The crash occurs during test execution in `js_def_realloc` called from `js_realloc_array`.
|
||||
Root cause not yet identified - likely a pointer being passed to realloc that wasn't allocated with malloc.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- JSVarDef.var_name is JSValue
|
||||
- JSClosureVar.var_name is JSValue
|
||||
- JSGlobalVar.var_name is JSValue
|
||||
- JSFunctionDef.func_name is JSValue
|
||||
- BlockEnv.label_name is JSValue
|
||||
- OP_get_field/put_field/define_field already use cpool index format
|
||||
- JSRecord with open addressing is fully implemented
|
||||
- js_key_hash and js_key_equal work with both immediate and heap text
|
||||
- js_key_equal_str enables comparison with C string literals for internal names
|
||||
@@ -135,7 +135,6 @@ JSC_SCALL(file_listfiles,
|
||||
JSValue arr = JS_NewArray(js);
|
||||
struct listfiles_ctx ctx = { js, arr, 0 };
|
||||
if (pd_file->listfiles(str, listfiles_cb, &ctx, showhidden) != 0) {
|
||||
JS_FreeValue(js, arr);
|
||||
ret = JS_NULL;
|
||||
} else {
|
||||
ret = arr;
|
||||
|
||||
@@ -112,9 +112,6 @@ static void encode_js_object(json_encoder *enc, JSContext *js, JSValue obj) {
|
||||
JSValue val = JS_GetProperty(js, obj, props[i].atom);
|
||||
enc->addTableMember(enc, key, strlen(key));
|
||||
encode_js_value(enc, js, val);
|
||||
JS_FreeValue(js, val);
|
||||
JS_FreeCString(js, key);
|
||||
JS_FreeAtom(js, props[i].atom);
|
||||
}
|
||||
js_free_rt(props);
|
||||
}
|
||||
@@ -125,12 +122,10 @@ static void encode_js_array(json_encoder *enc, JSContext *js, JSValue arr) {
|
||||
enc->startArray(enc);
|
||||
JSValue lenVal = JS_GetPropertyStr(js, arr, "length");
|
||||
int len = (int)js2number(js, lenVal);
|
||||
JS_FreeValue(js, lenVal);
|
||||
for (int i = 0; i < len; i++) {
|
||||
enc->addArrayMember(enc);
|
||||
JSValue val = JS_GetPropertyNumber(js, arr, i);
|
||||
encode_js_value(enc, js, val);
|
||||
JS_FreeValue(js, val);
|
||||
}
|
||||
enc->endArray(enc);
|
||||
}
|
||||
@@ -149,7 +144,6 @@ static void encode_js_value(json_encoder *enc, JSContext *js, JSValue val) {
|
||||
size_t len;
|
||||
const char *str = JS_ToCStringLen(js, &len, val);
|
||||
enc->writeString(enc, str, len);
|
||||
JS_FreeCString(js, str);
|
||||
} else if (JS_IsArray(val)) {
|
||||
encode_js_array(enc, js, val);
|
||||
} else if (JS_IsObject(val)) {
|
||||
|
||||
@@ -30,9 +30,6 @@ static void add_score_cb(PDScore *score, const char *errorMessage) {
|
||||
args[0] = score_to_js(g_scoreboard_js, score);
|
||||
args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
|
||||
JSValue ret = JS_Call(g_scoreboard_js, g_add_score_callback, JS_NULL, 2, args);
|
||||
JS_FreeValue(g_scoreboard_js, ret);
|
||||
JS_FreeValue(g_scoreboard_js, args[0]);
|
||||
JS_FreeValue(g_scoreboard_js, args[1]);
|
||||
}
|
||||
|
||||
static void personal_best_cb(PDScore *score, const char *errorMessage) {
|
||||
@@ -41,9 +38,6 @@ static void personal_best_cb(PDScore *score, const char *errorMessage) {
|
||||
args[0] = score_to_js(g_scoreboard_js, score);
|
||||
args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
|
||||
JSValue ret = JS_Call(g_scoreboard_js, g_personal_best_callback, JS_NULL, 2, args);
|
||||
JS_FreeValue(g_scoreboard_js, ret);
|
||||
JS_FreeValue(g_scoreboard_js, args[0]);
|
||||
JS_FreeValue(g_scoreboard_js, args[1]);
|
||||
}
|
||||
|
||||
static void boards_list_cb(PDBoardsList *boards, const char *errorMessage) {
|
||||
@@ -65,9 +59,6 @@ static void boards_list_cb(PDBoardsList *boards, const char *errorMessage) {
|
||||
}
|
||||
args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
|
||||
JSValue ret = JS_Call(g_scoreboard_js, g_boards_list_callback, JS_NULL, 2, args);
|
||||
JS_FreeValue(g_scoreboard_js, ret);
|
||||
JS_FreeValue(g_scoreboard_js, args[0]);
|
||||
JS_FreeValue(g_scoreboard_js, args[1]);
|
||||
}
|
||||
|
||||
static void scores_cb(PDScoresList *scores, const char *errorMessage) {
|
||||
@@ -92,9 +83,6 @@ static void scores_cb(PDScoresList *scores, const char *errorMessage) {
|
||||
}
|
||||
args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
|
||||
JSValue ret = JS_Call(g_scoreboard_js, g_scores_callback, JS_NULL, 2, args);
|
||||
JS_FreeValue(g_scoreboard_js, ret);
|
||||
JS_FreeValue(g_scoreboard_js, args[0]);
|
||||
JS_FreeValue(g_scoreboard_js, args[1]);
|
||||
}
|
||||
|
||||
// --- API Functions ---
|
||||
@@ -104,8 +92,7 @@ JSC_SCALL(scoreboards_addScore,
|
||||
uint32_t value = (uint32_t)js2number(js, argv[1]);
|
||||
if (argc > 2 && JS_IsFunction(argv[2])) {
|
||||
g_scoreboard_js = js;
|
||||
JS_FreeValue(js, g_add_score_callback);
|
||||
g_add_score_callback = JS_DupValue(js, argv[2]);
|
||||
g_add_score_callback = argv[2];
|
||||
}
|
||||
ret = JS_NewBool(js, pd_scoreboards->addScore(str, value, add_score_cb));
|
||||
)
|
||||
@@ -114,8 +101,7 @@ JSC_SCALL(scoreboards_getPersonalBest,
|
||||
if (!pd_scoreboards) return JS_RaiseDisrupt(js, "scoreboards not initialized");
|
||||
if (argc > 1 && JS_IsFunction(argv[1])) {
|
||||
g_scoreboard_js = js;
|
||||
JS_FreeValue(js, g_personal_best_callback);
|
||||
g_personal_best_callback = JS_DupValue(js, argv[1]);
|
||||
g_personal_best_callback = argv[1];
|
||||
}
|
||||
ret = JS_NewBool(js, pd_scoreboards->getPersonalBest(str, personal_best_cb));
|
||||
)
|
||||
@@ -131,8 +117,7 @@ JSC_CCALL(scoreboards_getScoreboards,
|
||||
if (!pd_scoreboards) return JS_RaiseDisrupt(js, "scoreboards not initialized");
|
||||
if (argc > 0 && JS_IsFunction(argv[0])) {
|
||||
g_scoreboard_js = js;
|
||||
JS_FreeValue(js, g_boards_list_callback);
|
||||
g_boards_list_callback = JS_DupValue(js, argv[0]);
|
||||
g_boards_list_callback = argv[0];
|
||||
}
|
||||
return JS_NewBool(js, pd_scoreboards->getScoreboards(boards_list_cb));
|
||||
)
|
||||
@@ -147,8 +132,7 @@ JSC_SCALL(scoreboards_getScores,
|
||||
if (!pd_scoreboards) return JS_RaiseDisrupt(js, "scoreboards not initialized");
|
||||
if (argc > 1 && JS_IsFunction(argv[1])) {
|
||||
g_scoreboard_js = js;
|
||||
JS_FreeValue(js, g_scores_callback);
|
||||
g_scores_callback = JS_DupValue(js, argv[1]);
|
||||
g_scores_callback = argv[1];
|
||||
}
|
||||
ret = JS_NewBool(js, pd_scoreboards->getScores(str, scores_cb));
|
||||
)
|
||||
|
||||
@@ -324,7 +324,6 @@ void script_startup(JSContext *js)
|
||||
js->actor_label = js->name; /* may be NULL; updated when name is set */
|
||||
JS_SetHeapMemoryLimit(js, ACTOR_MEMORY_LIMIT);
|
||||
|
||||
JS_FreeValue(js, js_core_blob_use(js));
|
||||
|
||||
// Try engine fast-path: load engine.cm from source-hash cache
|
||||
size_t bin_size;
|
||||
@@ -390,8 +389,8 @@ void script_startup(JSContext *js)
|
||||
|
||||
js->actor_sym_ref.val = JS_NewObject(js);
|
||||
JS_CellStone(js, js->actor_sym_ref.val);
|
||||
JS_SetActorSym(js, JS_DupValue(js, js->actor_sym_ref.val));
|
||||
JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, js->actor_sym_ref.val));
|
||||
JS_SetActorSym(js, js->actor_sym_ref.val);
|
||||
JS_SetPropertyStr(js, env_ref.val, "actorsym", js->actor_sym_ref.val);
|
||||
|
||||
// Always set init (even if null)
|
||||
if (js->init_wota) {
|
||||
@@ -621,11 +620,10 @@ int cell_init(int argc, char **argv)
|
||||
|
||||
ctx->actor_sym_ref.val = JS_NewObject(ctx);
|
||||
JS_CellStone(ctx, ctx->actor_sym_ref.val);
|
||||
JS_SetActorSym(ctx, JS_DupValue(ctx, ctx->actor_sym_ref.val));
|
||||
JS_SetActorSym(ctx, ctx->actor_sym_ref.val);
|
||||
|
||||
root_ctx = ctx;
|
||||
|
||||
JS_FreeValue(ctx, js_core_blob_use(ctx));
|
||||
|
||||
int exit_code = 0;
|
||||
int use_native_engine = 0;
|
||||
@@ -670,7 +668,7 @@ int cell_init(int argc, char **argv)
|
||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "core_path", btmp);
|
||||
btmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
|
||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "shop_path", btmp);
|
||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "actorsym", JS_DupValue(ctx, ctx->actor_sym_ref.val));
|
||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "actorsym", ctx->actor_sym_ref.val);
|
||||
btmp = js_core_json_use(ctx);
|
||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "json", btmp);
|
||||
if (native_mode)
|
||||
@@ -734,7 +732,7 @@ int cell_init(int argc, char **argv)
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp);
|
||||
tmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "shop_path", tmp);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, ctx->actor_sym_ref.val));
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "actorsym", ctx->actor_sym_ref.val);
|
||||
tmp = js_core_json_use(ctx);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
|
||||
if (native_mode || !warn_mode || eval_script) {
|
||||
|
||||
@@ -280,14 +280,6 @@ static inline JS_BOOL JS_IsTrue(JSValue v) { return v == JS_TRUE; }
|
||||
static inline JS_BOOL JS_IsFalse(JSValue v) { return v == JS_FALSE; }
|
||||
JS_BOOL JS_IsActor(JSContext *ctx, JSValue val);
|
||||
|
||||
/* ============================================================
|
||||
GC References — no-ops with copying GC
|
||||
============================================================ */
|
||||
#define JS_DupValue(ctx, v) (v)
|
||||
#define JS_FreeValue(ctx, v) ((void)0)
|
||||
#define JS_DupValueRT(rt, v) (v)
|
||||
#define JS_FreeValueRT(rt, v) ((void)0)
|
||||
|
||||
/* ============================================================
|
||||
C Function Typedefs
|
||||
============================================================ */
|
||||
@@ -1102,19 +1094,16 @@ JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \
|
||||
// Safe integer property extraction (null → 0)
|
||||
#define JS_GETINT(JS, TARGET, VALUE, PROP) { \
|
||||
JSValue __##PROP##__v = JS_GetPropertyStr(JS, VALUE, #PROP); \
|
||||
TARGET = JS_IsNull(__##PROP##__v) ? 0 : (int)js2number(JS, __##PROP##__v); \
|
||||
JS_FreeValue(JS, __##PROP##__v); }
|
||||
TARGET = JS_IsNull(__##PROP##__v) ? 0 : (int)js2number(JS, __##PROP##__v); }
|
||||
|
||||
// Common macros for property access
|
||||
#define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\
|
||||
JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \
|
||||
TARGET = js2##TYPE(JS, __##PROP##__v); \
|
||||
JS_FreeValue(JS,__##PROP##__v); }\
|
||||
TARGET = js2##TYPE(JS, __##PROP##__v); }\
|
||||
|
||||
#define JS_GETATOM(JS, TARGET, VALUE, ATOM, TYPE) {\
|
||||
JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#ATOM); \
|
||||
TARGET = js2##TYPE(JS, __##PROP##__v); \
|
||||
JS_FreeValue(JS,__##PROP##__v); }\
|
||||
TARGET = js2##TYPE(JS, __##PROP##__v); }\
|
||||
|
||||
/* ============================================================
|
||||
Enum Mapping System
|
||||
|
||||
@@ -307,7 +307,6 @@ void *timer_thread_func(void *arg) {
|
||||
JSValue cb = t.actor->timers[idx].value;
|
||||
hmdel(t.actor->timers, t.timer_id);
|
||||
actor_clock(t.actor, cb);
|
||||
JS_FreeValue(t.actor, cb);
|
||||
}
|
||||
pthread_mutex_unlock(t.actor->msg_mutex);
|
||||
}
|
||||
@@ -471,7 +470,6 @@ void actor_free(JSContext *actor)
|
||||
pthread_mutex_lock(actor->mutex);
|
||||
|
||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||
JS_FreeValue(actor, actor->timers[i].value);
|
||||
}
|
||||
|
||||
hmfree(actor->timers);
|
||||
@@ -481,7 +479,6 @@ void actor_free(JSContext *actor)
|
||||
if (actor->letters[i].type == LETTER_BLOB) {
|
||||
blob_destroy(actor->letters[i].blob_data);
|
||||
} else if (actor->letters[i].type == LETTER_CALLBACK) {
|
||||
JS_FreeValue(actor, actor->letters[i].callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -912,23 +909,19 @@ void actor_turn(JSContext *actor)
|
||||
if (JS_IsSuspended(result)) {
|
||||
actor->vm_suspended = 1;
|
||||
actor->state = ACTOR_SLOW;
|
||||
JS_FreeValue(actor, arg);
|
||||
goto ENDTURN_SLOW;
|
||||
}
|
||||
if (!uncaught_exception(actor, result))
|
||||
actor->disrupt = 1;
|
||||
JS_FreeValue(actor, arg);
|
||||
} else if (l.type == LETTER_CALLBACK) {
|
||||
result = JS_Call(actor, l.callback, JS_NULL, 0, NULL);
|
||||
if (JS_IsSuspended(result)) {
|
||||
actor->vm_suspended = 1;
|
||||
actor->state = ACTOR_SLOW;
|
||||
JS_FreeValue(actor, l.callback);
|
||||
goto ENDTURN_SLOW;
|
||||
}
|
||||
if (!uncaught_exception(actor, result))
|
||||
actor->disrupt = 1;
|
||||
JS_FreeValue(actor, l.callback);
|
||||
}
|
||||
|
||||
if (actor->disrupt) goto ENDTURN;
|
||||
@@ -1017,7 +1010,7 @@ void actor_clock(JSContext *actor, JSValue fn)
|
||||
{
|
||||
letter l;
|
||||
l.type = LETTER_CALLBACK;
|
||||
l.callback = JS_DupValue(actor, fn);
|
||||
l.callback = fn;
|
||||
pthread_mutex_lock(actor->msg_mutex);
|
||||
arrput(actor->letters, l);
|
||||
pthread_mutex_unlock(actor->msg_mutex);
|
||||
@@ -1031,7 +1024,7 @@ uint32_t actor_delay(JSContext *actor, JSValue fn, double seconds)
|
||||
static uint32_t global_timer_id = 1;
|
||||
uint32_t id = global_timer_id++;
|
||||
|
||||
JSValue cb = JS_DupValue(actor, fn);
|
||||
JSValue cb = fn;
|
||||
hmput(actor->timers, id, cb);
|
||||
|
||||
uint64_t now = cell_ns();
|
||||
|
||||
@@ -117,7 +117,6 @@ void actor_free(JSContext *actor)
|
||||
}
|
||||
|
||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||
JS_FreeValue(actor, actor->timers[i].value);
|
||||
}
|
||||
|
||||
hmfree(actor->timers);
|
||||
@@ -127,7 +126,6 @@ void actor_free(JSContext *actor)
|
||||
if (actor->letters[i].type == LETTER_BLOB) {
|
||||
blob_destroy(actor->letters[i].blob_data);
|
||||
} else if (actor->letters[i].type == LETTER_CALLBACK) {
|
||||
JS_FreeValue(actor, actor->letters[i].callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +287,6 @@ void actor_loop()
|
||||
JSValue cb = t.actor->timers[idx].value;
|
||||
hmdel(t.actor->timers, t.timer_id);
|
||||
actor_clock(t.actor, cb);
|
||||
JS_FreeValue(t.actor, cb);
|
||||
}
|
||||
}
|
||||
continue; // Loop again
|
||||
@@ -385,11 +382,9 @@ void actor_turn(JSContext *actor)
|
||||
blob_destroy(l.blob_data);
|
||||
result = JS_Call(actor, actor->message_handle_ref.val, JS_NULL, 1, &arg);
|
||||
uncaught_exception(actor, result);
|
||||
JS_FreeValue(actor, arg);
|
||||
} else if (l.type == LETTER_CALLBACK) {
|
||||
result = JS_Call(actor, l.callback, JS_NULL, 0, NULL);
|
||||
uncaught_exception(actor, result);
|
||||
JS_FreeValue(actor, l.callback);
|
||||
}
|
||||
|
||||
if (actor->disrupt) goto ENDTURN;
|
||||
@@ -405,7 +400,7 @@ void actor_clock(JSContext *actor, JSValue fn)
|
||||
{
|
||||
letter l;
|
||||
l.type = LETTER_CALLBACK;
|
||||
l.callback = JS_DupValue(actor, fn);
|
||||
l.callback = fn;
|
||||
arrput(actor->letters, l);
|
||||
set_actor_state(actor);
|
||||
}
|
||||
@@ -415,7 +410,7 @@ uint32_t actor_delay(JSContext *actor, JSValue fn, double seconds)
|
||||
static uint32_t global_timer_id = 1;
|
||||
uint32_t id = global_timer_id++;
|
||||
|
||||
JSValue cb = JS_DupValue(actor, fn);
|
||||
JSValue cb = fn;
|
||||
hmput(actor->timers, id, cb);
|
||||
|
||||
uint64_t now = cell_ns();
|
||||
|
||||
Reference in New Issue
Block a user