48 Commits

Author SHA1 Message Date
John Alanbrook
fb36832f7e BROKEN: unified stack frames 2026-02-04 08:09:00 -06:00
John Alanbrook
19576533d9 no more global obj; eval w/ env 2026-02-03 23:07:30 -06:00
John Alanbrook
c08249b6f1 clean up var ref 2026-02-03 22:01:42 -06:00
John Alanbrook
dc348d023f optimize lref 2026-02-03 21:37:17 -06:00
John Alanbrook
fd5e4d155e varref cleanup 2026-02-03 20:09:35 -06:00
John Alanbrook
e734353722 Begin removal of varref 2026-02-03 19:13:17 -06:00
John Alanbrook
94f1645be1 new closure model 2026-02-03 19:04:38 -06:00
John Alanbrook
41e3a6d91a closures work 2026-02-03 18:42:14 -06:00
John Alanbrook
04c569eab1 fix parser 2026-02-03 18:24:06 -06:00
John Alanbrook
dc3c474b3a link 2026-02-03 18:01:31 -06:00
John Alanbrook
f1117bbd41 get_global 2026-02-03 17:35:24 -06:00
John Alanbrook
43faad95e0 new opcodes 2026-02-03 17:21:41 -06:00
John Alanbrook
acc9878b36 bytecode dump 2026-02-03 17:08:21 -06:00
John Alanbrook
03c45ee8b0 lexer 2026-02-03 16:59:20 -06:00
John Alanbrook
a171a0d2af -e flag run script 2026-02-03 16:06:18 -06:00
John Alanbrook
bb8d3930b3 fix many internals wrt gc 2026-02-03 06:55:26 -06:00
John Alanbrook
522ae6128a add FORCE_GC_AT_MALLOC logic 2026-02-03 03:00:46 -06:00
John Alanbrook
16c26e4bf2 fix object fwd growth 2026-02-03 02:53:06 -06:00
John Alanbrook
a9804785e0 fix gc 2026-02-03 02:44:52 -06:00
John Alanbrook
11ae703693 suite.c roots 2026-02-03 02:40:40 -06:00
John Alanbrook
f203278c3e more gc correctness 2026-02-03 02:31:14 -06:00
John Alanbrook
a80557283a fix poison heap 2026-02-03 02:06:29 -06:00
John Alanbrook
3e40885e07 extensive debugging; fixed forwarding error 2026-02-03 01:50:30 -06:00
John Alanbrook
e4b7de46f6 add function aware to gc 2026-02-03 01:20:24 -06:00
John Alanbrook
e680439a9b gc asan poison 2026-02-03 01:03:23 -06:00
John Alanbrook
ae11504e00 copy gc 2026-02-03 00:34:07 -06:00
John Alanbrook
893deaec23 suite.c all works 2026-02-02 23:39:12 -06:00
John Alanbrook
69b032d3dc rm gc 2026-02-02 23:02:50 -06:00
John Alanbrook
256a00c501 fix mem errors 2026-02-02 22:54:38 -06:00
John Alanbrook
8e166b8f98 gc aware 2026-02-02 22:46:07 -06:00
John Alanbrook
ddbdd00496 update text and object to be gc aware 2026-02-02 22:37:42 -06:00
John Alanbrook
bdf0461e1f extensive C testing 2026-02-02 21:26:46 -06:00
John Alanbrook
0b86af1d4c array gc 2026-02-02 20:33:11 -06:00
John Alanbrook
beac9608ea chase 2026-02-02 18:54:15 -06:00
John Alanbrook
a04bebd0d7 fix str 2026-02-02 10:38:48 -06:00
John Alanbrook
ce74f726dd compiles + runs 2026-02-02 10:24:02 -06:00
John Alanbrook
4d1ab60852 rm notion of object values 2026-02-02 09:54:36 -06:00
John Alanbrook
22ab6c8098 rm gc fns 2026-02-02 09:22:51 -06:00
John Alanbrook
9a9775690f rm function proto 2026-02-02 08:35:14 -06:00
John Alanbrook
be71ae3bba remove **Free type functions 2026-02-02 08:32:11 -06:00
John Alanbrook
f2a76cbb55 fix number rep 2026-02-02 07:56:53 -06:00
John Alanbrook
2d834c37b3 simplify eq 2026-02-02 06:56:46 -06:00
John Alanbrook
ba1b92aa78 rm freevalue and dupvalue 2026-02-02 06:27:08 -06:00
John Alanbrook
c356fe462d compiles 2026-02-02 06:15:10 -06:00
John Alanbrook
b23b918f97 compiles 2026-02-01 23:03:01 -06:00
John Alanbrook
e720152bcd gc plan 2026-02-01 20:58:42 -06:00
John Alanbrook
f093e6f5a3 reformat 2026-02-01 19:36:20 -06:00
John Alanbrook
4fc904de63 rm 2026-02-01 19:33:19 -06:00
16 changed files with 8883 additions and 7018 deletions

View File

@@ -10,6 +10,10 @@ AllowShortFunctionsOnASingleLine: true
AllowShortBlocksOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
BreakBeforeBraces: Attach
ColumnLimit: 0
BreakFunctionDefinitionParameters: false
BinPackParameters: false
BinPackArguments: false
# --- Fix the "static T\nname(...)" style ---
AlwaysBreakAfterDefinitionReturnType: None

View File

@@ -58,7 +58,7 @@ static:
# Bootstrap: build cell from scratch using meson (only needed once)
# Also installs core scripts to ~/.cell/core
bootstrap:
meson setup build_bootstrap -Dbuildtype=debugoptimized -Db_sanitize=address
meson setup build_bootstrap -Dbuildtype=debug -Db_sanitize=address
meson compile -C build_bootstrap
cp build_bootstrap/cell .
cp build_bootstrap/libcell_runtime.dylib .

View File

