Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2920050
gc.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
36 KB
Referenced Files
None
Subscribers
None
gc.c
View Options
#include
"gc.h"
#include
"arena.h"
#include
"internal.h"
#include
"common.h"
#include
"modules/timer.h"
#include
<string.h>
#include
<stdlib.h>
#include
<stdio.h>
#include
<time.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"
static
uint8_t
*
gc_scratch_buf
=
NULL
;
static
size_t
gc_scratch_size
=
0
;
static
time_t
gc_last_run_time
=
0
;
static
bool
gc_throttled
=
false
;
void
js_gc_throttle
(
bool
enabled
)
{
gc_throttled
=
enabled
;
}
#define FWD_EMPTY ((jsoff_t)~0)
#define FWD_TOMBSTONE ((jsoff_t)~1)
#ifdef _WIN32
#define RELEASE_PAGES(p, sz) VirtualAlloc(p, sz, MEM_RESET, PAGE_READWRITE)
#else
#define RELEASE_PAGES(p, sz) madvise(p, sz, MADV_DONTNEED)
#endif
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
;
uint8_t
*
new_mem
;
uint8_t
*
mark_bits
;
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
void
mark_set
(
gc_ctx_t
*
ctx
,
jsoff_t
off
)
{
jsoff_t
idx
=
off
>>
3
;
ctx
->
mark_bits
[
idx
>>
3
]
|=
(
1
<<
(
idx
&
7
));
}
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
);
size_t
size
=
cap
*
sizeof
(
jsoff_t
);
fwd
->
old_offs
=
(
jsoff_t
*
)
gc_mmap
(
size
);
fwd
->
new_offs
=
(
jsoff_t
*
)
gc_mmap
(
size
);
if
(
!
fwd
->
old_offs
||
!
fwd
->
new_offs
)
{
if
(
fwd
->
old_offs
)
gc_munmap
(
fwd
->
old_offs
,
size
);
if
(
fwd
->
new_offs
)
gc_munmap
(
fwd
->
new_offs
,
size
);
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
;
size_t
new_size
=
new_cap
*
sizeof
(
jsoff_t
);
size_t
old_size
=
fwd
->
capacity
*
sizeof
(
jsoff_t
);
jsoff_t
*
new_old
=
(
jsoff_t
*
)
gc_mmap
(
new_size
);
jsoff_t
*
new_new
=
(
jsoff_t
*
)
gc_mmap
(
new_size
);
if
(
!
new_old
||
!
new_new
)
{
if
(
new_old
)
gc_munmap
(
new_old
,
new_size
);
if
(
new_new
)
gc_munmap
(
new_new
,
new_size
);
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
];
}
gc_munmap
(
fwd
->
old_offs
,
old_size
);
gc_munmap
(
fwd
->
new_offs
,
old_size
);
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
)
{
size_t
size
=
fwd
->
capacity
*
sizeof
(
jsoff_t
);
if
(
fwd
->
old_offs
)
gc_munmap
(
fwd
->
old_offs
,
size
);
if
(
fwd
->
new_offs
)
gc_munmap
(
fwd
->
new_offs
,
size
);
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
)
{
size_t
size
=
initial
*
sizeof
(
jsoff_t
);
work
->
items
=
(
jsoff_t
*
)
gc_mmap
(
size
);
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
;
size_t
new_size
=
new_cap
*
sizeof
(
jsoff_t
);
size_t
old_size
=
work
->
capacity
*
sizeof
(
jsoff_t
);
jsoff_t
*
new_items
=
(
jsoff_t
*
)
gc_mmap
(
new_size
);
if
(
!
new_items
)
return
false
;
memcpy
(
new_items
,
work
->
items
,
work
->
count
*
sizeof
(
jsoff_t
));
gc_munmap
(
work
->
items
,
old_size
);
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
)
{
size_t
size
=
work
->
capacity
*
sizeof
(
jsoff_t
);
if
(
work
->
items
)
gc_munmap
(
work
->
items
,
size
);
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
);
}
static
jsoff_t
gc_copy_string
(
gc_ctx_t
*
ctx
,
jsoff_t
old_off
)
{
if
(
old_off
>=
ctx
->
js
->
brk
)
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
;
bool
is_rope_str
=
(
header
&
ROPE_FLAG
)
!=
0
;
jsoff_t
size
;
if
(
is_rope_str
)
{
size
=
sizeof
(
rope_node_t
);
}
else
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
;
mark_set
(
ctx
,
old_off
);
if
(
is_rope_str
)
{
jsval_t
left
,
right
,
cached
;
memcpy
(
&
left
,
&
ctx
->
js
->
mem
[
old_off
+
offsetof
(
rope_node_t
,
left
)],
sizeof
(
jsval_t
));
memcpy
(
&
right
,
&
ctx
->
js
->
mem
[
old_off
+
offsetof
(
rope_node_t
,
right
)],
sizeof
(
jsval_t
));
memcpy
(
&
cached
,
&
ctx
->
js
->
mem
[
old_off
+
offsetof
(
rope_node_t
,
cached
)],
sizeof
(
jsval_t
));
jsval_t
new_left
=
gc_update_val
(
ctx
,
left
);
jsval_t
new_right
=
gc_update_val
(
ctx
,
right
);
jsval_t
new_cached
=
gc_update_val
(
ctx
,
cached
);
memcpy
(
&
ctx
->
new_mem
[
new_off
+
offsetof
(
rope_node_t
,
left
)],
&
new_left
,
sizeof
(
jsval_t
));
memcpy
(
&
ctx
->
new_mem
[
new_off
+
offsetof
(
rope_node_t
,
right
)],
&
new_right
,
sizeof
(
jsval_t
));
memcpy
(
&
ctx
->
new_mem
[
new_off
+
offsetof
(
rope_node_t
,
cached
)],
&
new_cached
,
sizeof
(
jsval_t
));
}
return
new_off
;
}
static
jsoff_t
gc_copy_bigint
(
gc_ctx_t
*
ctx
,
jsoff_t
old_off
)
{
if
(
old_off
>=
ctx
->
js
->
brk
)
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
;
mark_set
(
ctx
,
old_off
);
return
new_off
;
}
static
jsoff_t
gc_reserve_object
(
gc_ctx_t
*
ctx
,
jsoff_t
old_off
)
{
if
(
old_off
>=
ctx
->
js
->
brk
)
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_OBJ
)
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
;
mark_set
(
ctx
,
old_off
);
if
(
!
work_push
(
&
ctx
->
work
,
old_off
))
ctx
->
failed
=
true
;
return
new_off
;
}
static
jsoff_t
gc_reserve_prop
(
gc_ctx_t
*
ctx
,
jsoff_t
old_off
)
{
if
(
old_off
>=
ctx
->
js
->
brk
)
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_PROP
)
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
;
mark_set
(
ctx
,
old_off
);
if
(
!
work_push
(
&
ctx
->
work
,
old_off
))
ctx
->
failed
=
true
;
return
new_off
;
}
static
void
gc_process_prop
(
gc_ctx_t
*
ctx
,
jsoff_t
old_off
)
{
jsoff_t
new_off
=
fwd_lookup
(
&
ctx
->
fwd
,
old_off
);
if
(
new_off
==
(
jsoff_t
)
~
0
)
return
;
jsoff_t
header
=
gc_loadoff
(
ctx
->
js
->
mem
,
old_off
);
jsoff_t
next_prop
=
header
&
~
(
3ULL
|
FLAGMASK
);
if
(
next_prop
!=
0
&&
next_prop
<
ctx
->
js
->
brk
)
{
jsoff_t
new_next
=
gc_reserve_prop
(
ctx
,
next_prop
);
jsoff_t
new_header
=
(
new_next
&
~
3ULL
)
|
(
header
&
(
3ULL
|
FLAGMASK
));
gc_saveoff
(
ctx
->
new_mem
,
new_off
,
new_header
);
}
bool
is_slot
=
(
header
&
SLOTMASK
)
!=
0
;
if
(
!
is_slot
)
{
jsoff_t
key_off
=
gc_loadoff
(
ctx
->
js
->
mem
,
old_off
+
sizeof
(
jsoff_t
));
if
(
key_off
<
ctx
->
js
->
brk
)
{
jsoff_t
new_key
=
gc_copy_string
(
ctx
,
key_off
);
gc_saveoff
(
ctx
->
new_mem
,
new_off
+
sizeof
(
jsoff_t
),
new_key
);
}
}
jsval_t
val
=
gc_loadval
(
ctx
->
js
->
mem
,
old_off
+
sizeof
(
jsoff_t
)
+
sizeof
(
jsoff_t
));
jsval_t
new_val
=
gc_update_val
(
ctx
,
val
);
gc_saveval
(
ctx
->
new_mem
,
new_off
+
sizeof
(
jsoff_t
)
+
sizeof
(
jsoff_t
),
new_val
);
}
static
void
gc_process_object
(
gc_ctx_t
*
ctx
,
jsoff_t
old_off
)
{
jsoff_t
new_off
=
fwd_lookup
(
&
ctx
->
fwd
,
old_off
);
if
(
new_off
==
(
jsoff_t
)
~
0
)
return
;
jsoff_t
header
=
gc_loadoff
(
ctx
->
js
->
mem
,
old_off
);
jsoff_t
first_prop
=
header
&
~
(
3ULL
|
FLAGMASK
);
if
(
first_prop
!=
0
&&
first_prop
<
ctx
->
js
->
brk
)
{
jsoff_t
new_first
=
gc_reserve_prop
(
ctx
,
first_prop
);
jsoff_t
new_header
=
(
new_first
&
~
3ULL
)
|
(
header
&
(
3ULL
|
FLAGMASK
));
gc_saveoff
(
ctx
->
new_mem
,
new_off
,
new_header
);
}
jsoff_t
parent_off
=
gc_loadoff
(
ctx
->
js
->
mem
,
old_off
+
sizeof
(
jsoff_t
));
if
(
parent_off
<
ctx
->
js
->
brk
)
{
jsoff_t
new_parent
=
gc_reserve_object
(
ctx
,
parent_off
);
gc_saveoff
(
ctx
->
new_mem
,
new_off
+
sizeof
(
jsoff_t
),
new_parent
);
}
jsoff_t
tail_off
=
gc_loadoff
(
ctx
->
js
->
mem
,
old_off
+
sizeof
(
jsoff_t
)
+
sizeof
(
jsoff_t
));
if
(
tail_off
!=
0
&&
tail_off
<
ctx
->
js
->
brk
)
{
jsoff_t
new_tail
=
fwd_lookup
(
&
ctx
->
fwd
,
tail_off
);
if
(
new_tail
==
(
jsoff_t
)
~
0
)
new_tail
=
gc_reserve_prop
(
ctx
,
tail_off
);
gc_saveoff
(
ctx
->
new_mem
,
new_off
+
sizeof
(
jsoff_t
)
+
sizeof
(
jsoff_t
),
new_tail
);
}
}
static
void
gc_drain_work_queue
(
gc_ctx_t
*
ctx
)
{
jsoff_t
off
;
while
((
off
=
work_pop
(
&
ctx
->
work
))
!=
(
jsoff_t
)
~
0
)
{
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
;
}
}
}
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
:
{
if
(
old_off
>=
ctx
->
js
->
brk
)
return
val
;
jsoff_t
new_off
=
gc_reserve_object
(
ctx
,
old_off
);
if
(
new_off
!=
(
jsoff_t
)
~
0
)
return
gc_mkval
(
type
,
new_off
);
break
;
}
case
T_STR
:
{
if
(
old_off
>=
ctx
->
js
->
brk
)
return
val
;
jsoff_t
new_off
=
gc_copy_string
(
ctx
,
old_off
);
if
(
new_off
!=
(
jsoff_t
)
~
0
)
return
gc_mkval
(
type
,
new_off
);
break
;
}
case
T_PROP
:
{
if
(
old_off
>=
ctx
->
js
->
brk
)
return
val
;
jsoff_t
new_off
=
gc_reserve_prop
(
ctx
,
old_off
);
if
(
new_off
!=
(
jsoff_t
)
~
0
)
return
gc_mkval
(
type
,
new_off
);
break
;
}
case
T_BIGINT
:
{
if
(
old_off
>=
ctx
->
js
->
brk
)
return
val
;
jsoff_t
new_off
=
gc_copy_bigint
(
ctx
,
old_off
);
if
(
new_off
!=
(
jsoff_t
)
~
0
)
return
gc_mkval
(
type
,
new_off
);
break
;
}
default
:
break
;
}
return
val
;
}
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
;
jsoff_t
new_off
=
fwd_lookup
(
&
ctx
->
fwd
,
old_off
);
if
(
new_off
!=
(
jsoff_t
)
~
0
)
return
new_off
;
static
const
void
*
dispatch
[]
=
{
&&
l_obj
,
&&
l_prop
,
&&
l_str
,
&&
l_default
};
jsoff_t
header
=
gc_loadoff
(
ctx
->
js
->
mem
,
old_off
);
goto
*
dispatch
[
header
&
3
];
l_obj
:
new_off
=
gc_reserve_object
(
ctx
,
old_off
);
goto
l_done
;
l_prop
:
new_off
=
gc_reserve_prop
(
ctx
,
old_off
);
goto
l_done
;
l_str
:
new_off
=
gc_copy_string
(
ctx
,
old_off
);
goto
l_done
;
l_default
:
return
old_off
;
l_done
:
return
(
new_off
!=
(
jsoff_t
)
~
0
)
?
new_off
:
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
;
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
;
switch
(
type
)
{
case
T_OBJ
:
case
T_FUNC
:
case
T_ARR
:
case
T_PROMISE
:
case
T_GENERATOR
:
case
T_STR
:
case
T_PROP
:
case
T_BIGINT
:
{
jsoff_t
new_off
=
fwd_lookup
(
&
ctx
->
fwd
,
old_off
);
if
(
new_off
!=
(
jsoff_t
)
~
0
)
return
gc_mkval
(
type
,
new_off
);
break
;
}
default
:
break
;
}
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
);
}
static
jsoff_t
gc_weak_off_callback
(
void
*
ctx_ptr
,
jsoff_t
old_off
)
{
gc_ctx_t
*
ctx
=
(
gc_ctx_t
*
)
ctx_ptr
;
if
(
old_off
>=
ctx
->
js
->
brk
)
return
old_off
;
return
fwd_lookup
(
&
ctx
->
fwd
,
old_off
);
}
size_t
js_gc_compact
(
ant_t
*
js
)
{
if
(
!
js
||
js
->
brk
==
0
)
return
0
;
if
(
js
->
brk
<
2
*
1024
*
1024
)
return
0
;
mco_coro
*
running
=
mco_running
();
int
in_coroutine
=
(
running
!=
NULL
&&
running
->
stack_base
!=
NULL
);
if
(
in_coroutine
)
{
js
->
needs_gc
=
true
;
return
0
;
}
time_t
now
=
time
(
NULL
);
if
(
now
!=
(
time_t
)
-1
&&
gc_last_run_time
!=
0
)
{
double
elapsed
=
difftime
(
now
,
gc_last_run_time
);
double
cooldown
;
if
(
js
->
brk
>
64
*
1024
*
1024
)
cooldown
=
0.5
;
else
if
(
js
->
brk
>
16
*
1024
*
1024
)
cooldown
=
1.0
;
else
if
(
js
->
brk
>
4
*
1024
*
1024
)
cooldown
=
2.0
;
else
cooldown
=
4.0
;
if
(
elapsed
>=
0.0
&&
elapsed
<
cooldown
&&
js
->
gc_alloc_since
<
js
->
brk
/
4
)
return
0
;
}
if
(
now
!=
(
time_t
)
-1
)
gc_last_run_time
=
now
;
size_t
old_brk
=
js
->
brk
;
size_t
new_size
=
js
->
size
;
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
;
uint8_t
*
new_mem
=
gc_scratch_buf
;
size_t
bitmap_size
=
(
js
->
brk
/
8
+
7
)
/
8
+
1
;
uint8_t
*
mark_bits
=
(
uint8_t
*
)
calloc
(
1
,
bitmap_size
);
if
(
!
mark_bits
)
return
0
;
size_t
estimated_objs
=
js
->
brk
/
64
;
if
(
estimated_objs
<
256
)
estimated_objs
=
256
;
gc_ctx_t
ctx
;
ctx
.
js
=
js
;
ctx
.
new_mem
=
new_mem
;
ctx
.
new_brk
=
0
;
ctx
.
new_size
=
(
jsoff_t
)
new_size
;
ctx
.
mark_bits
=
mark_bits
;
if
(
!
fwd_init
(
&
ctx
.
fwd
,
estimated_objs
))
{
free
(
mark_bits
);
return
0
;
}
if
(
!
work_init
(
&
ctx
.
work
,
estimated_objs
/
4
<
64
?
64
:
estimated_objs
/
4
))
{
fwd_free
(
&
ctx
.
fwd
);
free
(
mark_bits
);
return
0
;
}
ctx
.
failed
=
false
;
if
(
js
->
brk
>
0
)
{
jsoff_t
header_at_0
=
gc_loadoff
(
js
->
mem
,
0
);
if
((
header_at_0
&
3
)
==
T_OBJ
)
gc_reserve_object
(
&
ctx
,
0
);
}
js_gc_reserve_roots
(
js
,
gc_fwd_off_callback
,
gc_fwd_val_callback
,
&
ctx
);
gc_drain_work_queue
(
&
ctx
);
if
(
ctx
.
failed
)
{
free
(
mark_bits
);
work_free
(
&
ctx
.
work
);
fwd_free
(
&
ctx
.
fwd
);
return
0
;
}
js_gc_update_roots
(
js
,
gc_apply_off_callback
,
gc_weak_off_callback
,
gc_apply_val_callback
,
&
ctx
);
memcpy
(
js
->
mem
,
new_mem
,
ctx
.
new_brk
);
js
->
brk
=
ctx
.
new_brk
;
free
(
mark_bits
);
work_free
(
&
ctx
.
work
);
fwd_free
(
&
ctx
.
fwd
);
if
(
gc_scratch_buf
&&
gc_scratch_size
>
0
)
{
RELEASE_PAGES
(
gc_scratch_buf
,
gc_scratch_size
);
}
size_t
new_brk
=
ctx
.
new_brk
;
size_t
old_size
=
js
->
size
;
if
(
new_brk
<
old_size
/
2
&&
old_size
>
ARENA_GROW_INCREMENT
)
{
size_t
target
=
((
new_brk
*
2
+
ARENA_GROW_INCREMENT
-
1
)
/
ARENA_GROW_INCREMENT
)
*
ARENA_GROW_INCREMENT
;
if
(
target
<
ARENA_GROW_INCREMENT
)
target
=
ARENA_GROW_INCREMENT
;
if
(
target
<
old_size
)
{
ant_arena_decommit
(
js
->
mem
,
old_size
,
target
);
js
->
size
=
(
jsoff_t
)
target
;
}
}
return
(
old_brk
>
new_brk
?
old_brk
-
new_brk
:
0
);
}
void
js_gc_maybe
(
ant_t
*
js
)
{
// Nursery scavenge (fast, frequent) - do this first
if
(
js
->
nursery_full
&&
js
->
nursery
)
{
nursery_scavenge
(
js
);
js
->
nursery_full
=
false
;
}
jsoff_t
thresh
=
js
->
brk
/
4
;
jsoff_t
min_thresh
=
gc_throttled
?
8
*
1024
*
1024
:
2
*
1024
*
1024
;
jsoff_t
max_thresh
=
gc_throttled
?
64
*
1024
*
1024
:
16
*
1024
*
1024
;
if
(
thresh
<
min_thresh
)
thresh
=
min_thresh
;
if
(
thresh
>
max_thresh
)
thresh
=
max_thresh
;
if
(
js
->
gc_alloc_since
>
thresh
||
js
->
needs_gc
)
{
js
->
needs_gc
=
false
;
if
(
js_gc_compact
(
js
)
>
0
)
js
->
gc_alloc_since
=
0
;
}
}
// ============================================================================
// Generational GC: Nursery Implementation
// ============================================================================
bool
nursery_init
(
ant_t
*
js
,
size_t
size
)
{
if
(
size
<
NURSERY_SIZE_MIN
)
size
=
NURSERY_SIZE_MIN
;
if
(
size
>
NURSERY_SIZE_MAX
)
size
=
NURSERY_SIZE_MAX
;
js
->
nursery
=
(
uint8_t
*
)
gc_mmap
(
size
);
if
(
!
js
->
nursery
)
return
false
;
js
->
nursery_ptr
=
0
;
js
->
nursery_size
=
(
jsoff_t
)
size
;
js
->
nursery_full
=
false
;
js
->
nursery_enabled
=
false
;
// enabled after snapshot loads
js
->
remembered_set
=
NULL
;
js
->
rs_count
=
0
;
js
->
rs_cap
=
0
;
return
true
;
}
void
nursery_enable
(
ant_t
*
js
)
{
if
(
js
->
nursery
)
{
js
->
nursery_enabled
=
true
;
}
}
void
nursery_free
(
ant_t
*
js
)
{
if
(
js
->
nursery
)
{
gc_munmap
(
js
->
nursery
,
js
->
nursery_size
);
js
->
nursery
=
NULL
;
js
->
nursery_ptr
=
0
;
js
->
nursery_size
=
0
;
js
->
nursery_enabled
=
false
;
}
if
(
js
->
remembered_set
)
{
free
(
js
->
remembered_set
);
js
->
remembered_set
=
NULL
;
js
->
rs_count
=
0
;
js
->
rs_cap
=
0
;
}
}
// Add object to remembered set (old→young reference)
void
rs_add
(
ant_t
*
js
,
jsval_t
obj
)
{
if
(
js
->
rs_count
>=
js
->
rs_cap
)
{
size_t
new_cap
=
js
->
rs_cap
==
0
?
64
:
js
->
rs_cap
*
2
;
jsval_t
*
new_set
=
(
jsval_t
*
)
realloc
(
js
->
remembered_set
,
new_cap
*
sizeof
(
jsval_t
));
if
(
!
new_set
)
return
;
js
->
remembered_set
=
new_set
;
js
->
rs_cap
=
new_cap
;
}
js
->
remembered_set
[
js
->
rs_count
++
]
=
obj
;
}
void
rs_clear
(
ant_t
*
js
)
{
js
->
rs_count
=
0
;
}
// Nursery allocation with bump pointer (very fast)
jsoff_t
nursery_alloc
(
ant_t
*
js
,
size_t
size
)
{
size
=
(
size
+
7
)
&
~
(
size_t
)
7
;
// align to 8 bytes
if
(
js
->
nursery_ptr
+
size
>
js
->
nursery_size
)
{
js
->
nursery_full
=
true
;
return
~
(
jsoff_t
)
0
;
// signal nursery full
}
jsoff_t
off
=
js
->
nursery_ptr
;
js
->
nursery_ptr
+=
(
jsoff_t
)
size
;
return
off
|
NURSERY_BIT
;
// tag as nursery pointer
}
// Forward a nursery pointer to old gen, return new offset
static
jsoff_t
scavenge_copy_object
(
ant_t
*
js
,
jsoff_t
nursery_off
,
jsoff_t
*
fwd_table
,
size_t
fwd_cap
)
{
jsoff_t
raw_off
=
nursery_raw_off
(
nursery_off
);
// Check if already forwarded
if
(
raw_off
<
fwd_cap
&&
fwd_table
[
raw_off
>>
3
]
!=
0
)
{
return
fwd_table
[
raw_off
>>
3
];
}
// Get object header from nursery
jsoff_t
header
;
memcpy
(
&
header
,
&
js
->
nursery
[
raw_off
],
sizeof
(
header
));
// Calculate size based on type
size_t
size
;
uint8_t
type
=
header
&
3
;
if
(
type
==
T_STR
)
{
if
(
header
&
ROPE_FLAG
)
{
size
=
sizeof
(
rope_node_t
);
}
else
{
size
=
esize
(
header
);
}
}
else
{
size
=
esize
(
header
);
}
if
(
size
==
(
size_t
)
~
0
||
size
==
0
)
{
size
=
24
;
// minimum object size
}
size
=
(
size
+
7
)
&
~
(
size_t
)
7
;
// Allocate in old gen
jsoff_t
available
=
js
->
size
-
js
->
brk
;
if
(
size
>
available
)
{
return
~
(
jsoff_t
)
0
;
// OOM
}
jsoff_t
new_off
=
js
->
brk
;
memcpy
(
&
js
->
mem
[
new_off
],
&
js
->
nursery
[
raw_off
],
size
);
js
->
brk
+=
(
jsoff_t
)
size
;
// Install forwarding pointer
if
(
raw_off
<
fwd_cap
)
{
fwd_table
[
raw_off
>>
3
]
=
new_off
;
}
return
new_off
;
}
// Context for scavenge_op_val callback
typedef
struct
{
ant_t
*
js
;
jsoff_t
*
fwd_table
;
size_t
fwd_cap
;
}
scavenge_ctx_t
;
static
jsval_t
scavenge_update_val
(
ant_t
*
js
,
jsval_t
val
,
jsoff_t
*
fwd_table
,
size_t
fwd_cap
);
// Wrapper callbacks for js_gc_scavenge_roots during scavenge
static
void
scavenge_op_val
(
void
*
ctx
,
jsval_t
*
val
)
{
scavenge_ctx_t
*
sctx
=
(
scavenge_ctx_t
*
)
ctx
;
*
val
=
scavenge_update_val
(
sctx
->
js
,
*
val
,
sctx
->
fwd_table
,
sctx
->
fwd_cap
);
}
static
void
scavenge_op_off
(
void
*
ctx
,
jsoff_t
*
off
)
{
scavenge_ctx_t
*
sctx
=
(
scavenge_ctx_t
*
)
ctx
;
if
(
!
is_nursery_off
(
*
off
))
return
;
jsoff_t
new_off
=
scavenge_copy_object
(
sctx
->
js
,
*
off
,
sctx
->
fwd_table
,
sctx
->
fwd_cap
);
if
(
new_off
!=
~
(
jsoff_t
)
0
)
*
off
=
new_off
;
}
// Recursively scan the scope chain and update parent pointers + properties
static
void
scavenge_scan_scope_chain
(
ant_t
*
js
,
jsoff_t
scope_off
,
jsoff_t
*
fwd_table
,
size_t
fwd_cap
,
int
depth
);
// Scan all properties of an old-gen object for nursery references
static
void
scavenge_scan_object_props
(
ant_t
*
js
,
jsoff_t
obj_off
,
jsoff_t
*
fwd_table
,
size_t
fwd_cap
)
{
if
(
obj_off
>=
js
->
brk
||
is_nursery_off
(
obj_off
))
return
;
jsoff_t
header
=
gc_loadoff
(
js
->
mem
,
obj_off
);
jsoff_t
first_prop
=
header
&
~
(
3ULL
|
FLAGMASK
);
// If first property is in nursery, copy it and update object header
if
(
first_prop
!=
0
&&
is_nursery_off
(
first_prop
))
{
jsoff_t
new_first
=
scavenge_copy_object
(
js
,
first_prop
,
fwd_table
,
fwd_cap
);
if
(
new_first
!=
~
(
jsoff_t
)
0
)
{
jsoff_t
new_header
=
(
new_first
&
~
3ULL
)
|
(
header
&
(
3ULL
|
FLAGMASK
));
gc_saveoff
(
js
->
mem
,
obj_off
,
new_header
);
first_prop
=
new_first
;
}
}
jsoff_t
prop_off
=
first_prop
;
while
(
prop_off
!=
0
&&
prop_off
<
js
->
brk
&&
!
is_nursery_off
(
prop_off
))
{
jsoff_t
prop_header
=
gc_loadoff
(
js
->
mem
,
prop_off
);
jsoff_t
next_prop
=
prop_header
&
~
(
3ULL
|
FLAGMASK
);
// If next property is in nursery, copy it and update current prop's header
if
(
next_prop
!=
0
&&
is_nursery_off
(
next_prop
))
{
jsoff_t
new_next
=
scavenge_copy_object
(
js
,
next_prop
,
fwd_table
,
fwd_cap
);
if
(
new_next
!=
~
(
jsoff_t
)
0
)
{
jsoff_t
new_prop_header
=
(
new_next
&
~
3ULL
)
|
(
prop_header
&
(
3ULL
|
FLAGMASK
));
gc_saveoff
(
js
->
mem
,
prop_off
,
new_prop_header
);
next_prop
=
new_next
;
}
}
// Update property key if it points to nursery
jsoff_t
key_off
=
gc_loadoff
(
js
->
mem
,
prop_off
+
sizeof
(
jsoff_t
));
if
(
is_nursery_off
(
key_off
))
{
jsoff_t
new_key
=
scavenge_copy_object
(
js
,
key_off
,
fwd_table
,
fwd_cap
);
if
(
new_key
!=
~
(
jsoff_t
)
0
)
{
gc_saveoff
(
js
->
mem
,
prop_off
+
sizeof
(
jsoff_t
),
new_key
);
}
}
// Update property value if it points to nursery
jsval_t
prop_val
=
gc_loadval
(
js
->
mem
,
prop_off
+
sizeof
(
jsoff_t
)
*
2
);
jsval_t
new_val
=
scavenge_update_val
(
js
,
prop_val
,
fwd_table
,
fwd_cap
);
if
(
new_val
!=
prop_val
)
{
gc_saveval
(
js
->
mem
,
prop_off
+
sizeof
(
jsoff_t
)
*
2
,
new_val
);
}
prop_off
=
next_prop
;
}
}
// Recursively scan the scope chain and update parent pointers + properties
static
void
scavenge_scan_scope_chain
(
ant_t
*
js
,
jsoff_t
scope_off
,
jsoff_t
*
fwd_table
,
size_t
fwd_cap
,
int
depth
)
{
if
(
depth
>
255
)
return
;
// prevent infinite recursion
if
(
scope_off
==
0
||
is_nursery_off
(
scope_off
)
||
scope_off
>=
js
->
brk
)
return
;
// Scan this scope's properties
scavenge_scan_object_props
(
js
,
scope_off
,
fwd_table
,
fwd_cap
);
// Get parent scope offset (stored at scope_off + sizeof(jsoff_t))
jsoff_t
parent_off
=
gc_loadoff
(
js
->
mem
,
scope_off
+
sizeof
(
jsoff_t
));
// If parent is in nursery, copy it and update the pointer
if
(
parent_off
!=
0
&&
is_nursery_off
(
parent_off
))
{
jsoff_t
new_parent
=
scavenge_copy_object
(
js
,
parent_off
,
fwd_table
,
fwd_cap
);
if
(
new_parent
!=
~
(
jsoff_t
)
0
)
{
gc_saveoff
(
js
->
mem
,
scope_off
+
sizeof
(
jsoff_t
),
new_parent
);
parent_off
=
new_parent
;
}
}
// Recurse into parent scope
if
(
parent_off
!=
0
&&
!
is_nursery_off
(
parent_off
)
&&
parent_off
<
js
->
brk
)
{
scavenge_scan_scope_chain
(
js
,
parent_off
,
fwd_table
,
fwd_cap
,
depth
+
1
);
}
}
// Update a jsval_t if it points to nursery
static
jsval_t
scavenge_update_val
(
ant_t
*
js
,
jsval_t
val
,
jsoff_t
*
fwd_table
,
size_t
fwd_cap
)
{
if
((
val
>>
53
)
!=
NANBOX_PREFIX_CHK
)
return
val
;
// not a tagged value
uint8_t
type
=
(
val
>>
NANBOX_TYPE_SHIFT
)
&
NANBOX_TYPE_MASK
;
jsoff_t
off
=
(
jsoff_t
)(
val
&
NANBOX_DATA_MASK
);
if
(
!
is_nursery_off
(
off
))
return
val
;
// not in nursery
switch
(
type
)
{
case
T_OBJ
:
case
T_FUNC
:
case
T_ARR
:
case
T_PROMISE
:
case
T_GENERATOR
:
case
T_STR
:
case
T_PROP
:
case
T_BIGINT
:
{
jsoff_t
new_off
=
scavenge_copy_object
(
js
,
off
,
fwd_table
,
fwd_cap
);
if
(
new_off
!=
~
(
jsoff_t
)
0
)
{
return
NANBOX_PREFIX
|
((
jsval_t
)(
type
&
NANBOX_TYPE_MASK
)
<<
NANBOX_TYPE_SHIFT
)
|
(
new_off
&
NANBOX_DATA_MASK
);
}
break
;
}
default
:
break
;
}
return
val
;
}
// Scavenge nursery: copy live objects to old gen
void
nursery_scavenge
(
ant_t
*
js
)
{
if
(
!
js
->
nursery
||
js
->
nursery_ptr
==
0
)
{
js
->
nursery_full
=
false
;
return
;
}
mco_coro
*
running
=
mco_running
();
if
(
running
!=
NULL
&&
running
->
stack_base
!=
NULL
)
{
return
;
}
// Allocate forwarding table (indexed by nursery offset >> 3)
size_t
fwd_cap
=
js
->
nursery_ptr
+
8
;
jsoff_t
*
fwd_table
=
(
jsoff_t
*
)
calloc
(
fwd_cap
>>
3
,
sizeof
(
jsoff_t
));
if
(
!
fwd_table
)
{
js
->
nursery_ptr
=
0
;
rs_clear
(
js
);
return
;
}
jsoff_t
scan_start
=
js
->
brk
;
// 1. Scan direct roots and copy nursery objects
// Process remembered set (old gen objects pointing to nursery)
for
(
size_t
i
=
0
;
i
<
js
->
rs_count
;
i
++
)
{
jsval_t
old_obj
=
js
->
remembered_set
[
i
];
jsoff_t
obj_off
=
(
jsoff_t
)(
old_obj
&
NANBOX_DATA_MASK
);
if
(
is_nursery_off
(
obj_off
))
continue
;
// skip nursery objects in rs
if
(
obj_off
>=
js
->
brk
)
continue
;
// Scan object's properties for nursery references
jsoff_t
header
=
gc_loadoff
(
js
->
mem
,
obj_off
);
jsoff_t
prop_off
=
header
&
~
(
3ULL
|
FLAGMASK
);
while
(
prop_off
!=
0
&&
prop_off
<
js
->
brk
)
{
jsoff_t
prop_header
=
gc_loadoff
(
js
->
mem
,
prop_off
);
// Update property value if it points to nursery
jsval_t
prop_val
=
gc_loadval
(
js
->
mem
,
prop_off
+
sizeof
(
jsoff_t
)
*
2
);
jsval_t
new_val
=
scavenge_update_val
(
js
,
prop_val
,
fwd_table
,
fwd_cap
);
if
(
new_val
!=
prop_val
)
{
gc_saveval
(
js
->
mem
,
prop_off
+
sizeof
(
jsoff_t
)
*
2
,
new_val
);
}
prop_off
=
prop_header
&
~
(
3ULL
|
FLAGMASK
);
}
}
// 2. Scan all runtime roots (scope stacks, for_let_stack, this_stack,
// coroutines, promise registry, timers, modules, etc.)
scavenge_ctx_t
sctx
=
{
.
js
=
js
,
.
fwd_table
=
fwd_table
,
.
fwd_cap
=
fwd_cap
};
js_gc_scavenge_roots
(
js
,
scavenge_op_val
,
scavenge_op_off
,
&
sctx
);
// Scan global object properties for nursery references
jsoff_t
global_off
=
(
jsoff_t
)(
js
->
global
&
NANBOX_DATA_MASK
);
scavenge_scan_object_props
(
js
,
global_off
,
fwd_table
,
fwd_cap
);
// Scan the entire scope chain (current scope + all parents)
jsoff_t
scope_off
=
(
jsoff_t
)(
js
->
scope
&
NANBOX_DATA_MASK
);
if
(
!
is_nursery_off
(
scope_off
))
{
scavenge_scan_scope_chain
(
js
,
scope_off
,
fwd_table
,
fwd_cap
,
0
);
}
// 3. Cheney scan: process copied objects for more references
jsoff_t
scan
=
scan_start
;
int
obj_count
=
0
;
while
(
scan
<
js
->
brk
)
{
jsoff_t
header
=
gc_loadoff
(
js
->
mem
,
scan
);
uint8_t
type
=
header
&
3
;
size_t
obj_size
;
// Handle rope nodes specially (strings with ROPE_FLAG set)
if
(
type
==
T_STR
&&
(
header
&
ROPE_FLAG
))
{
obj_size
=
sizeof
(
rope_node_t
);
}
else
{
obj_size
=
esize
(
header
);
}
if
(
obj_size
==
(
size_t
)
~
0
)
obj_size
=
24
;
obj_size
=
(
obj_size
+
7
)
&
~
(
size_t
)
7
;
obj_count
++
;
if
(
type
==
T_OBJ
||
type
==
T_PROP
)
{
// Update first prop/next prop
jsoff_t
prop_ref
=
header
&
~
(
3ULL
|
FLAGMASK
);
if
(
is_nursery_off
(
prop_ref
))
{
jsoff_t
new_ref
=
scavenge_copy_object
(
js
,
prop_ref
,
fwd_table
,
fwd_cap
);
if
(
new_ref
!=
~
(
jsoff_t
)
0
)
{
header
=
(
new_ref
&
~
3ULL
)
|
(
header
&
(
3ULL
|
FLAGMASK
));
gc_saveoff
(
js
->
mem
,
scan
,
header
);
}
}
if
(
type
==
T_OBJ
)
{
// Update parent
jsoff_t
parent
=
gc_loadoff
(
js
->
mem
,
scan
+
sizeof
(
jsoff_t
));
if
(
is_nursery_off
(
parent
))
{
jsoff_t
new_parent
=
scavenge_copy_object
(
js
,
parent
,
fwd_table
,
fwd_cap
);
if
(
new_parent
!=
~
(
jsoff_t
)
0
)
{
gc_saveoff
(
js
->
mem
,
scan
+
sizeof
(
jsoff_t
),
new_parent
);
}
}
// Update tail
jsoff_t
tail
=
gc_loadoff
(
js
->
mem
,
scan
+
sizeof
(
jsoff_t
)
*
2
);
if
(
is_nursery_off
(
tail
))
{
jsoff_t
new_tail
=
scavenge_copy_object
(
js
,
tail
,
fwd_table
,
fwd_cap
);
if
(
new_tail
!=
~
(
jsoff_t
)
0
)
{
gc_saveoff
(
js
->
mem
,
scan
+
sizeof
(
jsoff_t
)
*
2
,
new_tail
);
}
}
}
else
if
(
type
==
T_PROP
)
{
// Update property key (string offset stored at scan + sizeof(jsoff_t))
jsoff_t
key_off
=
gc_loadoff
(
js
->
mem
,
scan
+
sizeof
(
jsoff_t
));
if
(
is_nursery_off
(
key_off
))
{
jsoff_t
new_key
=
scavenge_copy_object
(
js
,
key_off
,
fwd_table
,
fwd_cap
);
if
(
new_key
!=
~
(
jsoff_t
)
0
)
{
gc_saveoff
(
js
->
mem
,
scan
+
sizeof
(
jsoff_t
),
new_key
);
}
}
// Update property value
jsval_t
prop_val
=
gc_loadval
(
js
->
mem
,
scan
+
sizeof
(
jsoff_t
)
*
2
);
jsval_t
new_val
=
scavenge_update_val
(
js
,
prop_val
,
fwd_table
,
fwd_cap
);
if
(
new_val
!=
prop_val
)
{
gc_saveval
(
js
->
mem
,
scan
+
sizeof
(
jsoff_t
)
*
2
,
new_val
);
}
}
}
else
if
(
type
==
T_STR
&&
(
header
&
ROPE_FLAG
))
{
// Rope node - update left/right/cached
jsval_t
left
=
gc_loadval
(
js
->
mem
,
scan
+
offsetof
(
rope_node_t
,
left
));
jsval_t
right
=
gc_loadval
(
js
->
mem
,
scan
+
offsetof
(
rope_node_t
,
right
));
jsval_t
cached
=
gc_loadval
(
js
->
mem
,
scan
+
offsetof
(
rope_node_t
,
cached
));
jsval_t
new_left
=
scavenge_update_val
(
js
,
left
,
fwd_table
,
fwd_cap
);
jsval_t
new_right
=
scavenge_update_val
(
js
,
right
,
fwd_table
,
fwd_cap
);
jsval_t
new_cached
=
scavenge_update_val
(
js
,
cached
,
fwd_table
,
fwd_cap
);
if
(
new_left
!=
left
)
gc_saveval
(
js
->
mem
,
scan
+
offsetof
(
rope_node_t
,
left
),
new_left
);
if
(
new_right
!=
right
)
gc_saveval
(
js
->
mem
,
scan
+
offsetof
(
rope_node_t
,
right
),
new_right
);
if
(
new_cached
!=
cached
)
gc_saveval
(
js
->
mem
,
scan
+
offsetof
(
rope_node_t
,
cached
),
new_cached
);
}
scan
+=
obj_size
;
}
// 4. Reset nursery
js
->
nursery_ptr
=
0
;
js
->
nursery_full
=
false
;
rs_clear
(
js
);
// Debug: check scope chain depth and global_scope_stack
{
jsoff_t
dbg_off
=
(
jsoff_t
)(
js
->
scope
&
NANBOX_DATA_MASK
);
int
chain_depth
=
0
;
while
(
dbg_off
!=
0
&&
chain_depth
<
50
)
{
if
(
is_nursery_off
(
dbg_off
)
||
dbg_off
>=
js
->
brk
)
break
;
dbg_off
=
gc_loadoff
(
js
->
mem
,
dbg_off
+
sizeof
(
jsoff_t
));
chain_depth
++
;
}
int
stack_len
=
global_scope_stack
?
(
int
)
utarray_len
(
global_scope_stack
)
:
0
;
fprintf
(
stderr
,
"SCAVENGE: scope chain depth=%d, stack_len=%d, scope=0x%llx, global=0x%llx
\n
"
,
chain_depth
,
stack_len
,
(
unsigned
long
long
)(
js
->
scope
&
NANBOX_DATA_MASK
),
(
unsigned
long
long
)(
js
->
global
&
NANBOX_DATA_MASK
));
if
(
stack_len
>
0
)
{
jsoff_t
*
first
=
(
jsoff_t
*
)
utarray_eltptr
(
global_scope_stack
,
0
);
fprintf
(
stderr
,
"SCAVENGE: scope_stack[0]=0x%llx
\n
"
,
(
unsigned
long
long
)
*
first
);
}
}
// Debug: verify no nursery refs remain in scope chain after scavenge
{
jsoff_t
dbg_off
=
(
jsoff_t
)(
js
->
scope
&
NANBOX_DATA_MASK
);
int
dbg_depth
=
0
;
while
(
dbg_off
!=
0
&&
dbg_depth
<
50
)
{
if
(
is_nursery_off
(
dbg_off
))
{
fprintf
(
stderr
,
"POST-SCAVENGE BUG: scope chain[%d] is nursery 0x%llx
\n
"
,
dbg_depth
,
(
unsigned
long
long
)
dbg_off
);
break
;
}
if
(
dbg_off
>=
js
->
brk
)
{
fprintf
(
stderr
,
"POST-SCAVENGE BUG: scope chain[%d] OOB 0x%llx (brk=0x%llx)
\n
"
,
dbg_depth
,
(
unsigned
long
long
)
dbg_off
,
(
unsigned
long
long
)
js
->
brk
);
break
;
}
jsoff_t
hdr
=
gc_loadoff
(
js
->
mem
,
dbg_off
);
jsoff_t
fp
=
hdr
&
~
(
3ULL
|
FLAGMASK
);
if
(
fp
!=
0
&&
is_nursery_off
(
fp
))
{
fprintf
(
stderr
,
"POST-SCAVENGE BUG: scope[%d] first_prop nursery 0x%llx
\n
"
,
dbg_depth
,
(
unsigned
long
long
)
fp
);
}
jsoff_t
pp
=
fp
;
int
pi
=
0
;
while
(
pp
!=
0
&&
!
is_nursery_off
(
pp
)
&&
pp
<
js
->
brk
&&
pi
<
500
)
{
jsoff_t
ph
=
gc_loadoff
(
js
->
mem
,
pp
);
jsoff_t
np
=
ph
&
~
(
3ULL
|
FLAGMASK
);
if
(
np
!=
0
&&
is_nursery_off
(
np
))
fprintf
(
stderr
,
"POST-SCAVENGE BUG: scope[%d].prop[%d] next nursery 0x%llx
\n
"
,
dbg_depth
,
pi
,
(
unsigned
long
long
)
np
);
jsoff_t
koff
=
gc_loadoff
(
js
->
mem
,
pp
+
sizeof
(
jsoff_t
));
if
(
is_nursery_off
(
koff
))
fprintf
(
stderr
,
"POST-SCAVENGE BUG: scope[%d].prop[%d] key nursery 0x%llx
\n
"
,
dbg_depth
,
pi
,
(
unsigned
long
long
)
koff
);
jsval_t
pv
=
gc_loadval
(
js
->
mem
,
pp
+
sizeof
(
jsoff_t
)
*
2
);
jsoff_t
pv_off
=
(
jsoff_t
)(
pv
&
NANBOX_DATA_MASK
);
if
((
pv
>>
53
)
==
NANBOX_PREFIX_CHK
&&
is_nursery_off
(
pv_off
))
fprintf
(
stderr
,
"POST-SCAVENGE BUG: scope[%d].prop[%d] val nursery 0x%llx
\n
"
,
dbg_depth
,
pi
,
(
unsigned
long
long
)
pv_off
);
pp
=
np
;
pi
++
;
}
dbg_off
=
gc_loadoff
(
js
->
mem
,
dbg_off
+
sizeof
(
jsoff_t
));
dbg_depth
++
;
}
// Also check global_scope_stack entries
if
(
global_scope_stack
)
{
jsoff_t
*
sp
=
NULL
;
int
si
=
0
;
while
((
sp
=
(
jsoff_t
*
)
utarray_next
(
global_scope_stack
,
sp
))
!=
NULL
)
{
if
(
is_nursery_off
(
*
sp
))
fprintf
(
stderr
,
"POST-SCAVENGE BUG: scope_stack[%d] nursery 0x%llx
\n
"
,
si
,
(
unsigned
long
long
)
*
sp
);
si
++
;
}
}
}
free
(
fwd_table
);
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Fri, Mar 27, 5:18 AM (2 d)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
512623
Default Alt Text
gc.c (36 KB)
Attached To
Mode
rANT Ant
Attached
Detach File
Event Timeline
Log In to Comment