Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2949912
gc.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
19 KB
Referenced Files
None
Subscribers
None
gc.c
View Options
#include
"arena.h"
#include
"internal.h"
#include
"common.h"
#include
"slab.h"
#include
<string.h>
#include
<stdlib.h>
#include
<stdio.h>
#ifdef _WIN32
#include
<windows.h>
static
inline
void
*
gc_mmap
(
size_t
size
)
{
return
VirtualAlloc
(
NULL
,
size
,
MEM_COMMIT
|
MEM_RESERVE
,
PAGE_READWRITE
);
}
static
inline
void
gc_munmap
(
void
*
ptr
,
size_t
size
)
{
(
void
)
size
;
VirtualFree
(
ptr
,
0
,
MEM_RELEASE
);
}
#else
#include
<sys/mman.h>
static
inline
void
*
gc_mmap
(
size_t
size
)
{
void
*
p
=
mmap
(
NULL
,
size
,
PROT_READ
|
PROT_WRITE
,
MAP_PRIVATE
|
MAP_ANON
,
-1
,
0
);
return
(
p
==
MAP_FAILED
)
?
NULL
:
p
;
}
static
inline
void
gc_munmap
(
void
*
ptr
,
size_t
size
)
{
munmap
(
ptr
,
size
);
}
#endif
#define MCO_API extern
#include
"minicoro.h"
#define GC_FWD_LOAD_FACTOR 70
static
uint8_t
*
gc_scratch_buf
=
NULL
;
static
size_t
gc_scratch_size
=
0
;
#define FWD_EMPTY ((jsoff_t)~0)
#define FWD_TOMBSTONE ((jsoff_t)~1)
typedef
struct
{
jsoff_t
*
old_offs
;
jsoff_t
*
new_offs
;
size_t
count
;
size_t
capacity
;
size_t
mask
;
}
gc_forward_table_t
;
typedef
struct
{
jsoff_t
*
items
;
size_t
count
;
size_t
capacity
;
}
gc_work_queue_t
;
typedef
struct
{
ant_t
*
js
;
slab_state_t
*
slab
;
uint8_t
*
new_mem
;
gc_forward_table_t
fwd
;
gc_work_queue_t
work
;
jsoff_t
new_brk
;
jsoff_t
new_size
;
bool
failed
;
}
gc_ctx_t
;
static
jsval_t
gc_update_val
(
gc_ctx_t
*
ctx
,
jsval_t
val
);
static
inline
size_t
next_pow2
(
size_t
n
)
{
n
--
;
n
|=
n
>>
1
;
n
|=
n
>>
2
;
n
|=
n
>>
4
;
n
|=
n
>>
8
;
n
|=
n
>>
16
;
#if SIZE_MAX > 0xFFFFFFFF
n
|=
n
>>
32
;
#endif
return
n
+
1
;
}
static
bool
fwd_init
(
gc_forward_table_t
*
fwd
,
size_t
estimated
)
{
size_t
cap
=
next_pow2
(
estimated
<
64
?
64
:
estimated
);
fwd
->
old_offs
=
(
jsoff_t
*
)
ant_calloc
(
cap
*
sizeof
(
jsoff_t
));
fwd
->
new_offs
=
(
jsoff_t
*
)
ant_calloc
(
cap
*
sizeof
(
jsoff_t
));
if
(
!
fwd
->
old_offs
||
!
fwd
->
new_offs
)
{
if
(
fwd
->
old_offs
)
free
(
fwd
->
old_offs
);
if
(
fwd
->
new_offs
)
free
(
fwd
->
new_offs
);
return
false
;
}
for
(
size_t
i
=
0
;
i
<
cap
;
i
++
)
fwd
->
old_offs
[
i
]
=
FWD_EMPTY
;
fwd
->
count
=
0
;
fwd
->
capacity
=
cap
;
fwd
->
mask
=
cap
-
1
;
return
true
;
}
static
bool
fwd_grow
(
gc_forward_table_t
*
fwd
)
{
size_t
new_cap
=
fwd
->
capacity
*
2
;
size_t
new_mask
=
new_cap
-
1
;
jsoff_t
*
new_old
=
(
jsoff_t
*
)
ant_calloc
(
new_cap
*
sizeof
(
jsoff_t
));
jsoff_t
*
new_new
=
(
jsoff_t
*
)
ant_calloc
(
new_cap
*
sizeof
(
jsoff_t
));
if
(
!
new_old
||
!
new_new
)
{
if
(
new_old
)
free
(
new_old
);
if
(
new_new
)
free
(
new_new
);
return
false
;
}
for
(
size_t
i
=
0
;
i
<
new_cap
;
i
++
)
new_old
[
i
]
=
FWD_EMPTY
;
for
(
size_t
i
=
0
;
i
<
fwd
->
capacity
;
i
++
)
{
jsoff_t
key
=
fwd
->
old_offs
[
i
];
if
(
key
==
FWD_EMPTY
||
key
==
FWD_TOMBSTONE
)
continue
;
size_t
h
=
(
key
>>
3
)
&
new_mask
;
while
(
new_old
[
h
]
!=
FWD_EMPTY
)
h
=
(
h
+
1
)
&
new_mask
;
new_old
[
h
]
=
key
;
new_new
[
h
]
=
fwd
->
new_offs
[
i
];
}
free
(
fwd
->
old_offs
);
free
(
fwd
->
new_offs
);
fwd
->
old_offs
=
new_old
;
fwd
->
new_offs
=
new_new
;
fwd
->
capacity
=
new_cap
;
fwd
->
mask
=
new_mask
;
return
true
;
}
static
inline
bool
fwd_add
(
gc_forward_table_t
*
fwd
,
jsoff_t
old_off
,
jsoff_t
new_off
)
{
if
(
fwd
->
count
*
100
>=
fwd
->
capacity
*
GC_FWD_LOAD_FACTOR
)
{
if
(
!
fwd_grow
(
fwd
))
return
false
;
}
size_t
h
=
(
old_off
>>
3
)
&
fwd
->
mask
;
while
(
fwd
->
old_offs
[
h
]
!=
FWD_EMPTY
&&
fwd
->
old_offs
[
h
]
!=
FWD_TOMBSTONE
)
{
if
(
fwd
->
old_offs
[
h
]
==
old_off
)
{
fwd
->
new_offs
[
h
]
=
new_off
;
return
true
;
}
h
=
(
h
+
1
)
&
fwd
->
mask
;
}
fwd
->
old_offs
[
h
]
=
old_off
;
fwd
->
new_offs
[
h
]
=
new_off
;
fwd
->
count
++
;
return
true
;
}
static
inline
jsoff_t
fwd_lookup
(
gc_forward_table_t
*
fwd
,
jsoff_t
old_off
)
{
size_t
h
=
(
old_off
>>
3
)
&
fwd
->
mask
;
for
(
size_t
i
=
0
;
i
<
fwd
->
capacity
;
i
++
)
{
jsoff_t
key
=
fwd
->
old_offs
[
h
];
if
(
key
==
FWD_EMPTY
)
return
(
jsoff_t
)
~
0
;
if
(
key
==
old_off
)
return
fwd
->
new_offs
[
h
];
h
=
(
h
+
1
)
&
fwd
->
mask
;
}
return
(
jsoff_t
)
~
0
;
}
static
void
fwd_free
(
gc_forward_table_t
*
fwd
)
{
if
(
fwd
->
old_offs
)
free
(
fwd
->
old_offs
);
if
(
fwd
->
new_offs
)
free
(
fwd
->
new_offs
);
fwd
->
old_offs
=
NULL
;
fwd
->
new_offs
=
NULL
;
fwd
->
count
=
0
;
fwd
->
capacity
=
0
;
}
static
bool
work_init
(
gc_work_queue_t
*
work
,
size_t
initial
)
{
work
->
items
=
(
jsoff_t
*
)
ant_calloc
(
initial
*
sizeof
(
jsoff_t
));
if
(
!
work
->
items
)
return
false
;
work
->
count
=
0
;
work
->
capacity
=
initial
;
return
true
;
}
static
inline
bool
work_push
(
gc_work_queue_t
*
work
,
jsoff_t
off
)
{
if
(
work
->
count
>=
work
->
capacity
)
{
size_t
new_cap
=
work
->
capacity
*
2
;
jsoff_t
*
new_items
=
(
jsoff_t
*
)
ant_calloc
(
new_cap
*
sizeof
(
jsoff_t
));
if
(
!
new_items
)
return
false
;
memcpy
(
new_items
,
work
->
items
,
work
->
count
*
sizeof
(
jsoff_t
));
free
(
work
->
items
);
work
->
items
=
new_items
;
work
->
capacity
=
new_cap
;
}
work
->
items
[
work
->
count
++
]
=
off
;
return
true
;
}
static
inline
jsoff_t
work_pop
(
gc_work_queue_t
*
work
)
{
if
(
work
->
count
==
0
)
return
(
jsoff_t
)
~
0
;
return
work
->
items
[
--
work
->
count
];
}
static
void
work_free
(
gc_work_queue_t
*
work
)
{
if
(
work
->
items
)
free
(
work
->
items
);
work
->
items
=
NULL
;
work
->
count
=
0
;
work
->
capacity
=
0
;
}
static
inline
jsoff_t
gc_loadoff
(
uint8_t
*
mem
,
jsoff_t
off
)
{
jsoff_t
val
;
memcpy
(
&
val
,
&
mem
[
off
],
sizeof
(
val
));
return
val
;
}
static
inline
jsval_t
gc_loadval
(
uint8_t
*
mem
,
jsoff_t
off
)
{
jsval_t
val
;
memcpy
(
&
val
,
&
mem
[
off
],
sizeof
(
val
));
return
val
;
}
static
inline
void
gc_saveoff
(
uint8_t
*
mem
,
jsoff_t
off
,
jsoff_t
val
)
{
memcpy
(
&
mem
[
off
],
&
val
,
sizeof
(
val
));
}
static
inline
void
gc_saveval
(
uint8_t
*
mem
,
jsoff_t
off
,
jsval_t
val
)
{
memcpy
(
&
mem
[
off
],
&
val
,
sizeof
(
val
));
}
static
jsoff_t
gc_alloc
(
gc_ctx_t
*
ctx
,
size_t
size
)
{
size
=
(
size
+
7
)
/
8
*
8
;
if
(
ctx
->
new_brk
+
size
>
ctx
->
new_size
)
{
ctx
->
failed
=
true
;
return
(
jsoff_t
)
~
0
;
}
jsoff_t
off
=
ctx
->
new_brk
;
ctx
->
new_brk
+=
(
jsoff_t
)
size
;
return
off
;
}
static
inline
bool
gc_is_tagged
(
jsval_t
v
)
{
return
(
v
>>
53
)
==
NANBOX_PREFIX_CHK
;
}
static
inline
uint8_t
gc_vtype
(
jsval_t
v
)
{
return
gc_is_tagged
(
v
)
?
((
v
>>
NANBOX_TYPE_SHIFT
)
&
NANBOX_TYPE_MASK
)
:
255
;
}
static
inline
size_t
gc_vdata
(
jsval_t
v
)
{
return
(
size_t
)(
v
&
NANBOX_DATA_MASK
);
}
static
inline
jsval_t
gc_mkval
(
uint8_t
type
,
uint64_t
data
)
{
return
NANBOX_PREFIX
|
((
jsval_t
)(
type
&
NANBOX_TYPE_MASK
)
<<
NANBOX_TYPE_SHIFT
)
|
(
data
&
NANBOX_DATA_MASK
);
}
// Check if an offset is within a slab page
static
bool
gc_is_slab_offset
(
gc_ctx_t
*
ctx
,
jsoff_t
off
)
{
return
slab_is_slab_offset
(
ctx
->
slab
,
ctx
->
js
->
mem
,
(
slab_off_t
)
off
);
}
// Copy a string from old memory to new compacted memory
static
jsoff_t
gc_copy_string
(
gc_ctx_t
*
ctx
,
jsoff_t
old_off
)
{
if
(
old_off
>=
ctx
->
js
->
brk
)
return
old_off
;
if
(
gc_is_slab_offset
(
ctx
,
old_off
))
return
old_off
;
jsoff_t
new_off
=
fwd_lookup
(
&
ctx
->
fwd
,
old_off
);
if
(
new_off
!=
(
jsoff_t
)
~
0
)
return
new_off
;
jsoff_t
header
=
gc_loadoff
(
ctx
->
js
->
mem
,
old_off
);
if
((
header
&
3
)
!=
T_STR
)
return
old_off
;
jsoff_t
size
=
esize
(
header
);
if
(
size
==
(
jsoff_t
)
~
0
)
return
old_off
;
new_off
=
gc_alloc
(
ctx
,
size
);
if
(
new_off
==
(
jsoff_t
)
~
0
)
return
old_off
;
memcpy
(
&
ctx
->
new_mem
[
new_off
],
&
ctx
->
js
->
mem
[
old_off
],
size
);
if
(
!
fwd_add
(
&
ctx
->
fwd
,
old_off
,
new_off
))
ctx
->
failed
=
true
;
return
new_off
;
}
// Copy a bigint from old memory to new compacted memory
static
jsoff_t
gc_copy_bigint
(
gc_ctx_t
*
ctx
,
jsoff_t
old_off
)
{
if
(
old_off
>=
ctx
->
js
->
brk
)
return
old_off
;
if
(
gc_is_slab_offset
(
ctx
,
old_off
))
return
old_off
;
jsoff_t
new_off
=
fwd_lookup
(
&
ctx
->
fwd
,
old_off
);
if
(
new_off
!=
(
jsoff_t
)
~
0
)
return
new_off
;
jsoff_t
header
=
gc_loadoff
(
ctx
->
js
->
mem
,
old_off
);
size_t
total
=
(
header
>>
4
)
+
sizeof
(
jsoff_t
);
total
=
(
total
+
7
)
/
8
*
8
;
new_off
=
gc_alloc
(
ctx
,
total
);
if
(
new_off
==
(
jsoff_t
)
~
0
)
return
old_off
;
memcpy
(
&
ctx
->
new_mem
[
new_off
],
&
ctx
->
js
->
mem
[
old_off
],
total
);
if
(
!
fwd_add
(
&
ctx
->
fwd
,
old_off
,
new_off
))
ctx
->
failed
=
true
;
return
new_off
;
}
// Mark an object in slab and add to work queue (returns true if newly marked)
static
bool
gc_mark_object
(
gc_ctx_t
*
ctx
,
jsoff_t
off
)
{
if
(
off
==
0
||
off
>=
ctx
->
js
->
brk
)
return
false
;
if
(
!
gc_is_slab_offset
(
ctx
,
off
))
return
false
;
// Check if already marked to avoid infinite loops
if
(
slab_is_marked
(
ctx
->
js
->
mem
,
ctx
->
slab
,
SLAB_CLASS_OBJ
,
(
slab_off_t
)
off
))
return
false
;
slab_mark
(
ctx
->
js
->
mem
,
ctx
->
slab
,
SLAB_CLASS_OBJ
,
(
slab_off_t
)
off
);
work_push
(
&
ctx
->
work
,
off
);
return
true
;
}
// Mark a property in slab and add to work queue (returns true if newly marked)
static
bool
gc_mark_prop
(
gc_ctx_t
*
ctx
,
jsoff_t
off
)
{
if
(
off
==
0
||
off
>=
ctx
->
js
->
brk
)
return
false
;
if
(
!
gc_is_slab_offset
(
ctx
,
off
))
return
false
;
// Check if already marked to avoid infinite loops
if
(
slab_is_marked
(
ctx
->
js
->
mem
,
ctx
->
slab
,
SLAB_CLASS_PROP
,
(
slab_off_t
)
off
))
return
false
;
slab_mark
(
ctx
->
js
->
mem
,
ctx
->
slab
,
SLAB_CLASS_PROP
,
(
slab_off_t
)
off
);
work_push
(
&
ctx
->
work
,
off
);
return
true
;
}
// Process a property: mark its value references and copy strings
static
void
gc_process_prop
(
gc_ctx_t
*
ctx
,
jsoff_t
prop_off
)
{
jsoff_t
header
=
gc_loadoff
(
ctx
->
js
->
mem
,
prop_off
);
// Mark next property in chain
jsoff_t
next_prop
=
header
&
~
(
3U
|
FLAGMASK
);
if
(
next_prop
!=
0
)
gc_mark_prop
(
ctx
,
next_prop
);
bool
is_slot
=
(
header
&
SLOTMASK
)
!=
0
;
// Copy string key if not a slot
if
(
!
is_slot
)
{
jsoff_t
key_off
=
gc_loadoff
(
ctx
->
js
->
mem
,
prop_off
+
sizeof
(
jsoff_t
));
if
(
key_off
!=
0
&&
key_off
<
ctx
->
js
->
brk
&&
!
gc_is_slab_offset
(
ctx
,
key_off
))
{
gc_copy_string
(
ctx
,
key_off
);
}
}
// Process value
jsval_t
val
=
gc_loadval
(
ctx
->
js
->
mem
,
prop_off
+
sizeof
(
jsoff_t
)
+
sizeof
(
jsoff_t
));
gc_update_val
(
ctx
,
val
);
}
// Process an object: mark its property chain and parent
static
void
gc_process_object
(
gc_ctx_t
*
ctx
,
jsoff_t
obj_off
)
{
jsoff_t
header
=
gc_loadoff
(
ctx
->
js
->
mem
,
obj_off
);
// Mark first property
jsoff_t
first_prop
=
header
&
~
(
3U
|
FLAGMASK
);
if
(
first_prop
!=
0
)
gc_mark_prop
(
ctx
,
first_prop
);
// Mark parent object
jsoff_t
parent_off
=
gc_loadoff
(
ctx
->
js
->
mem
,
obj_off
+
sizeof
(
jsoff_t
));
if
(
parent_off
!=
0
)
gc_mark_object
(
ctx
,
parent_off
);
// Tail is just a cache, no need to mark separately (it's in the prop chain)
}
// Drain the work queue, processing all reachable objects
static
void
gc_drain_work_queue
(
gc_ctx_t
*
ctx
)
{
jsoff_t
off
;
while
((
off
=
work_pop
(
&
ctx
->
work
))
!=
(
jsoff_t
)
~
0
)
{
if
(
ctx
->
failed
)
return
;
jsoff_t
header
=
gc_loadoff
(
ctx
->
js
->
mem
,
off
);
switch
(
header
&
3
)
{
case
T_OBJ
:
gc_process_object
(
ctx
,
off
);
break
;
case
T_PROP
:
gc_process_prop
(
ctx
,
off
);
break
;
default
:
break
;
}
}
}
// Update a jsval_t, copying strings/bigints and marking objects
static
jsval_t
gc_update_val
(
gc_ctx_t
*
ctx
,
jsval_t
val
)
{
if
(
!
gc_is_tagged
(
val
))
return
val
;
uint8_t
type
=
gc_vtype
(
val
);
jsoff_t
old_off
=
(
jsoff_t
)
gc_vdata
(
val
);
switch
(
type
)
{
case
T_OBJ
:
case
T_FUNC
:
case
T_ARR
:
case
T_PROMISE
:
case
T_GENERATOR
:
gc_mark_object
(
ctx
,
old_off
);
return
val
;
// Objects stay in place
case
T_STR
:
{
if
(
old_off
>=
ctx
->
js
->
brk
)
return
val
;
if
(
gc_is_slab_offset
(
ctx
,
old_off
))
return
val
;
jsoff_t
new_off
=
gc_copy_string
(
ctx
,
old_off
);
if
(
new_off
!=
old_off
&&
new_off
!=
(
jsoff_t
)
~
0
)
return
gc_mkval
(
type
,
new_off
);
return
val
;
}
case
T_BIGINT
:
{
if
(
old_off
>=
ctx
->
js
->
brk
)
return
val
;
if
(
gc_is_slab_offset
(
ctx
,
old_off
))
return
val
;
jsoff_t
new_off
=
gc_copy_bigint
(
ctx
,
old_off
);
if
(
new_off
!=
old_off
&&
new_off
!=
(
jsoff_t
)
~
0
)
return
gc_mkval
(
type
,
new_off
);
return
val
;
}
default
:
break
;
}
return
val
;
}
// Update references in a property (for string compaction)
static
void
gc_fixup_prop
(
gc_ctx_t
*
ctx
,
jsoff_t
prop_off
)
{
jsoff_t
header
=
gc_loadoff
(
ctx
->
js
->
mem
,
prop_off
);
// Fix next pointer if it was forwarded (shouldn't happen for slab props, but safe)
jsoff_t
next_prop
=
header
&
~
(
3U
|
FLAGMASK
);
if
(
next_prop
!=
0
)
{
jsoff_t
new_next
=
fwd_lookup
(
&
ctx
->
fwd
,
next_prop
);
if
(
new_next
!=
(
jsoff_t
)
~
0
)
{
jsoff_t
new_header
=
(
new_next
&
~
3ULL
)
|
(
header
&
(
3ULL
|
FLAGMASK
));
gc_saveoff
(
ctx
->
js
->
mem
,
prop_off
,
new_header
);
}
}
bool
is_slot
=
(
header
&
SLOTMASK
)
!=
0
;
// Fix string key reference
if
(
!
is_slot
)
{
jsoff_t
key_off
=
gc_loadoff
(
ctx
->
js
->
mem
,
prop_off
+
sizeof
(
jsoff_t
));
if
(
key_off
!=
0
&&
key_off
<
ctx
->
js
->
brk
)
{
jsoff_t
new_key
=
fwd_lookup
(
&
ctx
->
fwd
,
key_off
);
if
(
new_key
!=
(
jsoff_t
)
~
0
)
{
gc_saveoff
(
ctx
->
js
->
mem
,
prop_off
+
sizeof
(
jsoff_t
),
new_key
);
}
}
}
// Fix value reference
jsval_t
val
=
gc_loadval
(
ctx
->
js
->
mem
,
prop_off
+
sizeof
(
jsoff_t
)
+
sizeof
(
jsoff_t
));
if
(
gc_is_tagged
(
val
))
{
uint8_t
type
=
gc_vtype
(
val
);
jsoff_t
old_off
=
(
jsoff_t
)
gc_vdata
(
val
);
if
((
type
==
T_STR
||
type
==
T_BIGINT
)
&&
old_off
<
ctx
->
js
->
brk
)
{
jsoff_t
new_off
=
fwd_lookup
(
&
ctx
->
fwd
,
old_off
);
if
(
new_off
!=
(
jsoff_t
)
~
0
)
{
jsval_t
new_val
=
gc_mkval
(
type
,
new_off
);
gc_saveval
(
ctx
->
js
->
mem
,
prop_off
+
sizeof
(
jsoff_t
)
+
sizeof
(
jsoff_t
),
new_val
);
}
}
}
}
// Update references in an object (for string compaction)
static
void
gc_fixup_object
(
gc_ctx_t
*
ctx
,
jsoff_t
obj_off
)
{
// Object header first_prop and parent are object offsets, not strings
// They stay in place, no fixup needed
(
void
)
ctx
;
(
void
)
obj_off
;
}
// Fixup all slab-allocated objects/props after string compaction
static
void
gc_fixup_slabs
(
gc_ctx_t
*
ctx
)
{
for
(
int
c
=
0
;
c
<
SLAB_CLASS_COUNT
;
c
++
)
{
slab_class_t
*
sc
=
&
ctx
->
slab
->
classes
[
c
];
for
(
uint32_t
p
=
0
;
p
<
sc
->
page_count
;
p
++
)
{
slab_off_t
page_off
=
sc
->
pages
[
p
];
slab_page_t
*
page
=
(
slab_page_t
*
)(
ctx
->
js
->
mem
+
page_off
);
uint8_t
*
alloc_bits
=
slab_alloc_bits
(
ctx
->
js
->
mem
,
page_off
);
uint8_t
*
mark_bits
=
slab_mark_bits
(
ctx
->
js
->
mem
,
page_off
,
page
->
slot_count
);
uint8_t
*
slots
=
slab_slots
(
ctx
->
js
->
mem
,
page_off
,
page
->
slot_count
);
for
(
uint32_t
i
=
0
;
i
<
page
->
slot_count
;
i
++
)
{
bool
marked
=
mark_bits
[
i
/
8
]
&
(
1
<<
(
i
%
8
));
if
(
!
marked
)
continue
;
jsoff_t
slot_off
=
(
jsoff_t
)(
slots
-
ctx
->
js
->
mem
)
+
i
*
page
->
slot_size
;
if
(
c
==
SLAB_CLASS_OBJ
)
{
gc_fixup_object
(
ctx
,
slot_off
);
}
else
if
(
c
==
SLAB_CLASS_PROP
)
{
gc_fixup_prop
(
ctx
,
slot_off
);
}
}
}
}
}
// Callbacks for root traversal
static
jsoff_t
gc_fwd_off_callback
(
void
*
ctx_ptr
,
jsoff_t
old_off
)
{
gc_ctx_t
*
ctx
=
(
gc_ctx_t
*
)
ctx_ptr
;
if
(
old_off
==
0
)
return
0
;
if
(
old_off
>=
ctx
->
js
->
brk
)
return
old_off
;
// Check if it's a slab offset - mark it
if
(
gc_is_slab_offset
(
ctx
,
old_off
))
{
jsoff_t
header
=
gc_loadoff
(
ctx
->
js
->
mem
,
old_off
);
if
((
header
&
3
)
==
T_OBJ
)
gc_mark_object
(
ctx
,
old_off
);
else
if
((
header
&
3
)
==
T_PROP
)
gc_mark_prop
(
ctx
,
old_off
);
return
old_off
;
}
// Check for string/bigint forwarding
jsoff_t
new_off
=
fwd_lookup
(
&
ctx
->
fwd
,
old_off
);
if
(
new_off
!=
(
jsoff_t
)
~
0
)
return
new_off
;
// Copy string if needed
jsoff_t
header
=
gc_loadoff
(
ctx
->
js
->
mem
,
old_off
);
if
((
header
&
3
)
==
T_STR
)
{
return
gc_copy_string
(
ctx
,
old_off
);
}
return
old_off
;
}
static
jsval_t
gc_fwd_val_callback
(
void
*
ctx_ptr
,
jsval_t
val
)
{
gc_ctx_t
*
ctx
=
(
gc_ctx_t
*
)
ctx_ptr
;
return
gc_update_val
(
ctx
,
val
);
}
static
jsoff_t
gc_apply_off_callback
(
void
*
ctx_ptr
,
jsoff_t
old_off
)
{
gc_ctx_t
*
ctx
=
(
gc_ctx_t
*
)
ctx_ptr
;
if
(
old_off
==
0
)
return
0
;
if
(
old_off
>=
ctx
->
js
->
brk
)
return
old_off
;
if
(
gc_is_slab_offset
(
ctx
,
old_off
))
return
old_off
;
jsoff_t
new_off
=
fwd_lookup
(
&
ctx
->
fwd
,
old_off
);
return
(
new_off
!=
(
jsoff_t
)
~
0
)
?
new_off
:
old_off
;
}
static
jsval_t
gc_apply_val
(
gc_ctx_t
*
ctx
,
jsval_t
val
)
{
if
(
!
gc_is_tagged
(
val
))
return
val
;
uint8_t
type
=
gc_vtype
(
val
);
jsoff_t
old_off
=
(
jsoff_t
)
gc_vdata
(
val
);
if
(
old_off
>=
ctx
->
js
->
brk
)
return
val
;
if
(
gc_is_slab_offset
(
ctx
,
old_off
))
return
val
;
if
(
type
==
T_STR
||
type
==
T_BIGINT
)
{
jsoff_t
new_off
=
fwd_lookup
(
&
ctx
->
fwd
,
old_off
);
if
(
new_off
!=
(
jsoff_t
)
~
0
)
return
gc_mkval
(
type
,
new_off
);
}
return
val
;
}
static
jsval_t
gc_apply_val_callback
(
void
*
ctx_ptr
,
jsval_t
val
)
{
gc_ctx_t
*
ctx
=
(
gc_ctx_t
*
)
ctx_ptr
;
return
gc_apply_val
(
ctx
,
val
);
}
size_t
js_gc_compact
(
ant_t
*
js
)
{
if
(
!
js
||
js
->
brk
==
0
)
return
0
;
mco_coro
*
running
=
mco_running
();
int
in_coroutine
=
(
running
!=
NULL
&&
running
->
stack_base
!=
NULL
);
if
(
in_coroutine
||
js_has_pending_coroutines
())
return
0
;
slab_state_t
*
slab
=
js_get_slab_state
();
if
(
!
slab
||
!
slab
->
initialized
)
return
0
;
size_t
old_brk
=
js
->
brk
;
size_t
new_size
=
js
->
size
;
// Allocate scratch buffer for compacted strings/bigints
if
(
new_size
>
gc_scratch_size
)
{
if
(
gc_scratch_buf
)
gc_munmap
(
gc_scratch_buf
,
gc_scratch_size
);
gc_scratch_buf
=
(
uint8_t
*
)
gc_mmap
(
new_size
);
gc_scratch_size
=
gc_scratch_buf
?
new_size
:
0
;
}
if
(
!
gc_scratch_buf
)
return
0
;
memset
(
gc_scratch_buf
,
0
,
new_size
);
size_t
estimated_strings
=
js
->
brk
/
128
;
if
(
estimated_strings
<
256
)
estimated_strings
=
256
;
gc_ctx_t
ctx
;
ctx
.
js
=
js
;
ctx
.
slab
=
slab
;
ctx
.
new_mem
=
gc_scratch_buf
;
ctx
.
new_brk
=
0
;
ctx
.
new_size
=
(
jsoff_t
)
new_size
;
ctx
.
failed
=
false
;
if
(
!
fwd_init
(
&
ctx
.
fwd
,
estimated_strings
))
{
return
0
;
}
if
(
!
work_init
(
&
ctx
.
work
,
estimated_strings
/
4
<
64
?
64
:
estimated_strings
/
4
))
{
fwd_free
(
&
ctx
.
fwd
);
return
0
;
}
// Mark global scope
jsoff_t
scope_off
=
(
jsoff_t
)
gc_vdata
(
js
->
scope
);
if
(
scope_off
<
js
->
brk
)
gc_mark_object
(
&
ctx
,
scope_off
);
// Mark other roots
gc_update_val
(
&
ctx
,
js
->
this_val
);
gc_update_val
(
&
ctx
,
js
->
module_ns
);
gc_update_val
(
&
ctx
,
js
->
current_func
);
gc_update_val
(
&
ctx
,
js
->
thrown_value
);
gc_update_val
(
&
ctx
,
js
->
tval
);
js_gc_reserve_roots
(
js
,
gc_fwd_off_callback
,
gc_fwd_val_callback
,
&
ctx
);
// Process work queue (traverse object graph)
gc_drain_work_queue
(
&
ctx
);
if
(
ctx
.
failed
)
{
work_free
(
&
ctx
.
work
);
fwd_free
(
&
ctx
.
fwd
);
return
0
;
}
// Fixup slab objects/props with forwarded string references
gc_fixup_slabs
(
&
ctx
);
// Update root references
js
->
scope
=
gc_apply_val
(
&
ctx
,
js
->
scope
);
js
->
this_val
=
gc_apply_val
(
&
ctx
,
js
->
this_val
);
js
->
module_ns
=
gc_apply_val
(
&
ctx
,
js
->
module_ns
);
js
->
current_func
=
gc_apply_val
(
&
ctx
,
js
->
current_func
);
js
->
thrown_value
=
gc_apply_val
(
&
ctx
,
js
->
thrown_value
);
js
->
tval
=
gc_apply_val
(
&
ctx
,
js
->
tval
);
js_gc_update_roots
(
js
,
gc_apply_off_callback
,
gc_apply_val_callback
,
&
ctx
);
// TODO: Slab sweeping disabled - marking logic needs debugging
// Slabs will grow but not be reclaimed until this is fixed
size_t
slab_freed
=
0
;
// size_t slab_freed = slab_sweep_all(js->mem, slab);
// Copy compacted strings back
// We need to find a new location in the arena for them
// For now, we copy them back to the original positions where strings were
// This is tricky because we can't easily find free space without more bookkeeping
// Simpler approach: copy compacted strings to the end of currently used bump space
// We need to recalculate where strings go...
// Actually, the easiest approach is:
// 1. Strings/bigints were copied to scratch buffer at new offsets
// 2. We need to copy them back to js->mem
// 3. But we need free space - strings that died freed up space
// For slab-based GC, we can't easily compact the bump region because
// slab pages are interspersed. Let's just count what we freed from slabs.
// TODO: Full arena compaction would require more sophisticated bookkeeping
// For now, just report slab freed slots as the benefit
work_free
(
&
ctx
.
work
);
fwd_free
(
&
ctx
.
fwd
);
// Return approximate freed bytes
return
slab_freed
*
SLAB_OBJ_SIZE
;
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Sat, Mar 28, 10:40 AM (1 d, 23 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
513909
Default Alt Text
gc.c (19 KB)
Attached To
Mode
rANT Ant
Attached
Detach File
Event Timeline
Log In to Comment