@@ -8,14 +8,14 @@ static JSClassID js_writer_class_id;
static void js_reader_finalizer(JSRuntime *rt, JSValue val) {
mz_zip_archive *zip = JS_GetOpaque(val, js_reader_class_id);
mz_zip_reader_end(zip);
js_free_rt(rt,zip);
js_free_rt(zip);
}
static void js_writer_finalizer(JSRuntime *rt, JSValue val) {
mz_zip_archive *zip = JS_GetOpaque(val, js_writer_class_id);
mz_zip_writer_finalize_archive(zip);
mz_zip_writer_end(zip);
js_free_rt(rt,zip);
js_free_rt(zip);
}
static JSClassDef js_reader_class = {

View File

@@ -1,8 +1,6 @@
#include "cell.h"
JSC_CCALL(os_gc, JS_RunGC(JS_GetRuntime(js)) )
JSC_CCALL(os_mem_limit, JS_SetMemoryLimit(JS_GetRuntime(js), js2number(js,argv[0])))
JSC_CCALL(os_gc_threshold, JS_SetGCThreshold(JS_GetRuntime(js), js2number(js,argv[0])))
JSC_CCALL(os_max_stacksize, JS_SetMaxStackSize(JS_GetRuntime(js), js2number(js,argv[0])))
// Compute the approximate size of a single JS value in memory.
@@ -91,9 +89,7 @@ JSC_CCALL(js_fn_info,
static const JSCFunctionListEntry js_js_funcs[] = {
MIST_FUNC_DEF(os, calc_mem, 0),
MIST_FUNC_DEF(os, mem_limit, 1),
MIST_FUNC_DEF(os, gc_threshold, 1),
MIST_FUNC_DEF(os, max_stacksize, 1),
MIST_FUNC_DEF(os, gc, 0),
MIST_FUNC_DEF(os, eval, 2),
MIST_FUNC_DEF(js, compile, 2),
MIST_FUNC_DEF(js, eval_compile, 1),

View File

@@ -1,602 +1,403 @@
# GC Refactoring Plan: Cheney Copying Collector
# Plan: Complete Copying GC Implementation
## Overview
Replace the current reference-counting GC with a simple two-space Cheney copying collector.
This fundamentally simplifies the system: no more `JSGCObjectHeader`, no ref counts,
no cycle detection - just bump allocation and copying live objects when memory fills up.
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.
## Architecture
## Target Architecture (from docs/memory.md)
```
JSRuntime (256 MB pool)
├── Buddy allocator for block management
├── Class definitions (shared across contexts)
└── JSContext #1 (actor)
├── Current block (64KB initially)
├── heap_base: start of block
├── heap_free: bump pointer (total used = heap_free - heap_base)
├── Text interning table (per-context, rebuilt on GC)
└── On memory pressure: request new block, copy live objects, return old block
```
### Object Types (simplified from current):
**Key principle**: Each JSContext (actor) owns its own memory. Nothing is stored in JSRuntime
except the buddy allocator and class definitions. There is no runtime-level string arena.
**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
## Memory Model (from docs/memory.md)
### 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
### Object Header (objhdr_t - 64 bits)
```
[56 bits: capacity] [1 bit: R flag] [3 bits: reserved] [1 bit: stone] [3 bits: type]
```
## Current State (needs refactoring)
All heap objects start with just `objhdr_t`. No `JSGCObjectHeader`, no ref counts.
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
### Object Types
- 0: OBJ_ARRAY - Header, Length, Elements[]
- 1: OBJ_BLOB - Header, Length (bits), BitWords[]
- 2: OBJ_TEXT - Header, Length/Hash, PackedChars[] (see Text section below)
- 3: OBJ_RECORD - Header, Prototype, Length, Key/Value pairs
- 4: OBJ_FUNCTION - Header, Code, Outer (always stone, 3 words)
- 5: OBJ_CODE - Header, Arity, Size, ClosureSize, Entry, Disruption (in context memory)
- 6: OBJ_FRAME - Header, Function, Caller, ReturnAddr, Slots[]
- 7: OBJ_FORWARD - Forwarding pointer (used during GC)
## Implementation Steps
### Text (Type 2) - Two Modes
### Phase 1: Define No-Op DupValue/FreeValue (To Enable Compilation)
Text has two forms depending on the stone bit:
**Pretext (stone=0)**: Mutable intermediate representation
- `capacity` = max chars it can hold
- `length` word = actual number of characters
- Used during string building/concatenation
**Text (stone=1)**: Immutable user-facing string
- `capacity` = length (they're equal for stoned text)
- `length` word = hash (for record key lookup)
- All text keys in records must be stoned
- Text literals are stoned and interned
Add these near line 100 in `source/quickjs.c`:
```c
typedef struct {
objhdr_t hdr; /* type=OBJ_TEXT, cap=capacity, s=stone bit */
uint64_t len_or_hash; /* length if pretext (s=0), hash if text (s=1) */
uint64_t packed[]; /* 2 UTF32 chars per word */
/* 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;
```
**DELETE JSString** - just use JSText (currently named mist_text).
### Phase 3: Complete gc_object_size for All Types
### Text Interning (Per-Context)
Each context maintains its own text interning table:
- Texts used as record keys are stoned and interned
- Text literals are stoned and interned
- During GC, a new interning table is built as live objects are copied
- This prevents the table from becoming a graveyard
Update `gc_object_size` (line 1850) to read header at offset 0:
```c
/* In JSContext */
JSText **text_intern_array; /* indexed by ID */
uint32_t *text_intern_hash; /* hash table mapping to IDs */
uint32_t text_intern_count; /* number of interned texts */
uint32_t text_intern_size; /* hash table size */
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
## Phase 1: Add Buddy Allocator to JSRuntime [DONE]
Update `gc_scan_object` (line 1924):
### File: source/quickjs.c
**1.1 Add buddy allocator structures**
```c
#define BUDDY_MIN_ORDER 16 /* 64KB minimum block */
#define BUDDY_MAX_ORDER 28 /* 256MB maximum */
#define BUDDY_LEVELS (BUDDY_MAX_ORDER - BUDDY_MIN_ORDER + 1)
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);
typedef struct BuddyBlock {
struct BuddyBlock *next;
struct BuddyBlock *prev;
uint8_t order; /* log2 of size */
uint8_t is_free;
} BuddyBlock;
typedef struct BuddyAllocator {
uint8_t *base; /* 256MB base address */
size_t total_size; /* 256MB */
BuddyBlock *free_lists[BUDDY_LEVELS];
} BuddyAllocator;
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;
}
}
```
**1.2 Update JSRuntime**
### Phase 5: Fix gc_copy_value Forwarding
Update `gc_copy_value` (line 1883) for offset 0 headers:
```c
struct JSRuntime {
BuddyAllocator buddy;
int class_count;
JSClass *class_array;
struct list_head context_list;
/* REMOVE: gc_obj_list, gc_zero_ref_count_list, gc_phase, malloc_gc_threshold */
/* REMOVE: malloc functions, malloc_state - contexts use bump allocation */
};
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);
}
```
**1.3 Implement buddy functions** [DONE]
- `buddy_init(BuddyAllocator *b)` - allocate 256MB, initialize free lists
- `buddy_alloc(BuddyAllocator *b, size_t size)` - allocate block of given size
- `buddy_free(BuddyAllocator *b, void *ptr, size_t size)` - return block
- `buddy_destroy(BuddyAllocator *b)` - free the 256MB
### Phase 6: Complete GC Root Tracing
---
Update `ctx_gc` (line 1966) to trace all roots including JSGCRef:
## Phase 2: Restructure JSContext for Bump Allocation [DONE]
### File: source/quickjs.c
**2.1 Update JSContext**
```c
struct JSContext {
JSRuntime *rt;
struct list_head link;
static int ctx_gc(JSContext *ctx) {
// ... existing setup code ...
/* Actor memory block - bump allocation */
uint8_t *heap_base; /* start of current block */
uint8_t *heap_free; /* bump pointer */
uint8_t *heap_end; /* end of block */
size_t current_block_size; /* 64KB initially */
size_t next_block_size; /* doubles if <10% recovered */
// 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 ...
/* Stack (VM execution) */
JSValue *value_stack;
int value_stack_top;
int value_stack_capacity;
struct VMFrame *frame_stack;
int frame_stack_top;
int frame_stack_capacity;
// 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);
}
/* Roots */
JSValue global_obj;
JSValue *class_proto;
JSValue current_exception;
// 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);
}
/* Text interning (per-context, rebuilt on GC) */
JSText **text_intern_array;
uint32_t *text_intern_hash;
uint32_t text_intern_count;
uint32_t text_intern_size;
// Copy current exception
ctx->current_exception = gc_copy_value(ctx, ctx->current_exception, &to_free, to_end);
/* Other context state */
uint16_t class_count;
int interrupt_counter;
void *user_opaque;
/* REMOVE: JSGCObjectHeader header */
};
// Cheney scan (existing)
// ...
}
```
**2.2 Implement bump allocator** [DONE]
### Phase 7: Trigger GC on Allocation Failure
Update `js_malloc` (line 1495):
```c
static void *ctx_alloc(JSContext *ctx, size_t size) {
size = (size + 7) & ~7; /* 8-byte align */
if (ctx->heap_free + size > ctx->heap_end) {
if (ctx_gc(ctx) < 0) return NULL; /* triggers GC */
if (ctx->heap_free + size > ctx->heap_end) {
return NULL; /* still OOM after GC */
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 += size;
ctx->heap_free = (uint8_t*)ctx->heap_free + size;
return ptr;
}
```
---
### Phase 8: Frame Reduction (for closures)
## Phase 3: Unify Object Headers (Remove JSGCObjectHeader)
### 3.1 New unified object layout
All heap objects start with just `objhdr_t`:
When a function returns, "reduce" its frame to just closure variables:
```c
/* Array */
typedef struct {
objhdr_t hdr; /* type=OBJ_ARRAY, cap=element_capacity */
uint64_t len;
JSValue elem[];
} JSArray;
static void reduce_frame(JSContext *ctx, JSFrame *frame) {
if (frame->caller == NULL) return; // Already reduced
/* Text (replaces both mist_text and JSString) */
typedef struct {
objhdr_t hdr; /* type=OBJ_TEXT, cap=capacity, s=stone bit */
uint64_t len_or_hash; /* len if pretext (s=0), hash if text (s=1) */
uint64_t packed[]; /* 2 UTF32 chars per word */
} JSText;
JSCode *code = frame->function->code;
uint32_t closure_size = code->closure_size;
/* Record (object) */
typedef struct JSRecord {
objhdr_t hdr; /* type=OBJ_RECORD, cap=mask, s=stone bit */
struct JSRecord *proto;
uint64_t len;
uint64_t tombs;
uint16_t class_id;
uint16_t _pad;
uint32_t rec_id; /* for record-as-key hashing */
JSValue slots[]; /* key[0], val[0], key[1], val[1], ... */
} JSRecord;
/* Function */
typedef struct {
objhdr_t hdr; /* type=OBJ_FUNCTION, always stone */
JSValue code; /* pointer to code object */
JSValue outer; /* pointer to enclosing frame */
} JSFunction;
/* Frame */
typedef struct {
objhdr_t hdr; /* type=OBJ_FRAME, cap=slot_count */
JSValue function; /* JSFunction */
JSValue caller; /* JSFrame or null */
uint64_t return_addr;
JSValue slots[]; /* args, locals, temporaries */
} JSFrame;
/* Code (in context memory, always stone) */
typedef struct {
objhdr_t hdr; /* type=OBJ_CODE, always stone */
uint32_t arity;
uint32_t frame_size;
uint32_t closure_size;
uint64_t entry_point;
uint64_t disruption_point;
uint8_t bytecode[];
} JSCode;
```
### 3.2 Delete legacy types
**DELETE:**
- `JSString` struct - use `JSText` instead
- `JSGCObjectHeader` struct
- `mist_text` - rename to `JSText`
- All `p->header.ref_count` accesses
- All `p->header.gc_obj_type` accesses
- `JS_GC_OBJ_TYPE_*` enum values
- `add_gc_object()`, `remove_gc_object()` functions
- `js_alloc_string_rt()` - no runtime-level string allocation
---
## Phase 4: Implement Cheney Copying GC [DONE - needs update]
### 4.1 Core GC function
```c
static int ctx_gc(JSContext *ctx) {
size_t old_used = ctx->heap_free - ctx->heap_base;
/* Request new block from runtime */
size_t new_size = ctx->next_block_size;
uint8_t *new_block = buddy_alloc(&ctx->rt->buddy, new_size);
if (!new_block) return -1;
uint8_t *to_base = new_block;
uint8_t *to_free = new_block;
uint8_t *to_end = new_block + new_size;
/* Reset text interning table (will be rebuilt during copy) */
ctx->text_intern_count = 0;
memset(ctx->text_intern_hash, 0, ctx->text_intern_size * sizeof(uint32_t));
/* Copy roots */
ctx->global_obj = gc_copy_value(ctx, ctx->global_obj, &to_free, to_end);
ctx->current_exception = gc_copy_value(ctx, ctx->current_exception, &to_free, to_end);
for (int i = 0; i < ctx->class_count; i++) {
ctx->class_proto[i] = gc_copy_value(ctx, ctx->class_proto[i], &to_free, to_end);
}
/* Copy stack */
for (int i = 0; i < ctx->value_stack_top; i++) {
ctx->value_stack[i] = gc_copy_value(ctx, ctx->value_stack[i], &to_free, to_end);
}
/* Cheney scan: process copied objects */
uint8_t *scan = to_base;
while (scan < to_free) {
gc_scan_object(ctx, scan, &to_free, to_end);
scan += gc_object_size(scan);
}
/* Return old block */
buddy_free(&ctx->rt->buddy, ctx->heap_base, ctx->current_block_size);
/* Update context */
size_t new_used = to_free - to_base;
size_t recovered = old_used - new_used;
ctx->heap_base = to_base;
ctx->heap_free = to_free;
ctx->heap_end = to_end;
ctx->current_block_size = new_size;
/* If <10% recovered, double next block size */
if (recovered < old_used / 10) {
ctx->next_block_size = new_size * 2;
}
return 0;
// 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
}
```
### 4.2 Copy functions per type
### Phase 9: Remove Unused Reference Counting Code
```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 */
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
void *ptr = JS_VALUE_GET_PTR(v);
objhdr_t hdr = *(objhdr_t *)ptr;
## Files to Modify
/* Already forwarded? */
if (objhdr_type(hdr) == OBJ_FORWARD) {
/* Extract forwarding address from cap56 field */
return JS_MKPTR(JS_TAG_PTR, (void *)(uintptr_t)objhdr_cap56(hdr));
}
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
/* Copy object */
size_t size = gc_object_size(ptr);
void *new_ptr = *to_free;
memcpy(new_ptr, ptr, size);
*to_free += size;
2. **source/quickjs.h** - Public API:
- Remove JSGCObjectHeader
- Update JSValue type checks if needed
- Ensure JS_IsStone works with offset 0 headers
/* Install forwarding pointer in old location */
*(objhdr_t *)ptr = objhdr_make((uint64_t)(uintptr_t)new_ptr, OBJ_FORWARD, 0, 0, 0, 0);
## Execution Order
/* If it's a stoned text, re-intern it */
if (objhdr_type(hdr) == OBJ_TEXT && objhdr_s(hdr)) {
gc_intern_text(ctx, (JSText *)new_ptr);
}
return JS_MKPTR(JS_TAG_PTR, new_ptr);
}
static void gc_scan_object(JSContext *ctx, void *ptr, uint8_t **to_free, uint8_t *to_end) {
objhdr_t hdr = *(objhdr_t *)ptr;
switch (objhdr_type(hdr)) {
case OBJ_ARRAY: {
JSArray *arr = ptr;
for (uint64_t i = 0; i < arr->len; i++) {
arr->elem[i] = gc_copy_value(ctx, arr->elem[i], to_free, to_end);
}
break;
}
case OBJ_RECORD: {
JSRecord *rec = ptr;
if (rec->proto) {
JSValue pv = JS_MKPTR(JS_TAG_PTR, rec->proto);
pv = gc_copy_value(ctx, pv, to_free, to_end);
rec->proto = (JSRecord *)JS_VALUE_GET_PTR(pv);
}
uint64_t mask = objhdr_cap56(hdr);
for (uint64_t i = 0; i <= mask; i++) {
rec->slots[i*2] = gc_copy_value(ctx, rec->slots[i*2], to_free, to_end);
rec->slots[i*2+1] = gc_copy_value(ctx, rec->slots[i*2+1], to_free, to_end);
}
break;
}
case OBJ_FUNCTION: {
JSFunction *fn = ptr;
fn->code = gc_copy_value(ctx, fn->code, to_free, to_end);
fn->outer = gc_copy_value(ctx, fn->outer, to_free, to_end);
break;
}
case OBJ_FRAME: {
JSFrame *fr = ptr;
fr->function = gc_copy_value(ctx, fr->function, to_free, to_end);
fr->caller = gc_copy_value(ctx, fr->caller, to_free, to_end);
uint64_t cap = objhdr_cap56(hdr);
for (uint64_t i = 0; i < cap; i++) {
fr->slots[i] = gc_copy_value(ctx, fr->slots[i], to_free, to_end);
}
break;
}
case OBJ_TEXT:
case OBJ_BLOB:
case OBJ_CODE:
/* No references to scan */
break;
}
}
```
---
## Phase 5: Remove Old GC Infrastructure
### Files: source/quickjs.c, source/quickjs.h
**Delete entirely:**
- `RC_TRACE` conditional code (~100 lines)
- `RcEvent` struct and `rc_log` array
- `rc_log_event`, `rc_trace_inc_gc`, `rc_trace_dec_gc`, `rc_dump_history`
- `gc_decref`, `gc_decref_child`, `gc_decref_child_dbg`, `gc_decref_child_edge`
- `gc_free_cycles`, `free_zero_refcount`, `free_gc_object`
- `add_gc_object`, `remove_gc_object`
- `JS_RunGCInternal`, `JS_GC_PHASE_*` enum
- `mark_children`, `gc_mark` (the old marking functions)
- `JSGCPhaseEnum`, `gc_phase` field in JSRuntime
- `gc_obj_list`, `gc_zero_ref_count_list` in JSRuntime
- `JSRefCountHeader` struct
- `js_alloc_string_rt` - no runtime string allocation
- Stone arena in runtime (`st_pages`, etc.) - each context has its own
**Update:**
- `JS_FreeValue` - becomes no-op (GC handles everything)
- `JS_DupValue` - becomes no-op (just return value)
- `__JS_FreeValueRT` - remove entirely
---
## Phase 6: Update Allocation Sites [IN PROGRESS]
### 6.1 Replace js_malloc with ctx_alloc
All object allocations change from:
```c
JSRecord *rec = js_mallocz(ctx, sizeof(JSRecord));
rec->tab = js_mallocz(ctx, sizeof(JSRecordEntry) * size);
```
to:
```c
size_t total = sizeof(JSRecord) + (mask+1) * 2 * sizeof(JSValue);
JSRecord *rec = ctx_alloc(ctx, total);
rec->hdr = objhdr_make(mask, OBJ_RECORD, false, false, false, false);
/* slots are inline after the struct */
```
### 6.2 Update object creation functions
- `JS_NewObject` - use ctx_alloc, set hdr, inline slots
- `JS_NewArray` - use ctx_alloc, set hdr, inline elements
- `JS_NewStringLen` - use ctx_alloc, create JSText
- `js_create_function` - use ctx_alloc
- String concatenation, array push, etc.
### 6.3 Delete js_malloc/js_free usage for heap objects
Keep `js_malloc_rt` only for:
- Class arrays (in runtime)
- VM stacks (external to GC'd heap)
- Temporary C allocations
---
## Phase 7: Delete JSString, Use JSText
### 7.1 Replace JSString with JSText
```c
/* OLD - DELETE */
struct JSString {
JSRefCountHeader header;
uint32_t pad;
objhdr_t hdr;
int64_t len;
uint64_t u[];
};
/* NEW - Single text type */
typedef struct {
objhdr_t hdr; /* type=OBJ_TEXT, cap=capacity, s=stone */
uint64_t len_or_hash; /* length if s=0, hash if s=1 */
uint64_t packed[]; /* 2 UTF32 chars per word */
} JSText;
```
### 7.2 Update string functions
- `js_alloc_string` -> allocate JSText via ctx_alloc
- Remove `js_alloc_string_rt` entirely
- `js_free_string` -> no-op (GC handles it)
- Update all JSString* to JSText*
### 7.3 Text stoning
When a text is used as a record key or becomes a literal:
1. Set stone bit: `hdr = objhdr_set_s(hdr, true)`
2. Compute hash and store in len_or_hash
3. Set capacity = length
4. Add to context's intern table
---
## Phase 8: Update Type Checks
Replace `JSGCObjectHeader.gc_obj_type` checks with `objhdr_type`:
```c
/* Old */
((JSGCObjectHeader *)ptr)->gc_obj_type == JS_GC_OBJ_TYPE_RECORD
/* New */
objhdr_type(*(objhdr_t *)ptr) == OBJ_RECORD
```
Update helper functions:
- `js_is_record(v)` - check `objhdr_type == OBJ_RECORD`
- `js_is_array(v)` - check `objhdr_type == OBJ_ARRAY`
- `js_is_function(v)` - check `objhdr_type == OBJ_FUNCTION`
- `JS_IsString(v)` - check `objhdr_type == OBJ_TEXT`
---
## Phase 9: Handle C Opaque Objects
Per docs/memory.md, C opaque objects need special handling:
**9.1 Track live opaque objects**
```c
typedef struct {
void *opaque;
JSClassID class_id;
uint8_t alive;
} OpaqueRef;
/* In JSContext */
OpaqueRef *opaque_refs;
int opaque_ref_count;
int opaque_ref_capacity;
```
**9.2 During GC**
1. When copying a JSRecord with opaque data, mark it alive in opaque_refs
2. After GC, iterate opaque_refs and call finalizer for those with `alive=0`
3. Clear all alive flags for next cycle
---
## File Changes Summary
### source/quickjs.c
- Remove ~800 lines: RC_TRACE, gc_decref, gc_free_cycles, JSGCObjectHeader, JSString, runtime string arena
- Add ~300 lines: buddy allocator, Cheney GC, JSText
- Modify ~400 lines: allocation sites, type checks, string handling
### source/quickjs.h
- Remove: JSGCObjectHeader, JSRefCountHeader from public API
- Remove: JS_MarkFunc, JS_MarkValue
- Update: JS_FreeValue, JS_DupValue to be no-ops
---
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. **Build**: `make` should compile without errors
2. **Basic test**: `./cell test suite` should pass
3. **Memory test**: Run with ASAN to verify no leaks or corruption
4. **GC trigger**: Test that GC runs when memory fills, objects survive correctly
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)
## Dependencies / Order of Work
1. Phase 1 (Buddy) - DONE
2. Phase 2 (JSContext bump alloc) - DONE
3. Phase 5 (Remove old GC) - Do this early to reduce conflicts
4. Phase 7 (Delete JSString) - Major cleanup
5. Phase 3 (Unify headers) - Depends on 5, 7
6. Phase 6 (Allocation sites) - Incremental, with Phase 3
7. Phase 4 (Cheney GC) - After Phases 3, 6
8. Phase 8 (Type checks) - With Phase 3
9. Phase 9 (Opaque) - Last, once basic GC works
---
## Notes
- Each context owns its memory - no runtime-level allocation except buddy blocks
- Text interning is per-context, rebuilt during GC
- OBJ_CODE lives in context memory (always stone)
- Frames use caller=null to signal returnable (can be shrunk during GC)
- Forward pointer type (7) used during GC to mark copied objects
- `heap_free - heap_base` = total memory used by context
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 ✓

View File

@@ -166,7 +166,7 @@ static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val, JSValueC
case JS_TAG_NULL:
nota_write_sym(&enc->nb, NOTA_NULL);
break;
case JS_TAG_OBJECT: {
case JS_TAG_PTR: {
if (js_is_blob(ctx, replaced)) {
size_t buf_len;
void *buf_data = js_get_blob_data(ctx, &buf_len, replaced);
@@ -178,7 +178,7 @@ static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val, JSValueC
break;
}
if (JS_IsArray(ctx, replaced)) {
if (JS_IsArray(replaced)) {
if (nota_stack_has(enc, replaced)) {
enc->cycle = 1;
break;

View File

@@ -40,6 +40,7 @@ sources = []
src += [ # core
'monocypher.c',
'cell.c',
'suite.c',
'wildmatch.c',
'qjs_actor.c',
'qjs_wota.c',

View File

@@ -150,7 +150,7 @@ static void encode_js_value(json_encoder *enc, JSContext *js, JSValue val) {
const char *str = JS_ToCStringLen(js, &len, val);
enc->writeString(enc, str, len);
JS_FreeCString(js, str);
} else if (JS_IsArray(js, val)) {
} else if (JS_IsArray(val)) {
encode_js_array(enc, js, val);
} else if (JS_IsObject(val)) {
encode_js_object(enc, js, val);

8
qop.c
View File

@@ -8,10 +8,10 @@ static void js_qop_archive_finalizer(JSRuntime *rt, JSValue val) {
qop_desc *qop = JS_GetOpaque(val, js_qop_archive_class_id);
if (qop) {
if (qop->hashmap) {
js_free_rt(rt, qop->hashmap);
js_free_rt(qop->hashmap);
}
qop_close(qop);
js_free_rt(rt, qop);
js_free_rt(qop);
}
}
@@ -34,8 +34,8 @@ static void js_qop_writer_finalizer(JSRuntime *rt, JSValue val) {
qop_writer *w = JS_GetOpaque(val, js_qop_writer_class_id);
if (w) {
if (w->fh) fclose(w->fh);
if (w->files) js_free_rt(rt, w->files);
js_free_rt(rt, w);
if (w->files) js_free_rt(w->files);
js_free_rt(w);
}
}

View File

@@ -19,6 +19,10 @@
#include <unistd.h>
#include <sys/stat.h>
/* Test suite declarations */
int run_c_test_suite(JSContext *ctx);
static int run_test_suite(size_t heap_size);
cell_rt *root_cell = NULL;
static char *core_path = NULL;
@@ -122,7 +126,7 @@ void script_startup(cell_rt *prt)
rt = JS_NewRuntime();
JSContext *js = JS_NewContextRaw(rt);
JS_SetInterruptHandler(rt, actor_interrupt_cb, prt);
JS_SetInterruptHandler(rt, (JSInterruptHandler *)actor_interrupt_cb, prt);
JS_AddIntrinsicBaseObjects(js);
JS_AddIntrinsicEval(js);
@@ -135,36 +139,57 @@ void script_startup(cell_rt *prt)
cell_rt *crt = JS_GetContextOpaque(js);
JS_FreeValue(js, js_blob_use(js));
JSValue globalThis = JS_GetGlobalObject(js);
/* Use GC refs to protect values during allocations.
Initialize values to JS_NULL before adding to GC list to avoid
scanning uninitialized memory. */
JSGCRef globalThis_ref, cell_ref, hidden_fn_ref;
globalThis_ref.val = JS_NULL;
cell_ref.val = JS_NULL;
hidden_fn_ref.val = JS_NULL;
JS_AddGCRef(js, &globalThis_ref);
JS_AddGCRef(js, &cell_ref);
JS_AddGCRef(js, &hidden_fn_ref);
JSValue cell = JS_NewObject(js);
JS_SetPropertyStr(js,globalThis,"cell", cell);
globalThis_ref.val = JS_GetGlobalObject(js);
cell_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, globalThis_ref.val, "cell", cell_ref.val);
JSValue hidden_fn = JS_NewObject(js);
hidden_fn_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, cell_ref.val, "hidden", hidden_fn_ref.val);
JS_SetPropertyStr(js, cell, "hidden", hidden_fn);
JS_SetPropertyStr(js, hidden_fn, "os", js_os_use(js));
/* Call functions that allocate BEFORE using their results as arguments,
to avoid GC invalidating already-evaluated arguments. */
JSGCRef tmp_ref;
tmp_ref.val = JS_NULL;
JS_AddGCRef(js, &tmp_ref);
tmp_ref.val = js_os_use(js);
JS_SetPropertyStr(js, hidden_fn_ref.val, "os", tmp_ref.val);
tmp_ref.val = JS_NewObject(js);
crt->actor_sym = tmp_ref.val;
tmp_ref.val = JS_DupValue(js, crt->actor_sym);
JS_SetPropertyStr(js, hidden_fn_ref.val, "actorsym", tmp_ref.val);
crt->actor_sym = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_fn, "actorsym", JS_DupValue(js,crt->actor_sym));
if (crt->init_wota) {
JS_SetPropertyStr(js, hidden_fn, "init", wota2value(js, crt->init_wota));
tmp_ref.val = wota2value(js, crt->init_wota);
JS_SetPropertyStr(js, hidden_fn_ref.val, "init", tmp_ref.val);
// init wota can now be freed
free(crt->init_wota);
crt->init_wota = NULL;
}
// Store the core path for scripts to use
JSValue js_cell = JS_GetPropertyStr(js, globalThis, "cell");
JSValue hidden = JS_GetPropertyStr(js, js_cell, "hidden");
if (core_path) {
JS_SetPropertyStr(js, hidden, "core_path", JS_NewString(js, core_path));
tmp_ref.val = JS_NewString(js, core_path);
JS_SetPropertyStr(js, hidden_fn_ref.val, "core_path", tmp_ref.val);
}
JS_FreeValue(js, hidden);
JS_FreeValue(js, js_cell);
JS_FreeValue(js, globalThis);
JS_DeleteGCRef(js, &tmp_ref);
JS_DeleteGCRef(js, &hidden_fn_ref);
JS_DeleteGCRef(js, &cell_ref);
JS_DeleteGCRef(js, &globalThis_ref);
// Load engine.cm from the core directory
size_t engine_size;
@@ -196,12 +221,174 @@ static void signal_handler(int sig)
}
#endif
if (!str) return;
exit_handler();
}
/* Run the C test suite with minimal runtime setup */
static int run_test_suite(size_t heap_size)
{
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
return 1;
}
JSContext *ctx = JS_NewContextRawWithHeapSize(rt, heap_size);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
return 1;
}
JS_AddIntrinsicBaseObjects(ctx);
int result = run_c_test_suite(ctx);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return result;
}
/* Run an immediate script string */
static int run_eval(const char *script_or_file, int print_bytecode)
{
if (!find_cell_shop()) return 1;
/* Check if argument is a file path */
struct stat st;
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
/* It's a file, read its contents */
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
/* Treat as inline script */
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContextRaw(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
JS_AddIntrinsicBaseObjects(ctx);
JS_AddIntrinsicEval(ctx);
JS_AddIntrinsicRegExp(ctx);
JS_AddIntrinsicJSON(ctx);
int result = 0;
if (print_bytecode) {
/* Compile only, then dump and optionally execute */
JSValue func = JS_Eval(ctx, script, strlen(script), filename, JS_EVAL_FLAG_COMPILE_ONLY);
if (JS_IsException(func)) {
uncaught_exception(ctx, func);
result = 1;
} else {
printf("=== Compiled Bytecode ===\n");
JS_DumpFunctionBytecode(ctx, func);
/* Link - resolve global references */
JSValue linked = JS_LinkFunction(ctx, func);
if (JS_IsException(linked)) {
uncaught_exception(ctx, linked);
result = 1;
} else {
printf("\n=== Linked Bytecode ===\n");
JS_DumpFunctionBytecode(ctx, linked);
/* Now execute the linked bytecode */
JSValue v = JS_EvalFunction(ctx, linked);
if (JS_IsException(v)) {
uncaught_exception(ctx, v);
result = 1;
} else {
JS_FreeValue(ctx, v);
}
}
}
} else {
/* Compile, link, execute */
JSValue func = JS_Eval(ctx, script, strlen(script), filename, JS_EVAL_FLAG_COMPILE_ONLY);
if (JS_IsException(func)) {
uncaught_exception(ctx, func);
result = 1;
} else {
JSValue linked = JS_LinkFunction(ctx, func);
if (JS_IsException(linked)) {
uncaught_exception(ctx, linked);
result = 1;
} else {
JSValue v = JS_EvalFunction(ctx, linked);
if (JS_IsException(v)) {
uncaught_exception(ctx, v);
result = 1;
} else {
JS_FreeValue(ctx, v);
}
}
}
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return result;
}
int cell_init(int argc, char **argv)
{
/* Check for --test flag to run C test suite */
if (argc >= 2 && strcmp(argv[1], "--test") == 0) {
size_t heap_size = 64 * 1024; /* 64KB default */
if (argc >= 3) {
heap_size = strtoull(argv[2], NULL, 0);
/* Round up to power of 2 for buddy allocator */
size_t p = 1;
while (p < heap_size) p <<= 1;
heap_size = p;
}
return run_test_suite(heap_size);
}
/* Check for -e or --eval flag to run immediate script */
/* Also check for -p flag to print bytecode */
if (argc >= 3 && (strcmp(argv[1], "-e") == 0 || strcmp(argv[1], "--eval") == 0)) {
return run_eval(argv[2], 0);
}
if (argc >= 3 && (strcmp(argv[1], "-p") == 0 || strcmp(argv[1], "--print-bytecode") == 0)) {
return run_eval(argv[2], 1);
}
int script_start = 1;
/* Find the cell shop at ~/.cell */

View File

@@ -19,14 +19,14 @@ typedef struct WotaEncodeContext {
static void wota_stack_push(WotaEncodeContext *enc, JSValueConst val)
{
if (!JS_IsObject(val)) return;
/* if (!JS_IsObject(val)) return;
ObjectRef *ref = malloc(sizeof(ObjectRef));
if (!ref) return;
ref->ptr = JS_VALUE_GET_PTR(val);
ref->next = enc->visited_stack;
enc->visited_stack = ref;
enc->visited_stack = ref;*/
}
static void wota_stack_pop(WotaEncodeContext *enc)
@@ -40,7 +40,7 @@ static void wota_stack_pop(WotaEncodeContext *enc)
static int wota_stack_has(WotaEncodeContext *enc, JSValueConst val)
{
if (!JS_IsObject(val)) return 0;
/* if (!JS_IsObject(val)) return 0;
void *ptr = JS_VALUE_GET_PTR(val);
ObjectRef *current = enc->visited_stack;
@@ -49,9 +49,10 @@ static int wota_stack_has(WotaEncodeContext *enc, JSValueConst val)
if (current->ptr == ptr) return 1;
current = current->next;
}
return 0;
return 0;*/
}
static void wota_stack_free(WotaEncodeContext *enc)
{
while (enc->visited_stack) {
@@ -156,7 +157,7 @@ static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueC
case JS_TAG_NULL:
wota_write_sym(&enc->wb, WOTA_NULL);
break;
case JS_TAG_OBJECT: {
case JS_TAG_PTR: {
if (js_is_blob(ctx, replaced)) {
size_t buf_len;
void *buf_data = js_get_blob_data(ctx, &buf_len, replaced);
@@ -171,7 +172,7 @@ static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueC
}
break;
}
if (JS_IsArray(ctx, replaced)) {
if (JS_IsArray(replaced)) {
if (wota_stack_has(enc, replaced)) {
enc->cycle = 1;
break;
@@ -249,11 +250,14 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val,
int scode;
data_ptr = wota_read_sym(&scode, data_ptr);
if (scode == WOTA_PRIVATE) {
JSValue inner = JS_NULL;
data_ptr = decode_wota_value(ctx, data_ptr, &inner, holder, JS_NULL, reviver);
JSGCRef inner_ref;
JS_PushGCRef(ctx, &inner_ref);
inner_ref.val = JS_NULL;
data_ptr = decode_wota_value(ctx, data_ptr, &inner_ref.val, holder, JS_NULL, reviver);
JSValue obj = JS_NewObject(ctx);
cell_rt *crt = JS_GetContextOpaque(ctx);
// JS_SetProperty(ctx, obj, crt->actor_sym, inner);
// JS_SetProperty(ctx, obj, crt->actor_sym, inner_ref.val);
JS_PopGCRef(ctx, &inner_ref);
*out_val = obj;
} else if (scode == WOTA_NULL) *out_val = JS_NULL;
else if (scode == WOTA_FALSE) *out_val = JS_NewBool(ctx, 0);
@@ -278,34 +282,40 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val,
}
case WOTA_ARR: {
long long c;
JSGCRef arr_ref;
data_ptr = wota_read_array(&c, data_ptr);
JSValue arr = JS_NewArrayLen(ctx, c);
JS_PushGCRef(ctx, &arr_ref);
arr_ref.val = JS_NewArrayLen(ctx, c);
for (long long i = 0; i < c; i++) {
JSValue elem_val = JS_NULL;
JSValue idx_key = JS_NewInt32(ctx, (int32_t)i);
data_ptr = decode_wota_value(ctx, data_ptr, &elem_val, arr, idx_key, reviver);
JS_SetPropertyUint32(ctx, arr, i, elem_val);
data_ptr = decode_wota_value(ctx, data_ptr, &elem_val, arr_ref.val, idx_key, reviver);
JS_SetPropertyUint32(ctx, arr_ref.val, i, elem_val);
}
*out_val = arr;
*out_val = JS_PopGCRef(ctx, &arr_ref);
break;
}
case WOTA_REC: {
long long c;
JSGCRef obj_ref;
data_ptr = wota_read_record(&c, data_ptr);
JSValue obj = JS_NewObject(ctx);
JS_PushGCRef(ctx, &obj_ref);
obj_ref.val = JS_NewObject(ctx);
for (long long i = 0; i < c; i++) {
char *tkey = NULL;
size_t key_len;
data_ptr = wota_read_text_len(&key_len, &tkey, data_ptr);
if (!tkey) continue; // invalid key
JSValue prop_key = JS_NewStringLen(ctx, tkey, key_len);
JSGCRef key_ref;
JS_PushGCRef(ctx, &key_ref);
key_ref.val = JS_NewStringLen(ctx, tkey, key_len);
JSValue sub_val = JS_NULL;
data_ptr = decode_wota_value(ctx, data_ptr, &sub_val, obj, prop_key, reviver);
JS_SetProperty(ctx, obj, prop_key, sub_val);
JS_FreeValue(ctx, prop_key);
data_ptr = decode_wota_value(ctx, data_ptr, &sub_val, obj_ref.val, key_ref.val, reviver);
JS_SetProperty(ctx, obj_ref.val, key_ref.val, sub_val);
JS_PopGCRef(ctx, &key_ref);
free(tkey);
}
*out_val = obj;
*out_val = JS_PopGCRef(ctx, &obj_ref);
break;
}
default:

View File

@@ -28,7 +28,6 @@ FMT(none)
FMT(none_int)
FMT(none_loc)
FMT(none_arg)
FMT(none_var_ref)
FMT(u8)
FMT(i8)
FMT(loc8)
@@ -42,7 +41,6 @@ FMT(npopx)
FMT(npop_u16)
FMT(loc)
FMT(arg)
FMT(var_ref)
FMT(u32)
FMT(i32)
FMT(const)
@@ -52,6 +50,7 @@ FMT(key)
FMT(key_u8)
FMT(key_u16)
FMT(key_label_u16)
FMT(u8_u16) /* 1 byte + 2 bytes for upvalue access */
#undef FMT
#endif /* FMT */
@@ -103,20 +102,18 @@ DEF( return, 1, 1, 0, none)
DEF( return_undef, 1, 0, 0, none)
DEF( throw, 1, 1, 0, none)
DEF( throw_error, 6, 0, 0, key_u8)
DEF( eval, 5, 1, 1, npop_u16) /* func args... -> ret_val */
DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a
bytecode string */
DEF( check_var, 5, 0, 1, key) /* check if a variable exists */
DEF( get_var_undef, 5, 0, 1, key) /* push undefined if the variable does not exist */
DEF( get_var, 5, 0, 1, key) /* throw an exception if the variable does not exist */
DEF( put_var, 5, 1, 0, key) /* must come after get_var */
DEF( put_var_init, 5, 1, 0, key) /* must come after put_var. Used to initialize a global lexical variable */
DEF( put_var_strict, 5, 2, 0, key) /* for strict mode variable write */
DEF( get_ref_value, 1, 2, 3, none)
DEF( put_ref_value, 1, 3, 0, none)
/* Global variable access - resolved by linker to get/set_global_slot */
DEF( check_var, 5, 0, 1, key) /* check if a variable exists - resolved by linker */
DEF( get_var_undef, 5, 0, 1, key) /* resolved by linker to get_global_slot */
DEF( get_var, 5, 0, 1, key) /* resolved by linker to get_global_slot */
DEF( put_var, 5, 1, 0, key) /* resolved by linker to set_global_slot */
DEF( put_var_init, 5, 1, 0, key) /* resolved by linker to set_global_slot */
DEF( put_var_strict, 5, 2, 0, key) /* resolved by linker to set_global_slot */
/* Global variable opcodes - resolved by linker to get/set_global_slot */
DEF( define_var, 6, 0, 0, key_u8)
DEF(check_define_var, 6, 0, 0, key_u8)
DEF( define_func, 6, 1, 0, key_u8)
@@ -143,18 +140,11 @@ DEF( set_loc, 3, 1, 1, loc) /* must come after put_loc */
DEF( get_arg, 3, 0, 1, arg)
DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */
DEF( set_arg, 3, 1, 1, arg) /* must come after put_arg */
DEF( get_var_ref, 3, 0, 1, var_ref)
DEF( put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */
DEF( set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */
DEF(set_loc_uninitialized, 3, 0, 0, loc)
DEF( get_loc_check, 3, 0, 1, loc)
DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */
DEF( put_loc_check_init, 3, 1, 0, loc)
DEF(get_loc_checkthis, 3, 0, 1, loc)
DEF(get_var_ref_check, 3, 0, 1, var_ref)
DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */
DEF(put_var_ref_check_init, 3, 1, 0, var_ref)
DEF( close_loc, 3, 0, 0, loc)
DEF( if_false, 5, 1, 0, label)
DEF( if_true, 5, 1, 0, label) /* must come after if_false */
DEF( goto, 5, 0, 0, label) /* must come after if_true */
@@ -163,15 +153,8 @@ DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */
DEF( ret, 1, 1, 0, none) /* used to return from the finally block */
DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */
DEF( to_object, 1, 1, 1, none)
//DEF( to_string, 1, 1, 1, none)
DEF( to_propkey, 1, 1, 1, none)
DEF( make_loc_ref, 7, 0, 2, key_u16)
DEF( make_arg_ref, 7, 0, 2, key_u16)
DEF(make_var_ref_ref, 7, 0, 2, key_u16)
DEF( make_var_ref, 5, 0, 2, key)
/* arithmetic/logic operations */
DEF( neg, 1, 1, 1, none)
DEF( plus, 1, 1, 1, none)
@@ -185,7 +168,7 @@ DEF( add_loc, 2, 1, 0, loc8)
DEF( not, 1, 1, 1, none)
DEF( lnot, 1, 1, 1, none)
DEF( delete, 1, 2, 1, none)
DEF( delete_var, 5, 0, 1, key)
DEF( delete_var, 5, 0, 1, key) /* deprecated - global object is immutable */
DEF( mul, 1, 2, 1, none)
DEF( mul_float, 1, 2, 1, none)
@@ -213,6 +196,16 @@ DEF( or, 1, 2, 1, none)
/* template literal concatenation - pops N parts, pushes concatenated string */
DEF(template_concat, 3, 0, 1, npop_u16)
/* Upvalue access (closures via outer_frame chain) */
DEF( get_up, 4, 0, 1, u8_u16) /* depth:u8, slot:u16 -> value */
DEF( set_up, 4, 1, 0, u8_u16) /* value, depth:u8, slot:u16 -> */
/* Name resolution with bytecode patching */
DEF( get_name, 5, 0, 1, const) /* cpool_idx -> value, patches itself */
DEF( get_env_slot, 3, 0, 1, u16) /* slot -> value (patched from get_name) */
DEF(get_global_slot, 3, 0, 1, u16) /* slot -> value (patched from get_var) */
DEF(set_global_slot, 3, 1, 0, u16) /* value -> slot (patched from put_var) */
/* must be the last non short and non temporary opcode */
DEF( nop, 1, 0, 0, none)
@@ -229,8 +222,6 @@ def(scope_get_var_undef, 7, 0, 1, key_u16) /* emitted in phase 1, removed in pha
def( scope_get_var, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2 */
def( scope_put_var, 7, 1, 0, key_u16) /* emitted in phase 1, removed in phase 2 */
def(scope_delete_var, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2 */
def( scope_make_ref, 11, 0, 2, key_label_u16) /* emitted in phase 1, removed in phase 2 */
def( scope_get_ref, 7, 0, 2, key_u16) /* emitted in phase 1, removed in phase 2 */
def(scope_put_var_init, 7, 0, 2, key_u16) /* emitted in phase 1, removed in phase 2 */
def(scope_get_var_checkthis, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2, only used to return 'this' in derived class constructors */
def(get_field_opt_chain, 5, 1, 1, key) /* emitted in phase 1, removed in phase 2 */
@@ -283,18 +274,6 @@ DEF( set_arg0, 1, 1, 1, none_arg)
DEF( set_arg1, 1, 1, 1, none_arg)
DEF( set_arg2, 1, 1, 1, none_arg)
DEF( set_arg3, 1, 1, 1, none_arg)
DEF( get_var_ref0, 1, 0, 1, none_var_ref)
DEF( get_var_ref1, 1, 0, 1, none_var_ref)
DEF( get_var_ref2, 1, 0, 1, none_var_ref)
DEF( get_var_ref3, 1, 0, 1, none_var_ref)
DEF( put_var_ref0, 1, 1, 0, none_var_ref)
DEF( put_var_ref1, 1, 1, 0, none_var_ref)
DEF( put_var_ref2, 1, 1, 0, none_var_ref)
DEF( put_var_ref3, 1, 1, 0, none_var_ref)
DEF( set_var_ref0, 1, 1, 1, none_var_ref)
DEF( set_var_ref1, 1, 1, 1, none_var_ref)
DEF( set_var_ref2, 1, 1, 1, none_var_ref)
DEF( set_var_ref3, 1, 1, 1, none_var_ref)
DEF( if_false8, 2, 1, 0, label8)
DEF( if_true8, 2, 1, 0, label8) /* must come after if_false8 */

File diff suppressed because it is too large Load Diff

View File

@@ -55,14 +55,13 @@ enum mist_obj_type {
OBJ_FORWARD = 7
};
#define OBJHDR_CAP_SHIFT 8u
#define OBJHDR_CAP_MASK (((objhdr_t)1ull << 56) - 1ull)
#define OBJHDR_S_BIT 3u
#define OBJHDR_P_BIT 4u
#define OBJHDR_A_BIT 5u
#define OBJHDR_R_BIT 7u
#define OBJHDR_FLAG(bit) ((objhdr_t)1ull << (bit))
#define OBJHDR_S_MASK OBJHDR_FLAG (OBJHDR_S_BIT)
#define OBJHDR_P_MASK OBJHDR_FLAG (OBJHDR_P_BIT)
@@ -150,6 +149,10 @@ enum {
JS_TAG_CATCH_OFFSET = 0x1F, /* 11111 */
};
/* Compatibility tag aliases for external code */
#define JS_TAG_STRING JS_TAG_STRING_IMM /* Alias: text/string type */
#define JS_TAG_FLOAT64 JS_TAG_SHORT_FLOAT /* Alias: floats use short float encoding */
#define JS_EMPTY_TEXT ((JSValue)JS_TAG_STRING_IMM)
/* JSGCRef - for rooting values during GC */
@@ -173,6 +176,8 @@ void JS_DeleteGCRef(JSContext *ctx, JSGCRef *ref);
Value Extraction
============================================================ */
#define JS_VALUE_GET_INT(v) ((int)(v) >> 1)
/* Get primary tag (low 2-3 bits) */
static inline int
JS_VALUE_GET_TAG (JSValue v) {
@@ -196,9 +201,6 @@ JS_VALUE_GET_TAG (JSValue v) {
/* Extract bool value from special tag */
#define JS_VALUE_GET_BOOL(v) ((int)((v) >> 5) & 1)
/* Extract pointer (clear low bits) */
#define JS_VALUE_GET_PTR(v) ((void *)((v) & ~((JSValue)(JSW - 1))))
/* Extract special tag payload (bits 5+) */
#define JS_VALUE_GET_SPECIAL_PAYLOAD(v) ((int32_t)((v) >> 5))
@@ -207,9 +209,6 @@ JS_VALUE_GET_TAG (JSValue v) {
============================================================ */
#define JS_MKVAL(tag, val) _JS_MkVal (tag, val)
#define JS_MKPTR(ptr) (((JSValue)(uintptr_t)(ptr)) | JS_TAG_PTR)
#define JS_MKFWD(ptr) (((JSValue)(uintptr_t)(ptr)) | JS_TAG_FORWARD)
static inline JSValue _JS_MkVal (int tag, int32_t val) {
if (tag == JS_TAG_INT) { return ((JSValue)(uint32_t)val << 1); }
/* Special tags encode payload in upper bits */
@@ -235,8 +234,8 @@ __JS_NewFloat64 (JSContext *ctx, double d) {
int exp = (u.u >> 52) & 0x7FF;
uint64_t mantissa = u.u & ((1ULL << 52) - 1);
/* Zero → short float zero */
if (exp == 0 && mantissa == 0) { return (sign << 63) | JS_TAG_SHORT_FLOAT; }
/* Zero → short float zero (always +0, no -0) */
if (exp == 0 && mantissa == 0) { return JS_TAG_SHORT_FLOAT; }
/* NaN/Inf → NULL */
if (exp == 0x7FF) { return JS_MKVAL (JS_TAG_NULL, 0); }
@@ -268,7 +267,7 @@ static inline double JS_VALUE_GET_FLOAT64 (JSValue v) {
uint64_t short_exp = (v >> 55) & 0xFF;
uint64_t mantissa = (v >> 3) & ((1ULL << 52) - 1);
if (short_exp == 0) return sign ? -0.0 : 0.0;
if (short_exp == 0) return 0.0; /* Always +0, no -0 */
uint64_t exp = short_exp - 127 + 1023;
union {
@@ -298,13 +297,10 @@ static inline double JS_VALUE_GET_FLOAT64 (JSValue v);
============================================================ */
static inline JS_BOOL JS_IsInt (JSValue v) { return (v & 1) == 0; }
static inline JS_BOOL JS_IsPtr (JSValue v) { return (v & 3) == JS_TAG_PTR; }
static inline JS_BOOL JS_IsPtr (JSValue v) { return (v & 7) == JS_TAG_PTR; }
static inline JS_BOOL JS_IsSpecial (JSValue v) { return (v & 3) == JS_TAG_SPECIAL; }
static inline JS_BOOL JS_IsStone(JSValue v) {
if (!JS_IsPtr(v)) return 1;
return objhdr_s(*(objhdr_t *)JS_VALUE_GET_PTR(v));
}
#ifdef JS_PTR64
static inline JS_BOOL
@@ -381,19 +377,17 @@ JSRuntime *JS_NewRuntime (void);
/* info lifetime must exceed that of rt */
void JS_SetRuntimeInfo (JSRuntime *rt, const char *info);
void JS_SetMemoryLimit (JSRuntime *rt, size_t limit);
void JS_SetGCThreshold (JSRuntime *rt, size_t gc_threshold);
/* use 0 to disable maximum stack size check */
void JS_SetMaxStackSize (JSRuntime *rt, size_t stack_size);
/* should be called when changing thread to update the stack top value
used to check stack overflow. */
void JS_UpdateStackTop (JSRuntime *rt);
JSRuntime *JS_NewRuntime2 (const JSMallocFunctions *mf, void *opaque);
void JS_FreeRuntime (JSRuntime *rt);
void *JS_GetRuntimeOpaque (JSRuntime *rt);
void JS_SetRuntimeOpaque (JSRuntime *rt, void *opaque);
typedef void JS_MarkFunc (JSRuntime *rt, JSGCObjectHeader *gp);
/* JS_MarkValue is a no-op with copying GC (values are traced from roots) */
void JS_MarkValue (JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func);
void JS_RunGC (JSRuntime *rt);
JS_BOOL JS_IsLiveObject (JSRuntime *rt, JSValue obj);
JSContext *JS_NewContext (JSRuntime *rt);
@@ -409,6 +403,7 @@ JSValue JS_GetClassProto (JSContext *ctx, JSClassID class_id);
/* the following functions are used to select the intrinsic object to
save memory */
JSContext *JS_NewContextRaw (JSRuntime *rt);
JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size);
typedef struct JSMemoryUsage {
int64_t malloc_size, malloc_limit, memory_used_size;
@@ -454,6 +449,18 @@ int JS_IsRegisteredClass (JSRuntime *rt, JSClassID class_id);
extern JSClassID js_class_id_alloc;
/* ============================================================
Copying GC - No Reference Counting Needed
============================================================
With a copying GC, reference counting is not needed since all live
objects are discovered by tracing from roots. These macros make
existing DupValue/FreeValue calls into no-ops.
============================================================ */
#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)
/* value handling */
static inline JSValue
@@ -570,37 +577,15 @@ static inline JS_BOOL JS_IsObject (JSValue v) {
return JS_IsPtr (v);
}
static inline JS_BOOL JS_IsArray(JSValue v) {
return JS_IsObject(v) && objhdr_type(*(objhdr_t *)JS_VALUE_GET_PTR(v)) == OBJ_ARRAY;
}
static inline JS_BOOL JS_IsRecord (JSValue v) {
return JS_IsObject(v) && objhdr_type(*(objhdr_t *)JS_VALUE_GET_PTR(v)) == OBJ_RECORD;
}
static inline JS_BOOL JS_IsFunction (JSValue v) {
return JS_IsObject(v) && objhdr_type(*(objhdr_t *)JS_VALUE_GET_PTR(v)) == OBJ_FUNCTION;
}
static inline JS_BOOL JS_IsCode (JSValue v) {
return JS_IsObject(v) && objhdr_type(*(objhdr_t *)JS_VALUE_GET_PTR(v)) == OBJ_CODE;
}
static inline JS_BOOL JS_IsForwarded (JSValue v) {
return JS_IsObject(v) && objhdr_type(*(objhdr_t *)JS_VALUE_GET_PTR(v)) == OBJ_FORWARD;
}
static inline JS_BOOL JS_IsFrame (JSValue v) {
return JS_IsObject(v) && objhdr_type(*(objhdr_t *)JS_VALUE_GET_PTR(v)) == OBJ_FRAME;
}
static inline JS_BOOL JS_IsBlob (JSValue v) {
return JS_IsObject(v) && objhdr_type(*(objhdr_t *)JS_VALUE_GET_PTR(v)) == OBJ_BLOB;
}
static inline JS_BOOL JS_IsText(JSValue v) {
return MIST_IsImmediateASCII(v) || (JS_IsObject(v) && objhdr_type(*(objhdr_t *)JS_VALUE_GET_PTR(v)) == OBJ_TEXT);
}
JS_BOOL JS_IsArray(JSValue v);
JS_BOOL JS_IsRecord(JSValue v);
JS_BOOL JS_IsFunction(JSValue v);
JS_BOOL JS_IsCode(JSValue v);
JS_BOOL JS_IsForwarded(JSValue v);
JS_BOOL JS_IsFrame(JSValue v);
JS_BOOL JS_IsBlob(JSValue v);
JS_BOOL JS_IsText(JSValue v);
static JS_BOOL JS_IsStone(JSValue v);
// Fundamental
int JS_GetLength (JSContext *ctx, JSValue obj, int64_t *pres);
@@ -624,7 +609,6 @@ JSValue __js_printf_like (2, 3)
JSValue JS_ThrowOutOfMemory (JSContext *ctx);
JS_BOOL JS_StrictEq (JSContext *ctx, JSValue op1, JSValue op2);
JS_BOOL JS_SameValue (JSContext *ctx, JSValue op1, JSValue op2);
int JS_ToBool (JSContext *ctx, JSValue val); /* return -1 for JS_EXCEPTION */
int JS_ToInt32 (JSContext *ctx, int32_t *pres, JSValue val);
@@ -657,8 +641,71 @@ JSValue JS_NewObject (JSContext *ctx);
JSValue JS_NewArray (JSContext *ctx);
JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len);
int JS_ArrayPush (JSContext *ctx, JSValue obj, JSValue val);
/* GC-safe push: takes pointer to array, updates it if array grows */
int JS_ArrayPush (JSContext *ctx, JSValue *arr_ptr, JSValue val);
JSValue JS_ArrayPop (JSContext *ctx, JSValue obj);
/* Intrinsic array operations - signatures match internal functions */
JSValue JS_Array (JSContext *ctx, JSValue arg0, JSValue arg1, JSValue arg2, JSValue arg3);
JSValue JS_ArrayFilter (JSContext *ctx, JSValue arr, JSValue fn);
JSValue JS_ArraySort (JSContext *ctx, JSValue arr, JSValue selector);
JSValue JS_ArrayFind (JSContext *ctx, JSValue arr, JSValue target_or_fn, JSValue reverse, JSValue from);
JSValue JS_ArrFor (JSContext *ctx, JSValue arr, JSValue fn, JSValue reverse, JSValue exit_val);
JSValue JS_ArrayReduce (JSContext *ctx, JSValue arr, JSValue fn, JSValue initial, JSValue reverse);
/* Cell intrinsic functions - C API wrappers */
/* Core functions */
JSValue JS_CellStone (JSContext *ctx, JSValue val);
JSValue JS_CellLength (JSContext *ctx, JSValue val);
JSValue JS_CellReverse (JSContext *ctx, JSValue val);
JSValue JS_CellProto (JSContext *ctx, JSValue obj);
JSValue JS_CellSplat (JSContext *ctx, JSValue val);
JSValue JS_CellMeme (JSContext *ctx, JSValue obj, JSValue deep);
JSValue JS_CellApply (JSContext *ctx, JSValue fn, JSValue args);
JSValue JS_CellCall (JSContext *ctx, JSValue fn, JSValue this_val, JSValue args);
JSValue JS_CellModulo (JSContext *ctx, JSValue a, JSValue b);
JSValue JS_CellNeg (JSContext *ctx, JSValue val);
JSValue JS_CellNot (JSContext *ctx, JSValue val);
/* Text functions */
JSValue JS_CellText (JSContext *ctx, JSValue val);
JSValue JS_CellLower (JSContext *ctx, JSValue text);
JSValue JS_CellUpper (JSContext *ctx, JSValue text);
JSValue JS_CellTrim (JSContext *ctx, JSValue text, JSValue chars);
JSValue JS_CellCodepoint (JSContext *ctx, JSValue text, JSValue idx);
JSValue JS_CellReplace (JSContext *ctx, JSValue text, JSValue pattern, JSValue replacement);
JSValue JS_CellSearch (JSContext *ctx, JSValue text, JSValue pattern, JSValue from);
JSValue JS_CellExtract (JSContext *ctx, JSValue text, JSValue from, JSValue to);
JSValue JS_CellCharacter (JSContext *ctx, JSValue codepoint);
/* Number functions */
JSValue JS_CellNumber (JSContext *ctx, JSValue val);
JSValue JS_CellAbs (JSContext *ctx, JSValue num);
JSValue JS_CellSign (JSContext *ctx, JSValue num);
JSValue JS_CellFloor (JSContext *ctx, JSValue num);
JSValue JS_CellCeiling (JSContext *ctx, JSValue num);
JSValue JS_CellRound (JSContext *ctx, JSValue num);
JSValue JS_CellTrunc (JSContext *ctx, JSValue num);
JSValue JS_CellWhole (JSContext *ctx, JSValue num);
JSValue JS_CellFraction (JSContext *ctx, JSValue num);
JSValue JS_CellMin (JSContext *ctx, JSValue a, JSValue b);
JSValue JS_CellMax (JSContext *ctx, JSValue a, JSValue b);
JSValue JS_CellRemainder (JSContext *ctx, JSValue a, JSValue b);
/* Object functions */
JSValue JS_CellObject (JSContext *ctx, JSValue proto, JSValue props);
/* Format function */
JSValue JS_CellFormat (JSContext *ctx, JSValue text, JSValue collection, JSValue transformer);
/* Helper functions */
JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values);
void JS_PrintText (JSContext *ctx, JSValue val);
void JS_PrintTextLn (JSContext *ctx, JSValue val);
void JS_PrintFormatted (JSContext *ctx, const char *fmt, int count, JSValue *values);
JSValue JS_GetProperty (JSContext *ctx, JSValue this_obj, JSValue prop);
// For records
@@ -736,6 +783,23 @@ JSValue JS_ReadObject (JSContext *ctx, const uint8_t *buf, size_t buf_len,
reading a script or module with JS_ReadObject() */
JSValue JS_EvalFunction (JSContext *ctx, JSValue fun_obj);
/* Eval function with environment record for variable resolution.
The env must be a stoned record. Variables are resolved env first,
then global intrinsics. */
JSValue JS_EvalFunctionEnv (JSContext *ctx, JSValue fun_obj, JSValue env);
/* Dump bytecode of a compiled function (for debugging) */
void JS_DumpFunctionBytecode (JSContext *ctx, JSValue func_val);
/* Link compiled bytecode to context - resolves global references.
Returns linked bytecode on success, JS_EXCEPTION on link error. */
JSValue JS_LinkFunction (JSContext *ctx, JSValue func_val);
/* Link compiled bytecode with environment record for variable resolution.
Variables are resolved: env first, then global intrinsics.
Returns linked bytecode on success, JS_EXCEPTION on link error. */
JSValue JS_LinkFunctionEnv (JSContext *ctx, JSValue func_val, JSValue env);
/* C function definition */
typedef enum JSCFunctionEnum {
JS_CFUNC_generic,
@@ -991,6 +1055,25 @@ JSValue js_debugger_fn_info (JSContext *ctx, JSValue fn);
JSValue js_debugger_fn_bytecode (JSContext *js, JSValue fn);
void *js_debugger_val_address (JSContext *js, JSValue val);
/* Memory allocation functions (bump allocator) */
void *js_malloc (JSContext *ctx, size_t size);
void *js_mallocz (JSContext *ctx, size_t size);
void *js_realloc (JSContext *ctx, void *ptr, size_t size);
void js_free (JSContext *ctx, void *ptr);
char *js_strdup (JSContext *ctx, const char *str);
/* Runtime-level memory functions */
void *js_malloc_rt (size_t size);
void *js_mallocz_rt (size_t size);
void js_free_rt (void *ptr);
/* Intrinsic setup functions */
void JS_AddIntrinsicBaseObjects (JSContext *ctx);
void JS_AddIntrinsicBasicObjects (JSContext *ctx);
void JS_AddIntrinsicEval (JSContext *ctx);
void JS_AddIntrinsicRegExp (JSContext *ctx);
void JS_AddIntrinsicJSON (JSContext *ctx);
#undef js_unlikely
#undef inline

2077
source/suite.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1244,15 +1244,16 @@ return {
// EDGE CASES AND SPECIAL VALUES
// ============================================================================
test_infinity: function() {
test_division_by_zero_is_null: function() {
var inf = 1 / 0
if (!(inf > 1000000)) throw "infinity failed"
if (!(-inf < -1000000)) throw "negative infinity failed"
if (inf != null) throw "division by zero should be null"
var ninf = -1 / 0
if (ninf != null) throw "negative division by zero should be null"
},
test_nan: function() {
test_zero_div_zero_is_null: function() {
var nan = 0 / 0
if (nan == nan) throw "NaN should not equal itself"
if (nan != null) throw "0/0 should be null"
},
test_max_safe_integer: function() {
@@ -1403,17 +1404,36 @@ return {
test_number_division_by_zero: function() {
var result = 1 / 0
if (!(result > 1000000)) throw "division by zero should give infinity"
if (result != null) throw "division by zero should give null"
},
test_number_negative_division_by_zero: function() {
var result = -1 / 0
if (!(result < -1000000)) throw "negative division by zero should give -infinity"
if (result != null) throw "negative division by zero should give null"
},
test_zero_division_by_zero: function() {
var result = 0 / 0
if (result == result) throw "0/0 should give NaN"
if (result != null) throw "0/0 should give null"
},
test_negative_zero_normalized: function() {
var nz = -0
if (nz != 0) throw "-0 should equal 0"
var mul_nz = 0 * -1
if (mul_nz != 0) throw "0 * -1 should be 0"
var neg_zero = -(0)
if (neg_zero != 0) throw "-(0) should be 0"
},
test_overflow_is_null: function() {
var result = 1e38 * 1e38
if (result != null) throw "overflow should give null"
},
test_modulo_by_zero_is_null: function() {
var result = 5 % 0
if (result != null) throw "modulo by zero should give null"
},
// ============================================================================
@@ -3506,4 +3526,158 @@ return {
if (obj.beta[1] != obj) throw "text key cycle failed"
},
// ============================================================================
// OBJECT INTRINSIC TESTS
// ============================================================================
test_object_shallow_copy: function() {
var orig = {a: 1, b: 2, c: 3}
var copy = object(orig)
if (copy.a != 1) throw "object copy a failed"
if (copy.b != 2) throw "object copy b failed"
if (copy.c != 3) throw "object copy c failed"
copy.a = 99
if (orig.a != 1) throw "object copy should not mutate original"
},
test_object_combine: function() {
var obj1 = {a: 1, b: 2}
var obj2 = {c: 3, d: 4}
var combined = object(obj1, obj2)
if (combined.a != 1) throw "object combine a failed"
if (combined.b != 2) throw "object combine b failed"
if (combined.c != 3) throw "object combine c failed"
if (combined.d != 4) throw "object combine d failed"
},
test_object_combine_override: function() {
var obj1 = {a: 1, b: 2}
var obj2 = {b: 99, c: 3}
var combined = object(obj1, obj2)
if (combined.a != 1) throw "object combine override a failed"
if (combined.b != 99) throw "object combine should override with second arg"
if (combined.c != 3) throw "object combine override c failed"
},
test_object_select_keys: function() {
var orig = {a: 1, b: 2, c: 3, d: 4}
var selected = object(orig, ["a", "c"])
if (selected.a != 1) throw "object select a failed"
if (selected.c != 3) throw "object select c failed"
if (selected.b != null) throw "object select should not include b"
if (selected.d != null) throw "object select should not include d"
},
test_object_from_keys_true: function() {
var keys = ["x", "y", "z"]
var obj = object(keys)
if (obj.x != true) throw "object from keys x failed"
if (obj.y != true) throw "object from keys y failed"
if (obj.z != true) throw "object from keys z failed"
},
test_object_from_keys_function: function() {
var keys = ["a", "b", "c"]
var obj = object(keys, function(k) { return k + "_val" })
if (obj.a != "a_val") throw "object from keys func a failed"
if (obj.b != "b_val") throw "object from keys func b failed"
if (obj.c != "c_val") throw "object from keys func c failed"
},
// ============================================================================
// SPLAT INTRINSIC TESTS
// ============================================================================
test_splat_prototype_flattening: function() {
var proto = {x: 10, y: 20}
var obj = {z: 30}
obj.__proto__ = proto
var flat = splat(obj)
if (flat.x != 10) throw "splat x failed"
if (flat.y != 20) throw "splat y failed"
if (flat.z != 30) throw "splat z failed"
},
// ============================================================================
// REVERSE INTRINSIC TESTS
// ============================================================================
test_reverse_array: function() {
var arr = [1, 2, 3, 4, 5]
var rev = reverse(arr)
if (rev[0] != 5) throw "reverse[0] failed"
if (rev[1] != 4) throw "reverse[1] failed"
if (rev[2] != 3) throw "reverse[2] failed"
if (rev[3] != 2) throw "reverse[3] failed"
if (rev[4] != 1) throw "reverse[4] failed"
if (arr[0] != 1) throw "reverse should not mutate original"
},
// ============================================================================
// APPLY INTRINSIC TESTS
// ============================================================================
test_apply_with_array_args: function() {
def sum = function(a, b, c) { return a + b + c }
var result = fn.apply(sum, [1, 2, 3])
if (result != 6) throw "apply with array args failed"
},
test_apply_with_no_args: function() {
def ret42 = function() { return 42 }
var result = fn.apply(ret42)
if (result != 42) throw "apply with no args failed"
},
test_apply_with_single_value: function() {
def double = function(x) { return x * 2 }
var result = fn.apply(double, 10)
if (result != 20) throw "apply with single value failed"
},
// ============================================================================
// GC STRESS TESTS FOR FIXED INTRINSICS
// ============================================================================
test_gc_reverse_under_pressure: function() {
// Create GC pressure by making many arrays, then reverse
var arrays = []
for (var i = 0; i < 100; i = i + 1) {
arrays[i] = [i, i+1, i+2, i+3, i+4]
}
// Now reverse each one - this tests re-chase after allocation
for (var i = 0; i < 100; i = i + 1) {
var rev = reverse(arrays[i])
if (rev[0] != i+4) throw "gc reverse stress failed at " + text(i)
}
},
test_gc_object_select_under_pressure: function() {
// Create GC pressure
var objs = []
for (var i = 0; i < 100; i = i + 1) {
objs[i] = {a: i, b: i+1, c: i+2, d: i+3}
}
// Select keys - tests re-chase in loop
for (var i = 0; i < 100; i = i + 1) {
var selected = object(objs[i], ["a", "c"])
if (selected.a != i) throw "gc object select stress failed at " + text(i)
if (selected.c != i+2) throw "gc object select stress c failed at " + text(i)
}
},
test_gc_object_from_keys_function_under_pressure: function() {
// Create GC pressure
var keysets = []
for (var i = 0; i < 50; i = i + 1) {
keysets[i] = ["k" + text(i), "j" + text(i), "m" + text(i)]
}
// Create objects with function - tests JS_PUSH/POP and re-chase
for (var i = 0; i < 50; i = i + 1) {
var obj = object(keysets[i], function(k) { return k + "_value" })
var expected = "k" + text(i) + "_value"
if (obj["k" + text(i)] != expected) throw "gc object from keys func stress failed at " + text(i)
}
},
}