SlideShare a Scribd company logo
1 of 59
Ocaml Internal 
Hyunchul Park, PL lab, POSTECH
mlvalue 
(Ocaml value, mlvalues.h) 
• Immediate value 
• Block 
(a pointer into the Ocaml heap, 
structured value) 
• No scan tag 
(a pointer pointing outside Ocaml heap)
mlvalue 
Word 
Immediate value 1 
Word 
Address to heap block 0 0 
Immediate value 
Block 
Pointer to heap 
block 
Heap 할당시 align 만큼 0으로 셋팅
Ocaml heap block structure 
Header 
: Hd_val(v) 
Value v 
Field는 또 다른 value 
Field (v, 0) … Field (v, 
Wosize_val(v)-1 ) 
Address to heap block 0 0 
Block 
Word – 10bit 2bit 8bit 
Size : Wosize_hd(hd) Color 
: Color_hd(hd) 
Tag 
: Tag_hd(hd)
Young heap 
caml_young_start caml_young_ptr caml_young_end 
 Grow Allocated 
• caml_aligned_malloc함수가 할당한 메모리 
Minor_heap_def 크기로 할당됨 
• Page_size 기준으로 align 된 주소 
• Memory.h:Alloc_small 매크로 함수를 참고하면 간단한 
할당 과정을 알수있다.
Old heap 
Heap chunk 
Heap block (alive) 
Empty blocks (blue color) 
Heap chunk 
chain 
Heap chunk dependency 
(Block field pointing to anoth 
er block)
caml_ref_table, caml_weak_ref_table 
table.base table.ptr table.threshold(??limit) 
addresses of value  Increase by one word 
p1 (address in old heap) 
Value v = p2 
p2 (address in young heap) 
Value in young heap 
• old heap에서 young heap으로 가리키는 경우 table에 추가해 둔다. 
Mutable ref 때문에 old heap에서 young heap을 참조하는 경우가 
발생 
• caml_alloc_table에서 caml_stat_alloc으로 할당 
caml_realloc_ref_table참고 
• value(pointer to block)을 저장하는 것이 아니라, value를 저장하는 
word address를 저장한다. Oldify에서 필요로 하기때문
Oldify 
• Oldify는 promote와 같은 개념, young heap에 있는 block을 old heap으로 옮긴다. 
• Oldify와 관련된 함수, 변수들 
– void caml_oldify_one (value v, value *p) 
• p(pointer to value) --> v(value) --> block in young heap 
• 실제로 블록의 promot를 실행한다. 블록의 모든 필드를 따라가면서 처리하지는 않고 
oldify_todo_list에 보관한다. caml_oldify_mopup 참고 
– void caml_oldify_mopup (void) 
– #define Oldify(p) 
• 먼저 value가 young heap에 있는 블록인지 확인하고, caml_oldify_one을 호출한다. 
• p --> __oldify__v__ --> block in young heap 
– static value oldify_todo_list = 0; 
– void caml_oldify_mopup (void)
void caml_oldify_one (value v, value *p) 
void caml_oldify_one (value v, value *p) { 
value result; 
header_t hd; 
mlsize_t sz, i; 
tag_t tag; 
tail_call: 
if (Is_block (v) && Is_young (v)){ 
Assert (Hp_val (v) >= caml_young_ptr); 
hd = Hd_val (v); 
if (hd == 0){ /* If already forwarded */ 
*p = Field (v, 0); /* then forward pointer is first field. */ 
}else{ 
tag = Tag_hd (hd); 
if (tag < Infix_tag){/* 첫번째 일반적인 케이스 */ 
value field0; 
sz = Wosize_hd (hd); 
result = caml_alloc_shr (sz, tag); 
*p = result; 
field0 = Field (v, 0); 
Hd_val (v) = 0; /* Set forward flag */ 
Field (v, 0) = result; /* and forward pointer. */ 
if (sz > 1){ 
Field (result, 0) = field0; 
Field (result, 1) = oldify_todo_list; /* Add this block */ 
oldify_todo_list = v; /* to the "to do" list. */ 
}else{ 
Assert (sz == 1); 
p = &Field (result, 0); 
v = field0; 
goto tail_call; 
} 
}else if (tag >= No_scan_tag){ 
/* tag >= No_scan_tag 경우는 다음 슬라이드에서 */ 
}else if (tag == Infix_tag){ 
mlsize_t offset = Infix_offset_hd (hd); 
caml_oldify_one (v - offset, p); /* Cannot recurse deeper than 1. */ 
*p += offset; 
}else{ 
/* 다음 슬라이드에서 */ 
} 
}else{ 
*p = v; 
} 
} 
• v : 현재 young heap 할당된 블록으로 oldify 대상이다 
• result : old heap의 블록을 가리키는 value 
• p : result를 저장할 어떤 word를 가리킨다. 최종적으로 p(word)result 
• otl(oldify_todo_list) : oldify는 필드에 대해서 재귀적으로 수행해 주어야 하는 
데 otl linked list에 넣어두고 caml_oldify_mopup에서 처리한다. 
Oldify_todo_list는 linked list의 해드에 해당한다. 
• next_otl : 코드에서 oldify_todo_list에 추가할 해드 노드를 마련했다. 원래 otl 
은 linked list의 next node로 가리킨다. 
p 
val0 
v 
val1 
*p 
hd Field(v,0) … Field(v,sz-1) 
result 
val2 
hd … 
p val1 
val0 
v 
*p 
val2 
위의 그림은 
result = caml_alloc_shr (sz, tag); 코드를 
실행한 시점 
밑에 그림은 첫번째 케이스로 실행한 최종 결과 
oldify_todo_list 
val1 
0 val2 Field(v,i) … 
result 
val2 
hd Field(v,0) next_otl uninit 
0 
oldify_todo_list의 다음 노드(young heap)
void caml_oldify_one (value v, value *p) 
• Forward란 
이전 슬라이드에서 old heap에 caml_alloc_shr 
로 할당하고 young heap 블록에서 가리키는 관 
계를 설정해 놓은 경우, young heap 블록을 
forwarded 라고 한다. 
– Old heap value(r), young heap value(orig_v, v), 
pointer to target value(p) 
– Forwarded condition 
이전 슬라이드 최종상태와 같이, Hd(v)==0이고 
Field(v,0)==r으로 old heap을 가리키고, 
Field(r,0)==Field(orig_v, 0)으로 첫번째 필드는 먼저 옮 
겨 놓음. Field(r,1)==next node of otl 
• hd == 0 
이전 슬라이드에서 처럼 forward 되었기 때문에, 
*p만 old heap value인 Field(v,0)로 assign 
• Tag >= No_scan_tag 
필드를 따라 가야할 필요가 없으므로 
caml_alloc_shr으로 old heap에 할당하고 
Field에 index마다 one-to-one assign 
여기서도 forward tag으로 설정했다. 
• Infix_tag 
value v 블록을 포함하는 바깥 블록을 v-offset으로 
잡는다. 바깥 블록 value으로 
caml_oldify 재귀호출하고 *p+=offse으로 다시 
infix 블록을 가리키도록 바꾸어 놓는다. 
void caml_oldify_one (value v, value *p) { 
value result; 
header_t hd; 
mlsize_t sz, i; 
tag_t tag; 
tail_call: 
if (Is_block (v) && Is_young (v)){ 
Assert (Hp_val (v) >= caml_young_ptr); 
hd = Hd_val (v); 
if (hd == 0){ /* If already forwarded */ 
*p = Field (v, 0); /* then forward pointer is first field. */ 
}else{ 
tag = Tag_hd (hd); 
if (tag < Infix_tag){ 
/* 이전 슬라이드에서 */ 
}else if (tag >= No_scan_tag){ 
sz = Wosize_hd (hd); 
result = caml_alloc_shr (sz, tag); 
for (i = 0; i < sz; i++) 
Field (result, i) = Field (v, i); 
Hd_val (v) = 0; /* Set forward flag */ 
Field (v, 0) = result; /* and forward pointer. */ 
*p = result; 
}else if (tag == Infix_tag){ 
mlsize_t offset = Infix_offset_hd (hd); 
caml_oldify_one (v - offset, p); /* Cannot recurse deeper than 1. */ 
*p += offset; 
}else{ 
} 
}else{ 
*p = v; 
} 
}
void caml_oldify_one (value v, value *p) 
• oldify_todo_list의 head node를 꺼내서 v로 갖 
는다. v(young heap value)와 old heap value의 
구조는 앞에서 forwarded block 조건을 따른다. 
• Field(new_v,0)으로 먼저 옮긴 Field(orig_v,0) 
을 caml_oldify_one으로 처리하고 
• 나머지 필드들도 마찬가지로 oldify 한다. 
• block이 아닌 경우라면 단순히 old heap block의 
필드에 대응하는 young heap block 필드를 assig 
한다. 
/* Finish the work that was put off by [caml_oldify_one]. 
Note that [caml_oldify_one] itself is called by oldify_mopup, so we 
have to be careful to remove the first entry from the list before 
oldifying its fields. */ 
void caml_oldify_mopup (void) 
{ 
value v, new_v, f; 
mlsize_t i; 
while (oldify_todo_list != 0){ 
v = oldify_todo_list; /* Get the head. */ 
Assert (Hd_val (v) == 0); /* It must be forwarded. */ 
new_v = Field (v, 0); /* Follow forward pointer. */ 
oldify_todo_list = Field (new_v, 1); /* Remove from list. */ 
f = Field (new_v, 0); 
if (Is_block (f) && Is_young (f)){ 
caml_oldify_one (f, &Field (new_v, 0)); 
} 
for (i = 1; i < Wosize_val (new_v); i++){ 
f = Field (v, i); 
if (Is_block (f) && Is_young (f)){ 
caml_oldify_one (f, &Field (new_v, i)); 
}else{ 
Field (new_v, i) = f; 
} 
} 
} 
} 
v 
val1 
oldify_todo_list 
0 val2 
new_v 
val2 
hd Field(v,0) = val3 next_otl uninit 
val1 
f 
val3 
caml_oldify_one 
v 
caml_oldify_one 
p
void caml_oldify_one (value v, value *p) 
• lazy_tag, forward_tag란? lazy.ml 주석참고 
– lazy_tag붙은 블록은 size=1 이며, 필드는 unit->’a type의 
closure이다. ‘a type의 블록의 evaluatio을 delay해 놓은 블록 
이다. 
– forward_tag 블록은 size=1이며, 필드는 ‘a type 이다. 즉 
computed value를 가리키는 포인터 
• f : forward_tag 블록이 가리키는 value 
• ft : f의 tag 
• vv = 0 
when Is_block(f) && !Is_young(f) 
&& !Is_in_value_area(f) 
• ft in [Forward_tag, Lazy_tag, Double_tag] 이 
면 forward_tag 블록을 생략하고 short-circuiting 
할 수 없다. TODO 구체적인 이유는?? 
• 이런 경우에는 forward_tag 블록도 old heap에 잡 
아놓고 f 블록도 oldify한다. 
• 그외의 경우에는 short-circuit(중간에 
indirectio으로 놓은 forward_tag 블록을 무시하 
고 바로 oldify 해서연결) 
void caml_oldify_one (value v, value *p) { 
value result; 
header_t hd; 
mlsize_t sz, i; 
tag_t tag; 
tail_call: 
if (Is_block (v) && Is_young (v)){ 
Assert (Hp_val (v) >= caml_young_ptr); 
hd = Hd_val (v); 
if (hd == 0){ /* If already forwarded */ 
*p = Field (v, 0); /* then forward pointer is first field. */ 
}else{ 
tag = Tag_hd (hd); 
if (tag < Infix_tag){ 
/* 이전 슬라이드에서 */ 
}else if (tag >= No_scan_tag){ 
}else if (tag == Infix_tag){ 
}else{ 
value f = Forward_val (v); 
tag_t ft = 0; 
int vv = 1; 
Assert (tag == Forward_tag); 
if (Is_block (f)){ 
if (Is_young (f)){ 
vv = 1; 
ft = Tag_val (Hd_val (f) == 0 ? Field (f, 0) : f); 
}else{ 
vv = Is_in_value_area(f); 
if (vv){ 
ft = Tag_val (f); 
} 
} 
} 
if (!vv || ft == Forward_tag || ft == Lazy_tag || ft == Double_tag){ 
/* Do not short-circuit the pointer. Copy as a normal block. */ 
Assert (Wosize_hd (hd) == 1); 
result = caml_alloc_shr (1, Forward_tag); 
*p = result; 
Hd_val (v) = 0; /* Set (GC) forward flag */ 
Field (v, 0) = result; /* and forward pointer. */ 
p = &Field (result, 0); 
v = f; 
goto tail_call; 
}else{ 
v = f; /* Follow the forwarding */ 
goto tail_call; /* then oldify. */ 
} 
} 
}else{ 
*p = v; 
} 
}
Heap chunk structure 
heap_chunk_head request 
padding1 heap_chunk_head Chunk body padding2 
typedef struct { 
hp (hp%Page_size==0) 
void *block; /* address of the malloced block this chunk live in */ 
asize_t alloc; /* in bytes, used for compaction */ 
asize_t size; /* in bytes */ 
char *next; 
} heap_chunk_head; 
• hp 주소는 Page_size단위로 align 
• chunck body부분의 Page_size 단위를 caml_page_table이라는 hash table로 관리하 
며, 주어진 주소 공간의 페이지가 어떤 용도로 사용되고 있는지 알아내는데 쓰인다. 
• Heap_chunk_head.next 로 연결하여 linked list를 구성한다. Linked list head는 
caml_heap_start 
• caml_alloc_shr, expand_heap 등 할당관련 함수 참고, call graph 참고 
• 실제 malloc, free가 발생하는 단위가 heap chunk 
할당된 chunk 안에서 block을 구성하여 사용한다.
Free list(old heap) 
Block 
Block 
• Chunk단위가 아니라, Block 단위로 free list를 관리한다. Free list는 사용중이지 
않은 block들을 엮은 linked list이다. 
• Major GC에서 해제할 block으로 확인되면 caml_fl_merge_block 호출하여 
freelist에 추가 
• caml_alloc_shr으로 블록을 할당할 때, caml_fl_allocate으로 free list에서 사용가 
능한 크기의 블록을 찾아본다. 
• Sentinel은 전역변수로 선언되어 있으며 free list linked list의 head역할을 한다. 
Linked list는 주소를 기준으로 정렬되어 있고 sentinel은 항상 가장낮은주소를 
갖는다. 
• Flp는 할당할때 효율성을 위해서 block 크기 순으로 증가하는 순서로 골라둔다. 
First_fit_polic에서만 사용됨 
• Caml_fl_merge, beyond 같은 free list block 포인터의 역할 참고 
Block 
Block 
Block 
Block 
Sentinel 
flp table
freelist.c(First_fit_policy 기준) flp table 
Fl_head 
sentinel 
flp table 
• Sentinel은 static global 변수로 잡혀 있으므로 항상 다른 블록보다 작은 주소 값을 갖는다. 
• 나머지 블록들은 free block이다. 즉, old heap의 블록이면서, 할당되지 못한 빈 공간에 해 
당하는 블록들이다. 
• free block들은 sentinel을 head로 하여 linked list chain을 구성한다. 다음 노드를 접근하는 
Next(b) 매크로를 제공한다. 블록의 주소 순서대로 정렬되어 있다. 
• flp table에는 주소가 증가하는 순서이면서 동시에 크기가 증가하는 순서로 블록을 골라두 
었다. 
• 정확히는 증가하는 크기로 선택된 블록의 바로 이전 블록들을 저장한다. 위에 그림에서 보 
면 빨간 선으로 연결된 블록들이 크기순을 선택된 블록이고, flp table은 그 바로 이전 블록 
들을 모아두었다. 
• 또한, flp table은 항상 모든 freelist에 대해서 관리되는 것은 아니다. 위에 그림처럼 뒷부분 
은 가리키지 않는 상태일 수 있다. 코드 내에 extend the flp array 부분 참고 
항상 모든 조건을 만족하도록 모든 freelist를 확인하지 않는다. flp는 일종의 cache 기능을 
한다. 만약 flp 탐색으로 실패하면 freelist를 하나씩 순회하며 찾는다.
freelist.c(First_fit_policy 기준) 함수 설명 
• static void fl_check (void) 
freelist가 제대로 관리되고 있는지 검사하는 디버그 코드 
• static char *allocate_block (mlsize_t wh_sz, int flpi, char *prev, char *cur) 
할당하기 사용할 블록을 flp table에서 찾았다. table에서 flpi 번째 블 
록이며, 바로전 블록은 prev, 찾은 대상블록은 cur이다. 블록을 사용할 만 
큼 잘라서 사용하고, 블록 헤더를 셋팅해 준다. 
• char *caml_fl_allocate (mlsize_t wo_sz) 
외부에서 호출하는 함수이다. wo_sz 크기의 블록을 freelist에서 찾아오라 
는 뜻이다. 없으면 NULL 리턴하고, 블록을 만들면 그 주소를 리턴 
• static void truncate_flp (char *changed) 
changed 주소 이후의 블록들을 flp table에서 빼버린다. 
• char *caml_fl_merge_block (char *bp) 
외부에서 호출하는 함수. freelist에 추가하고 싶은 블록을 넘기면 알아서 
처리한다. 필요시 연속된 blue 블락들을 merge한다. 
• Void caml_fl_add_blocks (char *bp) 
caml_alloc_shr에서 호출한다. 새로 할당한 (blue color의) 블록 체인을 
끼워 넣는다. Flp table은 업데이트 하지 않는듯하다(TODO로 아직 구현안 
함) 
• void caml_make_free_blocks (value *p, mlsize_t size, int 
do_merge, int color) 
old heap chunk만 만들어 놓았을때, 이거를 free block으로 쪼개서 free 
list에 넣는 작업까지 한다. caml_fl_add_blocks의 작업을 포함해서 조금 
더 일을 한다.
freelist.c(First_fit_policy 기준) 
caml_fl_allocate 
selected 
Fl_head 
sentinel 
flp table 
• char *caml_fl_allocate (mlsize_t wo_sz) 
flp table after update 
외부에서 호출하는 함수이다. wo_sz 크기의 블록을 freelist에서 찾아오라 
는 뜻이다. 없으면 NULL 리턴하고, 블록을 만들면 그 주소를 리턴 
• flp table을 따라가면서 사이즈가 맞는 블록을 찾으면 allocate_block 호 
출하고 결과를 반환 
• 못 찾는다면, flp_tab 마지막 블록 이후로 따라가면서 flp table을 늘려 
나간다(extend the flp array) 그러다가 찾으면 마찬가지로 
allocate_block호출하여 반환 
• flp table을 늘려가면서 찾는중에 flp table 크기에 제한되어 멈추는 경 
우 flp table을 늘리지는 않고 계속 free list 쫓아가면서 찾는다. 
• 반환하기전 공통작업(update_flp label) 
allocate_block에서는 flp table update하지 않기 때문에 후속 작업이 
필요하다. 
위 그림에서 처럼 flp table에서 블록이 선택되었다고 하자(selected) 
그 블록을 제거하고도 flp table이 구성되어야 한다. 
array 중간에 variable sized sub array를 끼워 넣어야한다. 때문에 
buffer공간과 memmove함수가 필요하다.
freelist.c(First_fit_policy 기준) 
allocate_block 
Fl_head 
sentinel 
flp table 
• static char *allocate_block (mlsize_t wh_sz, int flpi, char 
*prev, char *cur) 
할당하기 사용할 블록을 flp table에서 찾았다. table에서 flpi 
번째 블록이며, 바로전 블록은 prev, 찾은 대상블록은 cur이다. 
블록을 사용할 만큼 잘라서 사용하고, 블록 헤더를 셋팅해 준다. 
• 할당하려는 사이즈 wh_sz는 free block보다 작거나 같다. 
– Case 0 : 크기가 정확히 일치하는 블록 전체를 쓴다. 
– Case 1 : 1 word만큼 남는 경우 1 word는 size==0 해더만 
을 갖는 free block을 남는다. 135 Hd_op (cur) = Make_header (0, 0, 
Caml_white); 
– Case 2 : 필요한 크기만큼 블록을 뒤쪽에 잡고, 앞에 헤더 
를 줄어든 크기로 갱신한다(빨간색 블록이 남음) 
146 Hd_op (cur) = Make_header (Wosize_hd (h) - wh_sz, 0, Caml_blue);
freelist.c(First_fit_policy 기준) 
caml_fl_merge_block 
Fl_head 
sentinel 
added 
flp table 
• char *caml_fl_merge_block (char *bp) 
외부에서 호출하는 함수. freelist에 추가하고 싶은 블록을 넘기면 알아서 처리한다. 
• added block이 추가되는 위치의 앞뒤로 블록을 확인한다. 만약 앞이나 뒤 중에 연속된 블 
록이 있으면 합친다. 만약 합쳐서 크기가 제한 범위를 넘치면(Max_wosize) 합치지 않는다. 
• 앞 뒤 블록 외에도 1 word 짜리 블록 last_fragment와도 비교하여 가능하면 합친다. 
• 만약 합치는 것이 불가능하다면 그냥 끼워넣는다. 
446 Next (bp) = cur; 
447 Next (prev) = bp; 
• bp 블록이 sz==0이면 여기서 last_fragment로 등록된다. last_fragment가 합쳐지지 못하 
고 남겨질 수도 있다는 뜻. 나중에 GC에서 일괄 처리될 것이다. 
• 추가되는 블록 뒤로는 flp table 정보를 날려버린다. (truncate_flp)
freelist.c(First_fit_policy 기준) 
caml_fl_add_blocks 
sentinel 
• void caml_fl_add_blocks (char *bp) 
caml_alloc_shr에서 호출한다. 새로 할당한 blue color의 블록 체인을 끼워 넣는다. flp 
table은 업데이트 하지 않는듯하다(TODO로 아직 구현안함) 
• caml_fl_allocate 호출하고 실패하여 NULL 받았을 때만 호출되는 함수이다. bp > 
fl_last이면, 즉 추가될 블록 체인이 freelist 이후의 주소라면, flp table에 바로 추가 
한다. 주소 순서도 맞고, 크기 순서도 맞으니깐 
• 추가되는 블록은 expand_heap에 의해서 할당된 chunk의 블록들이므로 마지막 블록의 크기 
는 Max_wosize이하이다. 
flp table 
Fl_head 
remainder 
bp(Max_wosize) 
(Max_wosize)
Address Align 
• 주소를 align하여 address % mod = 0 만 
족하도록 한다 
• Bitwise 연산이 더 빠르고, bit수도 아낄 수 
있으므로 align하여 사용하는 경우가 많다. 
• Block address % 4 = 0 
• Page address % Page_size = 0
Garbage Collection 
• OCAML GC의 특징들 
Generational, incremental, write barriered, stop&copy(young heap), 
mark&sweep(old heap), freelist 
• Generational 
young heap과 old heap으로 나누어 관리한다. 처음에 블록할당은 young heap 
에서 한다. Young heap이 차면, young heap에서 살아남은 블록을 old heap으 
로 넘긴다. Young heap(minor GC)와 old heap(major GC)는 다른 GC 알고리즘 
사용 
• Incremental 
Old heap을 처리하는 major GC는 incremental하다. Major GC가 실행되었을때 
모든 작업을 한번에 끝내는 것이 아니라, 작업량을 분할하여 여러 번에 걸쳐 수 
행한다. GC작업이 길어져서 프로그램의 real time 반응을 떨어뜨리지 않도록 
GC작업 중간중간에 본래 프로그램(mutator)을 동작시키는 것이다. 
• Write barriered 
블록의 필드들도 value이다. Value에 따라 포인터일 수도 있다. 블록의 필드에 
기록(write, assign)하는 작업을 함부로 할 경우에, GC의 invariant를 위반할 수 
있다. 또한, old heap에서 young heap으로 가리키는 backward pointing은 따로 
관리해 주어야 한다. 기록에 앞서 필요한 작업을 수행해야 한다는 의미에서 
write barriered라고 한다.
Garbage Collection 
• OCAML GC의 특징들 
Generational, incremental, write barriered, stop&copy(young heap), 
mark&sweep(old heap), freelist 
• Stop&copy(young heap, minor GC) 
young heap에서 사용중인 블록들을 major로 넘긴다(oldify) 
oldify가 완료되면, young heap은 비운다. 
• Mark&sweep(old heap, major GC) 
사용중인 블록을 가려내기 위해서 블록에 색칠을 한다. 바로 접근가능 
한 블록들(roots)부터 시작해서 블록이 가리키는(pointer) 다른 포인터들 
을 DFS 탐색으로 순회하며 모두 칠한다(darken). 
Old heap을 모두 순회하면서, 칠해지지 않은 블록을 freelist에 포함시켜 
관리한다. 
• Freelist 
old heap은 정해진 크기의 heap chunk 단위로 malloc된다. 블록은 
heap chunk내에서 논리적으로 쪼개서 쓰는 것이다. 그렇기 때문에 GC 
에서 지우기로 결정한 블록은 바로 free하는 것이 아니라 freelist에 넣어 
관리한다.
GC Compact algorithm 
• Ocaml에서 관리하는 메모리 단위는 크게 두 가지로 나눌 수 있다. 
– Block : Ocaml 객체와 대응된다. Block header와 Field들이 연속 
되어 있다. Minor, major GC에서 관리하는 대상이다. 
– Chunk : Ocaml이 malloc API를 통해서 실제로 할당해 둔 메모리 
공간이다. Chunk 하나에 여러 Block들이 저장되기도 한다. Block 
과 Chunk가 일치할 수도 있지만, Chunk 안에 여러 Block들이 저 
장되고 해제되어 free list도 관리되다가 다른 Block이 잡히기도 
한다. 
• GC에서는 Block 단위로 사용중인 Block과 불필요한 
Block을 구분한다. 그러나, 메모리 free는 chunk 단위로 
일어난다. 
• Chunk의 사용 비율이 일정 기준 이하로 낮아지면 chunk 
들 안에 할당된 block의 위치를 이동시켜(compact)하고 
• 빈 chunk들을 대상으로 free 시킨다.
GC Compact algorithm 
• Compact 알고리즘에서는 사용중인 block들을 모아서 앞 쪽 chunk에서 부터 채워 넣는다. 물 
론, chunk의 남는 공간이 block 크기보다 작다면, 맞는 빈 공간을 다른 chunk에서 찾는다. 
• Block을 chunk내의 다른 위치로 copy 해야 하는데, 복잡한 문제들이 있다. 
– 하나의 block을 가리키는 pointer가 여러 개 있을 수 있다. Block A를 옮기려면 A를 가리 
키는 모든 포인터들을 알고, 그들이 가리키는 주소도 옮겨 줘야 한다. 
– Infix_tag를 갖는 block이 있다. Infix block은 block 내부에서 자신을 포함하는 외부 블록 
을 가리킨다. Infix block의 Header.size는 offset 을 의미한다. 
– Infix_tag를 포함하는 block은 closure_tag이며, 따라서 Infix offset으로 밖의 block을 쫓아 
나가는 것은 1회만 적용 가능하다고 추측한다. 
Oldify 코드 참고, interp 에서 재귀함수 closure에서 재귀호출을 위해서 라고 추측 
Infix hd val2 
Closure hd Field 0 
offset = Bosize_hd(Infix hd) 
Header Field 
New block Header Field
Inverted pointer 
• 블록을 가리키는 포인터들의 목록을 만드는 방법으로 inverted pointer chain을 사용한다. 
• 하나의 포인터에 대해서 inversion을 하는것을 invert_pointer_at 함수로 실행한다. 
• Root 포인터들을 순회하면서 inversion을 하고, Chunck에 있는 block 순회를 하면서 inversion 
을 한다. 
• Pointer inversion이 끝나면, 해당 블록을 옮길 공간을 정해놓고(실제로 복사는 하지 않는다) 그 
쪽 주소로 가리키도록, chain을 따라가면서 바꾼다. 
Header Field 
Header 
Field 
Header Field 
New block 
New block Header Field
Double inversion 
• Double inversion은 좀더 복잡하다. Double inversion은 infix tag때문에 생기는 문제를 해결하 
기 위해서 도입되었다. Infix block은 block 내부에 있다. 때문에 chunk block 순회할 때는 검사 
되지 않는다. 대신 포인터에 의해서 block 내부에 있는 infix block을 가리키는 경우가 발생한 
다. 
• Infix tag는 하나의 블록에 여러 개 존재할 수 있으며, 하나의 infix block은 여러 개의 포인터가 
가리킬 수 있다. 이 때문에 2차원의 chain을 구성한다. 
• Infix block은 header의 size를 offset으로 하여, infix가 밖의 closure안에 몇 바이트 떨어져있는 
지 표시한다. 그러므로 약간 다른 방법으로 포인팅한다고 볼 수 있다. 점선으로 표시 
• 아래 그림의 모든 포인터들은 clousre block이 할당되는 주소 dependent하다. 그러므로 묶어 
서 관리해야 한다. Double inversion chain으로 관리한다. 
Closure hd Field 0 Infix1 hd Infix2 hd
Double inversion 
• 모든 포인터의 inversion을 실행한 결과는 아래와 같다. 
• 빨간색으로 0~3번을 붙이는데(encoded heade의 마지막 2bit color), inversion에서 중요한 분 
류 번호이다. 
– color 00 : 마지막 2bit를 제외하고 포인터로 해석, Infix block이 아닌 일반 block에 쓰는 chain의 포인터 
– color 01 : Infix header 임을 알려준다, encoded heade가 아님 
– color 10 : Infix block에 쓰는 chain의 포인터 
– color 11 : 일반 block에 쓰는 chain의 마지막과 double inversion chain의 끝에 있는 closure header를 표시 
• Inversion 하기 전에, header encoding을 한다. 미처 인코딩되지 못한 헤더와 포인터, 이미 
encoding된 포인터들을 구분하기 위해 번호를 붙이는 것이다. 
• 새로 할당된 위치로 virtually reallocate하면서 포인터를 다시 돌려놓는다(revert), 헤더를 원래 
대로 돌려놓는다(decoding) 
0 0 0 0 0 0 0 0 0 
Closure hd 3 Field 0 Infix1 hd 1 Infix2 hd 1 
3 0 0 Closure hd 3 2 2 1 2 2 
0 Field 0 2 2
compact.c:invert_pointer_at case 0, 3 
static void invert_pointer_at (word *p) 
{ 
word q = *p; 
Assert (Ecolor ((intnat) p) == 0); 
/* Use Ecolor (q) == 0 instead of Is_block (q) because q could be an 
inverted pointer for an infix header (with Ecolor == 2). */ 
if (Ecolor (q) == 0 && (Classify_addr (q) & In_heap)){ 
switch (Ecolor (Hd_val (q))){ 
case 0: 
case 3: /* Pointer or header: insert in inverted list. */ 
*p = Hd_val (q); 
Hd_val (q) = (header_t) p; 
break; 
case 1: /* Infix header: make inverted infix list. */ 
/* Double inversion: the last of the inverted infix list points to 
the next infix header in this block. The last of the last list 
contains the original block header. */ 
{ 
/* This block as a value. */ 
value val = (value) q - Infix_offset_val (q); 
/* Get the block header. */ 
word *hp = (word *) Hp_val (val); 
while (Ecolor (*hp) == 0) hp = (word *) *hp; 
Assert (Ecolor (*hp) == 3); 
if (Tag_ehd (*hp) == Closure_tag){ 
/* This is the first infix found in this block. */ 
/* Save original header. */ 
*p = *hp; 
/* Link inverted infix list. */ 
Hd_val (q) = (header_t) ((word) p | 2); 
/* Change block header's tag to Infix_tag, and change its size 
to point to the infix list. */ 
*hp = Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); 
}else{ Assert (Tag_ehd (*hp) == Infix_tag); 
/* Point the last of this infix list to the current first infix 
list of the block. */ 
*p = (word) &Field (val, Wosize_ehd (*hp)) | 1; 
/* Point the head of this infix list to the above. */ 
Hd_val (q) = (header_t) ((word) p | 2); 
/* Change block header's size to point to this infix list. */ 
*hp = Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); 
} 
} 
break; 
case 2: /* Inverted infix list: insert. */ 
*p = Hd_val (q); 
Hd_val (q) = (header_t) ((word) p | 2); 
break; 
} 
} 
} 
p 
val0 
q 
val1 
val1 
val3 val2 
val3 
*p = Hd_val (q); 
val0 
Hd_val (q) = (header_t) p; 
p 
val0 
q 
val1 
val3 
val0 val2 
val3 
*p = Hd_val (q); 
val0 
Hd_val (q) = (header_t) p;
compact.c:invert_pointer_at case 1, Tag_ehd (*hp) == Closure_tag 
static void invert_pointer_at (word *p) 
{ 
word q = *p; 
Assert (Ecolor ((intnat) p) == 0); 
/* Use Ecolor (q) == 0 instead of Is_block (q) because q could be an 
inverted pointer for an infix header (with Ecolor == 2). */ 
if (Ecolor (q) == 0 && (Classify_addr (q) & In_heap)){ 
switch (Ecolor (Hd_val (q))){ 
case 0: 
case 3: /* Pointer or header: insert in inverted list. */ 
*p = Hd_val (q); 
Hd_val (q) = (header_t) p; 
break; 
case 1: /* Infix header: make inverted infix list. */ 
/* Double inversion: the last of the inverted infix list points to 
the next infix header in this block. The last of the last list 
contains the original block header. */ 
{ 
/* This block as a value. */ 
value val = (value) q - Infix_offset_val (q); 
/* Get the block header. */ 
word *hp = (word *) Hp_val (val); 
while (Ecolor (*hp) == 0) hp = (word *) *hp; 
Assert (Ecolor (*hp) == 3); 
if (Tag_ehd (*hp) == Closure_tag){ 
/* This is the first infix found in this block. */ 
/* Save original header. */ 
*p = *hp; 
/* Link inverted infix list. */ 
Hd_val (q) = (header_t) ((word) p | 2); 
/* Change block header's tag to Infix_tag, and change its size 
to point to the infix list. */ 
*hp = Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); 
}else{ Assert (Tag_ehd (*hp) == Infix_tag); 
/* Point the last of this infix list to the current first infix 
list of the block. */ 
*p = (word) &Field (val, Wosize_ehd (*hp)) | 1; 
/* Point the head of this infix list to the above. */ 
Hd_val (q) = (header_t) ((word) p | 2); 
/* Change block header's size to point to this infix list. */ 
*hp = Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); 
} 
} 
break; 
case 2: /* Inverted infix list: insert. */ 
*p = Hd_val (q); 
Hd_val (q) = (header_t) ((word) p | 2); 
break; 
} 
} 
} 
p 
val0 
q 
val1 
val1 
val3 val2 
New encoded header (offset, Infix_tag, 3) 
Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); 
val8 
*p = *hp; 
offset = Bosize_hd(val3) 
val 
val5 
val6_0 val4 
hp 
val7_i 
val6_1 
val6_2 
while (Ecolor (*hp) == 0) hp = (word *) *hp; 
… 
hp 
val9 
Assert (Ecolor (*hp) == 3); 
val0 | 2 
Hd_val (q) = ((word) p | 2); 
val8 
the first infix header 
Closure_tag
compact.c:invert_pointer_at case 1, Tag_ehd (*hp) != Closure_tag 
static void invert_pointer_at (word *p) 
{ 
word q = *p; 
Assert (Ecolor ((intnat) p) == 0); 
/* Use Ecolor (q) == 0 instead of Is_block (q) because q could be an 
inverted pointer for an infix header (with Ecolor == 2). */ 
if (Ecolor (q) == 0 && (Classify_addr (q) & In_heap)){ 
switch (Ecolor (Hd_val (q))){ 
case 0: 
case 3: /* Pointer or header: insert in inverted list. */ 
*p = Hd_val (q); 
Hd_val (q) = (header_t) p; 
break; 
case 1: /* Infix header: make inverted infix list. */ 
/* Double inversion: the last of the inverted infix list points to 
the next infix header in this block. The last of the last list 
contains the original block header. */ 
{ 
/* This block as a value. */ 
value val = (value) q - Infix_offset_val (q); 
/* Get the block header. */ 
word *hp = (word *) Hp_val (val); 
while (Ecolor (*hp) == 0) hp = (word *) *hp; 
Assert (Ecolor (*hp) == 3); 
if (Tag_ehd (*hp) == Closure_tag){ 
/* This is the first infix found in this block. */ 
/* Save original header. */ 
*p = *hp; 
/* Link inverted infix list. */ 
Hd_val (q) = (header_t) ((word) p | 2); 
/* Change block header's tag to Infix_tag, and change its size 
to point to the infix list. */ 
*hp = Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); 
}else{ Assert (Tag_ehd (*hp) == Infix_tag); 
/* Point the last of this infix list to the current first infix 
list of the block. */ 
*p = (word) &Field (val, Wosize_ehd (*hp)) | 1; 
/* Point the head of this infix list to the above. */ 
Hd_val (q) = (header_t) ((word) p | 2); 
/* Change block header's size to point to this infix list. */ 
*hp = Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); 
} 
} 
break; 
case 2: /* Inverted infix list: insert. */ 
*p = Hd_val (q); 
Hd_val (q) = (header_t) ((word) p | 2); 
break; 
} 
} 
} 
p 
val0 
q 
val1 
val1 
val3 val2 
val5 + Bosize_ehd(val8) 
*p = (word) &Field (val, Wosize_ehd (*hp)) | 1; 
offset = Bosize_hd(val3) 
New encoded header (offset, Infix_tag, 3) 
Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); 
val 
val5 
val6_0 val4 
hp 
val7_i 
val6_1 
val6_2 
while (Ecolor (*hp) == 0) hp = (word *) *hp; 
… 
hp 
val9 
Assert (Ecolor (*hp) == 3); 
val0 | 2 
Hd_val (q) = ((word) p | 2); 
val8 
the first infix header
compact.c:do_compaction 3rd pass 
/* Third pass: reallocate virtually; revert pointers; decode headers. 
Rebuild infix headers. */ 
{ 
init_compact_allocate (); 
ch = caml_heap_start; 
while (ch != NULL){ 
word *p = (word *) ch; 
chend = ch + Chunk_size (ch); 
while ((char *) p < chend){ 
word q = *p; 
if (Ecolor (q) == 0 || Tag_ehd (q) == Infix_tag){ 
/* There were (normal or infix) pointers to this block. */ 
size_t sz; 
tag_t t; 
char *newadr; 
word *infixes = NULL; 
while (Ecolor (q) == 0) q = * (word *) q; 
sz = Whsize_ehd (q); 
t = Tag_ehd (q); 
if (t == Infix_tag){ 
/* Get the original header of this block. */ 
infixes = p + sz; 
q = *infixes; Assert (Ecolor (q) == 2); 
while (Ecolor (q) != 3) q = * (word *) (q & ~(uintnat)3); 
sz = Whsize_ehd (q); 
t = Tag_ehd (q); 
} 
newadr = compact_allocate (Bsize_wsize (sz)); 
q = *p; 
while (Ecolor (q) == 0){ 
word next = * (word *) q; 
* (word *) q = (word) Val_hp (newadr); 
q = next; 
} 
*p = Make_header (Wosize_whsize (sz), t, Caml_white); 
if (infixes != NULL){ 
/* Rebuild the infix headers and revert the infix pointers. */ 
while (Ecolor ((word) infixes) != 3){ 
infixes = (word *) ((word) infixes & ~(uintnat) 3); 
q = *infixes; 
while (Ecolor (q) == 2){ 
word next; 
q = (word) q & ~(uintnat) 3; 
next = * (word *) q; 
* (word *) q = (word) Val_hp ((word *) newadr + (infixes - p)); 
q = next; 
} Assert (Ecolor (q) == 1 || Ecolor (q) == 3); 
*infixes = Make_header (infixes - p, Infix_tag, Caml_white); 
infixes = (word *) q; 
} 
} 
p += sz; 
}else{ Assert (Ecolor (q) == 3); 
/* This is guaranteed only if caml_compact_heap was called after a 
nonincremental major GC: Assert (Tag_ehd (q) == String_tag); 
*/ 
*p = Make_header (Wosize_ehd (q), Tag_ehd (q), Caml_blue); 
p += Whsize_ehd (q); 
} 
} 
ch = Chunk_next (ch); 
} 
} 
} 
p 
val0 
q0 
val1 
val1 
val2_0 
val2_1 
while (Ecolor (q) == 0) q = * (word *) q; 
… 
val4 
q1 
val4 
val5 
sz = Whsize_ehd(val4) 
infixes 
val6 
q2 
val5 
val7_0 
q = * (word *) (q & ~(uintnat)3); 
val7_1 
while (Ecolor (q) != 3) q = * (word *) (q & ~(uintnat)3); 
… 
q3 
val8 val8 
newadr 
val10 
Infix_tag 
compact_allocate (Bosize_ehd(val8)); 
Make_header of val8 
(Wosize_whsize (sz), 
t, Caml_white); 
val10+4 
* (word *) q = (word) 
Val_hp (newadr); 
val5 
val7_0 
val7_1 
… 
val8 
while (Ecolor (q) == 2) 
* q = newadr + (infixes - p) + 4; 
Ecolor (q) != 2 && while (Ecolor ((word) infixes) != 3){ 
Make_header (infixes - p, Infix_tag, 
Caml_white);
compact.c:do_compaction 4th pass /* Fourth pass: reallocate and move objects. 
Use the exact same allocation algorithm as pass 3. */ 
{ 
init_compact_allocate (); 
ch = caml_heap_start; 
while (ch != NULL){ 
word *p = (word *) ch; 
chend = ch + Chunk_size (ch); 
while ((char *) p < chend){ 
word q = *p; 
if (Color_hd (q) == Caml_white){ 
size_t sz = Bhsize_hd (q); 
char *newadr = compact_allocate (sz); 
memmove (newadr, p, sz); 
p += Wsize_bsize (sz); 
}else{ 
Assert (Color_hd (q) == Caml_blue); 
p += Whsize_hd (q); 
} 
} 
ch = Chunk_next (ch); 
} 
} 
• 3번 pass에서 init_compact_allocate도 하고 
compact allocate도 실행하여 포인터들이 새 
로 할당된 주소를 가리키도록 바꾼다. 
• 하지만 새로 할당된 주소로 데이터를 카피하 
는 것은 4단계에서 하고, 4단계에서 또 다시 
init_compact_allocate, compact_allocate를 
실행한다. 
• 여기서 가정은 3,4 단계에서 할당되는 메모리 
공간과 위치가 정확하게 일치한다는 점이다. 
3단계에서 다른 코드를 지우고, chunk block 
순회하면서 compact_allocate(sz) 호출하는 
부분만 보면 동일하다. 
• 그리고, 카피하는 대상과 원본의 주소 영역이 
같은 chunk 공간이라서 일부 데이터가 
overwrite되어 소실될 수 있을 것 같으나, 그 
런 문제도 없다. 
• 왜냐하면 compact을 하면, 
원본주소 >= 대상주소 
따라서, overwrite 된 메모리 영역은 이미 복 
사한(순회하고 있는 메모리 주소) 보다 작은 
주소값 영역이다. Chunk block 순회 자체가 
주소값이 증가하는 순서로 실행됨.
caml__roots_block 
next 
ntables = 1 
nitems = 1 
tables[5] 
caml_local_roots 
caml__roots_block 
next 
ntables = 1 
nitems = 1 
tables[5] 
Roots.c caml_local_roots 
val0 
struct caml__roots_block { 
struct caml__roots_block *next; 
intnat ntables; 
intnat nitems; 
value *tables [5]; 
}; 
caml__roots_block 
next 
ntables = 1 
nitems = 4 
tables[5] 
CAMLprim value caml_natdynlink_open(value filename, value global) { 
CAMLparam1 (filename); 
CAMLlocal1 (res); 
... 
res = caml_alloc_tuple(2); 
Field(res, 0) = (value) handle; 
Field(res, 1) = (value) (sym); 
CAMLreturn(res); 
} 
caml__roots_block 
// CAMLparam1 (filename); 
struct caml__roots_block *caml__frame = caml_local_roots; 
// - CAMLxparam1 (filename); 
struct caml__roots_block caml__roots_filename; 
caml__roots_filename.next = caml_local_roots; 
caml_local_roots = &caml__roots_filename; 
caml__roots_filename.nitems = 1; 
caml__roots_filename.ntables = 1; 
caml__roots_filename.tables [0] = &filename; 
// CAMLlocal1 (res); 
value res = 0; 
// - CAMLxparam1 (res); 
struct caml__roots_block caml__roots_res; 
caml__roots_res.next = caml_local_roots; 
caml_local_roots = &caml__roots_res; 
caml__roots_res.nitems = 1; 
caml__roots_res.ntables = 1; 
caml__roots_res.tables [0] = &res; 
// CAMLreturn(res); 
value caml__temp_result = (res); 
caml_local_roots = caml__frame; 
return (caml__temp_result); 
res filename 
next 
ntables = N 
nitems = 1 
tables[5] 
CAMLxparamN 
CAMLxparam4 
Macro 
preprocessing
Roots.c caml_frame_descriptors 
natdynlink.c 
caml_natdynlink_run 
(sym) 
roots.c 
caml_init_frame_descriptors 
(caml_frametable[i]) 
roots.c 
caml_register_frametable 
frametables 
추 
가 
struct link struct link struct link struct link 
roots.c 
caml_init_frame_descriptors 
/* Allocate the hash table 해쉬테이블 구성 */ 
caml_frame_descriptors = 
(frame_descr **) caml_stat_alloc(tblsize * sizeof(frame_descr *)); 
caml_frame_descriptors 
Hash table of 
frame_descr 
natdynlink.c 
getsym, flexdll_dlsym 
cmmgen.ml 
let frame_table namelist = 
let mksym name = 
Csymbol_address (Compilenv.make_symbol ~unitname:name (Some "frametable")) 
in 
Cdata(Cglobal_symbol "caml_frametable" :: 
Cdefine_symbol "caml_frametable" :: 
List.map mksym namelist 
@ [cint_zero]) 
typedef struct { 
uintnat retaddr; 
unsigned short frame_size; 
unsigned short num_live; 
unsigned short live_ofs[1]; 
} frame_descr; 
typedef struct { 
uintnat len; 
frame_descr * ptr_fd; 
char* ; 
short ; 
short ; 
short [ptr_fd->num_live] ; 
frame_descr *; 
}; 
sizeof(frame_descr*) 
round down align
caml allocation 
alloc.c 
caml_alloc 
memory.h 
Alloc_small 
minor heap에 할당 시도 
i386.S:caml_alloc1,caml_alloc2,caml_alloc3,caml_allocN 
io.c:caml_alloc_channel 
custom.c:caml_alloc_custom 
alloc.c:caml_alloc,caml_alloc_small,caml_alloc_tuple,caml_alloc_string, 
caml_alloc_final,caml_alloc_array,caml_alloc_dummy, 
caml_alloc_dummy_float 
Memory.c:caml_alloc_for_heap,caml_alloc_dependent_memory 
minor_gc.c:caml_alloc_table 
memory.c 
caml_alloc_shr 
freelist 찾아보고 없으면 
expand heap하고 다시 꺼내온다 
mlvalues.h 
Atom 
Wosize 0 짜리 block들을 보관 
하는 caml_atom_table 참조 
freelist.c 
caml_fl_allocate 
freelist에서 맞는 block을 찾 
아본다 
minor_gc.h 
caml_minor_collection 
minor heap을 비우고, major heap으로 
넘긴다. 
memory.c 
expand_heap 
chunk를 할당하고, align 작 
업,chunk linked list에 추가 
freelist.c 
caml_fl_add_blocks 
freelist에 새로운 블록을 추가 
alloc.c 
caml_alloc_small 
alloc.c 
caml_alloc_tuple, caml_alloc_array, 
caml_alloc_dummy, caml_alloc_dummy_float 
weak.c:caml_weak_get_copy 
natdynlink.c:caml_natdynlink_run_toplevel 
backtrace.c:caml_get_exception_backtrace 
debugger.c:caml_debugger_init 
signal.c:caml_install_signal_handler 
obj.c:caml_obj_block,caml_obj_dup,read_debug_info 
array.c 
caml_make_vect 
caml_array_gather
Minor GC roots and olidfy 
i386.S:caml_alloc1,caml_alloc2,caml_alloc3,caml_allocN 
io.c:caml_alloc_channel 
custom.c:caml_alloc_custom 
alloc.c:caml_alloc,caml_alloc_small,caml_alloc_tuple,caml_alloc 
_string, caml_alloc_final,caml_alloc_array,caml_alloc_dummy, 
caml_alloc_dummy_float 
Memory.c:caml_alloc_for_heap,caml_alloc_dependent_memory 
minor_gc.c:caml_alloc_table 
minor_gc.c 
caml_empty_minor_heap 
minor_gc.c 
caml_oldify_one 
roots.c 
caml_oldify_local_roots 
caml_globals 
array of value 
asmcomp/cmmgen.ml|2134| 
Cdefine_symbol "caml_globals" … 
어떻게 생성되는지 아직 확인된바 없음 
caml_dyn_globals 
linked list, node->data = value 
asmrun/roots.c|136| static link * 
caml_dyn_globals = NULL; 
OCAML-side stack local var 
saved reg values, stack stored values 
follow stack frames starting with 
caml_bottom_of_stack 
caml_local_roots 
linked list, node->tables[i][j] = value 
byterun/roots.c|28| CAMLexport struct 
caml__roots_block *caml_local_roots = NULL; 
caml_global_roots(_young) 
linked list, node->root = value 
byterun/globroots.c|171| struct 
global_root_list caml_global_roots = { NULL, 
{ NULL, }, 0 }; 
final_table 
linked list, node->fun, node->val = value 
asmrun/finalise.c|222| final_table = 
caml_stat_alloc (new_size * sizeof (struct 
final));
call graph to malloc 
misc.c 
caml_aligned_malloc 
실제 malloc 호출하여 Page_size 이 
상의 heap chunk를 만든다. 
misc.c 
caml_alloc_for_heap 
실제 malloc 호출하여 Page_size 이상의 
heap chunk를 만든다. 
minor_gc.c 
caml_set_minor_heap_size 
memory.c 
expand_heap 
fl_allocate 실패하면 old heap으 
로 chunk를 할당하여 확장 
major_gc.c 
caml_init_major_heap 
처음부터 old heap으로 하나의 
chunk를 잡아두고 시작한다. 
Heap chunk linked list의 head 
intern.c 
intern_alloc 
unmarshalling중에 내부적으로 사용하는 공 
간을 할당 page table 관리는 없다. 
compact.c 
caml_compact_heap 
compact하고나서 old heap에 적정 비율의 빈 공 
간이 있도록 해야한다. 부족할 경우 할당한다. 
memory.c 
caml_add_to_heap 
페이지 테이블에 추가, caml_heap_start heap chunk 
chaing에 추가 
memory.c 
caml_page_table_add 
Heap chunk linked list에 추가 
Chunk_next (chunk) = caml_heap_start; 
caml_heap_start = chunk;
gc trigger 
amd64.S 
caml_call_gc 
signals_asm.c 
caml_garbage_collection 
minor_gc.c 
caml_minor_collection 
major_gc.c 
caml_major_collection_slice 
major_gc.c 
mark_slice 
major_gc.c 
sweep_slice 
compact.c 
caml_compact_heap_maybe 
compact.c 
caml_compact_heap_maybe 
Mark phase 
Sweep phase 
Idle phase 
after sweep 
amd64.S 
caml_alloc1 .. N 
asmcomp/emit.ml 
let emit_instr fallthrough i = 
... 
| Lop(Ialloc n) -> 
if !fastcode_flag then begin 
let lbl_redo = new_label() in 
`{emit_label lbl_redo}: sub r15, {emit_int n}n`; 
` cmp r15, {emit_symbol "caml_young_limit"}n`; 
let lbl_call_gc = new_label() in 
let lbl_frame = record_frame_label i.live Debuginfo.none 
` jb {emit_label lbl_call_gc}n`; 
` lea {emit_reg i.res.(0)}, [r15+8]n`; 
call_gc_sites := 
{ gc_lbl = lbl_call_gc; 
gc_return_lbl = lbl_redo; 
gc_frame = lbl_frame } :: !call_gc_sites 
end else begin 
begin match n with 
16 -> ` call {emit_symbol "caml_alloc1"}n` 
asmcomp/emit.ml 
in 
List.iter emit_call_gc !call_gc_sites; 
asmcomp/amd64/emit_nt.mlp 
let emit_call_gc gc = 
`{emit_label gc.gc_lbl}: call {emit_symbol "caml_call_gc"}n`; 
`{emit_label gc.gc_frame}: jmp {emit_label gc.gc_return_lbl}n` 
emit된 asm코드에서 call 
byterun/memory.h 
Alloc_small 
caml_minor_collection ();
Infix tag 
f1 
0x4034972c 
Closure_tag head 
8 bytes 
let rec f1 n = if n=0 then 0 else f2 (n-1) 
and f2 n = if n=0 then 0 else f1 (n-1);; 
inspect f1;; 
inspect f2;; 
f2 
0x40349734 
Code ptr Infix_tag(size=2) 
f3 
0x40349714 
f4 
0x4034971c 
Closure_tag head Code ptr1 Infix_tag(size=2) Code ptr2 
f5 
let rec f3 n m = if n+m<=0 then 0 else f4 (n-1) m 
and f4 n m = if n+m<=0 then 1 else f5 m (n-1) 
and f5 n m = if n+m<=0 then 2 else f3 m (n-1) ;; 
inspect f3;; 
inspect f4;; 
inspect f5;; 
0x40349724 
Infix_tag(size=4) 
Code ptr3
caml_globals 
&camlPervasives &camlSimple &camlStd_lib 0 
caml_globals label 
in data section 
camlPervasives label 
in data section 
block header(size) 
camlPervasives__84 
fail_with label 
Tag=0, size=1 0x0061f3b8 
String_tag, size=2 “Pervasives.Exitx00” 
camlPervasives__85 
invalid_args label 
exc Exit 
heap block addr 
camlPervasives__83 
min label 
camlPervasives__82 
max label 
camlPervasives__82 
max label 
… 
infinity 
heap block addr 
Double_tag, size=1 0x00000000 0x7ff00000 
(gdb) x/8x 0x00007ffff7519fe0 
0x7ffff7519fe0: 0x000007fd 0x00000000 0x00000000 0x7ff00000 
0x7ffff7519ff0: 0x00000400 0x00000000 0x0061f3b8 0x00000000
frametable 
• frametable은 ML-side에서 생성한 모든 함수들의 frame 정보를 담는 frame descriptor를 가지 
고 있다. 
• frame descriptor는 ML-side에서 생성되어 assembly code의 data section에 symbol로 정의된 
다. 하지만 데이터에 대한 해석은 C-side의 stack.h : struct frame_descr 으로 접근한다. 
// stack.h 
typedef struct { 
uintnat retaddr; 
unsigned short frame_size; 
unsigned short num_live; 
unsigned short live_ofs[1]; 
} frame_descr; 
• retaddr : 해당하는 함수를 호출한 instruction의 다음 instruction pointer 즉 return addr 
• frame_size : 해당하는 stack activation frame의 크기 
• num_live : 해당 activation frame에 저장된 value 중에서 root로 사용될 value의 개수 
• live_ofs[1] : variable sized array로 간주한다. 각 항목은 root로 사용될 value에 해당하며 두 가 
지 종류(reg, stack)을 함께 저장한다. 
• live_ofs[i] % 2 == 1 
root : regs[ live_ofs[i]/2 ] 
• live_ofs[i] % 2 == 0 
root : sp + live_ofs[i] 
stack pointer + offset (스택에 저장된 value)
frametable 
• 스택에 저장된 activation record는 두 가지 종류이다. C-side/ML-side 
• asmrun/roots.c : caml_oldify_local_roots 함수에서 stack을 backtrace하면서 각 함수 호출에 
대응하는 activation record (frame)들을 모두 순회한다. 
• C-side의 지역변수들은 CAMLlocal 매크로 등을 이용하여 모두 등록해 두고 관리하기 때문에 
stack backtrace에서 고려해 주지 않아도 된다. C-side frame은 스킵한다. 
• ML-side frame에 대해서는 frame table의 정보를 참고하여 root 로 사용할 value들을 얻어낸 
다. 
• C-side frame을 스킵하는 방법 
– 스택에는 C-side/ML-side 스택이 번갈아 나올수가 있다. 그러나 C-side나 ML-side을 넘나들 때는 꼭 interface에 해당하 
는 함수를 거쳐야만 한다. 
– C-side -> ML-side (amd64.S : RECORD_STACK_FRAME 참고) 
amd64.S : caml_start_program 
amd64.S : caml_callback_exn 
– ML-side -> C-side 
amd64.S : caml_c_call 
amd64.S : caml_call_gc 
amd64.S : caml_raise_exn 
– bottom_of_stack, last_retaddr 
C-side 진입할때 직전 ML-side frame의 stack pointer, return address를 전역변수에 저장한다. 
– ML-side로 진입할 때는 스택에 bottom_of_stack, last_return_address 를 push하여 기억해 둔다. 
– backtrace를 하다가 C-side frame을 만나면(정확히는 L107 caml_start_program) 
스택에 저장된 bottom_of_stack을 얻어서 C-side frame chunk(연속된 C-side frame들)을 한번에 뛰어 넘을 수 있다.
frametable 
• C-side / ML-side frame 구분 방법 
– stack backtrace해서 만나게 되는 C-side frame은 L107 caml_start_program이 유일하다. 
– L107에 대응하는 frame descriptor는 amd64.S 에 정의되어 있다. 
– caml_oldify_local_roots 에서는 frame_size가 -1(0xFFF) 인지 확인하여 L107 frame 을 확인한다. 
G(caml_system__frametable): 
.quad 1 /* one descriptor */ 
.quad LBL(107) /* return address into callback */ 
.value -1 /* negative frame size => use callback link */ 
.value 0 /* no roots here */ 
.align EIGHT_ALIGN 
• Frame descriptor를 찾는 방법 
– 현재 frame에 저장된 return address를 hash key로 삼아서 찾는다. 
– frame descriptor 마다 대응하는 return address를 적어두었기 때문에 매칭하는 descriptor를 찾을 수 있다. 
– return address로 frame descriptor를 찾는다는 것은 call instruction을 포함하는 함수의 frame 정보를 찾는 것이다. 
– 아래 그림과 다음 슬라이드의 자세한 그림 참고 
camlNqueen1__loop_1032: 
.... 
movq %rsi, %rbx 
call ... 
.L161: // return address 
movq 24(%rsp), %rbx 
movq %rbx, %rdi 
addq $2, %rbx 
movq %rbx, 24(%rsp) 
movq 32(%rsp), %rax 
cmpq %rax, %rdi 
jne .L133 
.globl camlNqueen__frametable 
camlNqueen__frametable: 
.quad 17 
.... 
.quad .L161 // return address 
.word 48 // frame size 
.word 3 // # of local var 
.word 16 // var0 : *(sp+16) 
.word 24 // var1 : *(sp+24) 
.word 32 // var2 : *(sp+32) 
.align 8 
===> frame descriptor for camlNqueen1__loop_1032
frametables, caml_frametable 
caml_frametable symbol 
.data 
.globl caml_frametable 
caml_frametable: 
.quad caml_startup__frametable 
.quad caml_system__frametable 
.quad camlPervasives__frametable 
.quad camlNqueen__frametable 
.quad camlStd_exit__frametable 
.quad 0 
.globl camlNqueen__frametable 
camlNqueen__frametable: 
.quad 17 
.quad .L175 
.word 16 
.word 0 
.align 8 
... 
.quad .L165 // ret addr 
.word 16 // frame size 
.word 1 // # of local var 
.word 9 // var0 : *reg4 
.align 8 
... 
.quad .L161 // ret addr 
.word 48 // frame size 
.word 3 // # of local var 
.word 16 // var0 : *(sp+16) 
.word 24 // var1 : *(sp+24) 
.word 32 // var2 : *(sp+32) 
.align 8 
camlNqueen_frametable 
symbol 
assembly instructions of the function 
camlNqueen1__loop_1032 
camlNqueen1__loop_1032: 
.cfi_startproc 
subq $40, %rsp 
.cfi_adjust_cfa_offset 40 
.L124: 
movq %rax, %rdx 
movq $3, %rax 
movq 24(%rdi), %rsi 
cmpq %rsi, %rax 
jg .L120 
movq %rsi, 0(%rsp) 
movq %rax, 8(%rsp) 
movq %rdi, 24(%rsp) 
movq %rbx, 16(%rsp) 
movq %rdx, 32(%rsp) 
.L121: 
movq %rax, %rdi 
addq $-2, %rdi 
movq %rax, %rsi 
.... 
movq 32(%rax), %rdi 
movq $5, %rax 
movq %rsi, %rbx 
call camlNqueen1__loop_1032@PLT 
.L161: 
movq 24(%rsp), %rbx 
movq %rbx, %rdi 
addq $2, %rbx 
movq %rbx, 24(%rsp) 
movq 32(%rsp), %rax 
cmpq %rax, %rdi 
jne .L133
finalise 
• Gc.finalise : (‘a -> unit) -> ‘a -> unit 
• 어떤 블록이 해제되기 전에 꼭 호출 되어야 하는 함수를 등록해 둔다. 블록이 unreachable해지는 순간과 실 
제 GC에서 해제하는 순간 사이에 final함수가 호출된다. 
• final_table 
[0..old) : old set (마지막 GC에서 reachable으로 확인됨) 
[old..young) : recent set (아직 GC에서 확인되지 않음) 
[young..size) : free space 
• todo list 
마지막 major GC에서 unreachable로 확인됨, final_table에서 빠지고 todo list로 추가됨 
final 함수가 호출될 예정임 
• finalise 등록을 하면 final_table에 추가된다. final_table은 GC의 root set으로 사용되기 때문에 
GC 진행 결과 바로 해제되지 않는다. 특히 minor GC에서는 finalise 등록된 블록이 해제되지 않는다. 
caml_final_do_young_roots(Oldify 호출로 final 함수와 대상블록을 oldify 시도한다. 
• major GC에서 sweeping이 끝나고 caml_final_update를 호출하여 해제되어야 할 블록(white)은 todo list에 
넣어 버리고, 남아야 하는 블록은 old set에 넣는다. todo list의 블록들도 당장은 해제되면 안되기 때문에 
caml_darken 실행한다. 즉, 실제 final 함수 호출은 major GC 종료 후로 미루고, 블록 해제는 다음 GC로 미 
룬다. 
• major GC 종료 단계에서 caml_final_do_calls를 호출하여 todo list에 있는 final함수를 모두 호출한다. ML-side 
호출이므로 caml_callback_exn을 이용한다. 
이벤트 
(시간순서) 
블록 생성 v final함수 등록 minor GC major GC 
sweep phase 
after major GC major GC 
소속 young/old heap young/old heap 
final_table(recent set) 
young/old -> old heap 
final_table(old set) 
old heap 
final_table(old) 
-> todo list 
todo list -> X 해제됨 
담당 함수 caml_final_register caml_final_do_young_roots 
(caml_oldify_one, v) 
caml_final_do_young_roots 
(caml_oldify_one, f) 
caml_final_update caml_final_do_calls 
(final함수 호출)
startup(amd64 기준) 
• 실행순서 
asmrun/startup.c : caml_main(C 코드) -> amd64.S : caml_start_program(asm 코드) -> 
caml_program(컴파일러 생성) -> caml[모듈이름]__entry(모듈별로 컴파일러가 생성) 
• caml_main 
– parse_camlrunparam 
CAMLRUNPARAM 환경변수를 파싱하여 전역변수 설정(POcaml에서 몇가지 설정에 사용) 
– caml_init_gc 
young heap, old heap 할당, 관련 구조/상수 초기화 
– init_atoms 
필드 길이 0인 블록은 한 copy만 만들어 두고 공유한다. 길이 0이며 다양한 tag번호를 갖는 블록을 만들고 보관 
– caml_init_signals 
signal handler 시스템에 등록 
– caml_termination_jmpbuf 
프로그램 종료전 호출할 훅(caml_termination_hook) 등록가능 
ML-side에도 유사한 at_exit 함수가 있음(pervasive 모듈 참고) 
– caml_start_program (amd64.S asm 코드 호출) 
– caml_fatal_uncaught_exception 
unhandled exceptio으로 종료되는 경우 처리 
• caml_start_program 
– 각종 인터페이스 작업(reg save, 전역변수 save, 전역변수를 registe로 로드) 
– caml_program 호출 ( ‘call *%r12’ ) 
• caml_program 
– module 마다 entry 포인트 호출 (camlPervasives__entry), module dependency 순서에 따라 하나씩 호출 
– 마지막에는 camlStd_exit__entry 호출 
– 각 모듈 entry를 호출할 때마다 전역변수 caml_globals_inited를 카운트하여 현재까지 실행된 모듈의 수를 표시한다. 
(각 모듈이 실행되어야, 모듈의 전역변수가 설정되고 caml_globals를 통해 접근이 가능해 짐, roots.c 참고)
ML/C-side interface(amd64 기준) 
• C-side assembly code 
C 코드는 gcc로 컴파일된다. C-side 코드의 어셈블리규약(convention)은 gcc 컴파일러를 따른다. 
• ML-side assembly code 
ML-side는 ocaml/asmcomp에 있는 컴파일 프로그램을 거쳐서 assembly instruction으로 생성된다. 
그러므로 ocaml 컴파일러가 정한 convention을 따른다. 
• C/ML-side interface 
양쪽의 규약이 다르기 때문에, 넘어갈 때는 interface 함수를 거쳐야 한다. 
– C -> ML 
• caml_start_program(처음 프로그램 시작하여 caml_program 진입, caml_callback_exn으로 다시 ML-side 진입) 
• caml_callback_exn(C-side 실행 중에 ML-side 함수 호출) 
등록된 finalise, signal 함수 실행, at_exit 함수 실행, ML-side에서 인자로 받은 clousure를 C-side에서 호출 
• fail.c:caml_fail_with -> fail.c:caml_raise -> amd64.S:caml_raise_exception 
보통 ML-side에서 trap(exception handler) 설치 및 raise 할 때 caml_raise_exceptio을 사용한다. 
그러나 C-side에서도 caml_fail_with함수를 통해 raise 할 수 있다. 
– ML -> C 
• caml_call_gc 
ML-side의 메모리 할당(alloc)에서 실패할 경우 호출됨, signals_asm.c : caml_garbage_collection 호출 
• caml_c_call 
ML코드에 external으로 선언된 함수를 호출 때 사용됨, 또는 asmcomp에서 직접 삽입됨 
• caml_ml_array_bound_error 
array index범위 오류 시 호출됨, C-side로 넘어가서 caml_raise를 호출 
• external 선언이며 noalloc 옵션 
interface 를 거치지 않고 바로 C 함수가 호출됨 
• interface 작업 
– register 저장(PUSH/POP_CALLEE_SAVE_REGS, proc.ml : destroyed_at_c_call) 
– 전역변수 설정(caml_young_ptr : r15, caml_exception_pointer : r14, caml_bottom_of_stack, caml_last_return_address, 
caml_gc_regs) 
– argument passing(C_ARG_*, proc.ml: calling_conventions)
ML/C-side interface(amd64 기준) 
• proc.ml : int_reg_name 
register들의 이름과 번호를 맵핑해준다. C-side에서도 그 순서를 준수해야 한다. 
"rax"; "rbx"; "rdi“ .... 
• caml_gc_regs 
ML-side에서 사용하던 register가 GC root values를 가지고 있을 수도 있으므로, caml_call_gc에서 모든 register를 저장한다. 
아래 코드처럼 모든 register를 스택에 넣고 스택포인터주소($rsp)를 array처럼 사용가능 
... 
262 pushq %rdi 
263 pushq %rbx 
264 pushq %rax 
265 STORE_VAR(%rsp, caml_gc_regs) 
• caml_young_ptr(r15) 
young heap의 할당 위치를 표시하는 포인터, caml_young_ptr을 감소 시키는 것만으로 young heap에 alloc이 가능하다. 
ML-side에서 young heap alloc이 빈번하므로, caml_young_ptr를 r15 레지스터에 저장해서 사용한다. 
C-side에 진입할 때는 caml_young_ptr<-r15 으로 저장하고, 전역변수 caml_young_ptr가 r15 레지스터의 할당 포인터 역할을 
대신하고, ML-side로 복귀할때는 반대로 r15<-caml_young_ptr 
• caml_exception_pointer(r14) 
ML-side에서 trap(exception handler) frame이 저장된 주소(stack pointer)를 저장, raise가 ML-side 또는 C-side에서 발생하면 
stack pointer를 trap frame으로 설정하여 등록된 exception handler를 실행한다. 
asmcomp/emit.ml 
662 | Lpushtrap -> 
663 cfi_adjust_cfa_offset 8; 
664 (emit_string " pushq %r14n"); 
665 cfi_adjust_cfa_offset 8; 
666 (emit_string " movq %rsp, %r14n"); 
667 stack_offset := !stack_offset + 16 
... 
674 | Lraise -> 
675 if !Clflags.debug then begin 
676 (emit_char ' '; emit_call "caml_raise_exn"; emit_char 'n'); 
677 record_frame Reg.Set.empty i.dbg 
678 end else begin 
679 (emit_string " movq %r14, %rspn"); 
680 (emit_string " popq %r14n"); 
681 (emit_string " retn") 
682 end 
asmrun/amd64.S 
723 /* Raise an exception from C */ 
724 
725 FUNCTION(G(caml_raise_exception)) 
726 TESTL_VAR($1, caml_backtrace_active) 
727 jne LBL(111) 
728 movq C_ARG_1, %rax 
729 LOAD_VAR(caml_exception_pointer, %rsp) /* Cut stack */ 
730 popq %r14 /* Recover previous exception 
handler */ 
731 LOAD_VAR(caml_young_ptr, %r15) /* Reload alloc ptr */ 
732 ret 
733 LBL(111): 
...
ML/C-side interface(amd64 기준) 
• caml_last_return_address, caml_bottom_of_stack 
stack backtrace(stack에 저장된 call frame을 따라가기)에서 사용된다. 
roots.c에서 stack backtrace를 실행하는데, 여기서는 C-side frame은 건너뛰 
고 trace한다. (C-side에서는 CAML_local/param함수로 root value들을 명시 
적으로 관리하기때문) 
두 변수는 C-side에서 봤을때 마지막 ML-side frame의 return addr, stack 
pointer를 의미한다. 
• arguments in registers 
C/ML-side의 함수 호출 규약이 다르다. 
C-side는 함수 매개변수를 rdi, rsi, rdx ... 순서로 사용하는데 반해 
ML-side는 rax, rbx, ... 순서(proc.ml의 주석을 참조) 
interface에서는 매개변수를 재배치하여 규약에 맞도록 한다.(C_ARG_1, 
C_ARG_2 .. 참조) 
roots.c:caml_oldify_local_roots 
C-side call frames ... 
callee C-fun 
ML->C interface(Ex. caml_c_call) 
ML-side call frames ... 
callee ML-fun 
C->ML interface(Ex.caml_start_program) 
caller C-fun(Ex. caml_callback) 
C-side call frames ... 
... 
ML->C interface(Ex. caml_c_call) 
... 
caml_last_return_address 
caml_bottom_of_stack 
roots.c 
- skip C call frames 
next_ctx = Callback_link(sp); 
sp = next_ctx->bottom_of_stack; 
retaddr = next_ctx->last_retaddr; 
#0 0x0000000000440be4 in caml_raise_exception_r () 
#1 0x000000000044148a in caml_raise_r (ctx=0x67a070, v=140737340385952) at fail.c:94 
#2 0x0000000000441756 in caml_raise_with_arg_r (ctx=0x67a070, tag=6665128, arg=140737340385976) 
at fail.c:139 
#3 0x00000000004418e3 in caml_raise_with_string_r (ctx=0x67a070, tag=6665128, msg=<optimized 
out>) at fail.c:181 
#4 0x0000000000441905 in caml_failwith_r (ctx=<optimized out>, msg=<optimized out>) at 
fail.c:192 
#5 0x00000000004344d2 in extern_failwith (msg=0x4479f0 "Marshal.to_buffer: buffer overflow") 
at extern.c:277 
#6 grow_extern_output (required=<optimized out>) at extern.c:227 
#7 0x000000000043485a in writeblock ( 
data=0x65c620 ..., len=496) at extern.c:296 
#8 0x0000000000434b56 in extern_rec (v=6669856) at extern.c:432 
#9 0x000000000043502c in extern_value (v=6669856, flags=<optimized out>) at extern.c:557 
#10 0x0000000000435456 in caml_output_value_to_buffer_r (ctx=<optimized out>, ...>) at 
extern.c:697 
#11 0x000000000041c57c in camlMarshal__to_buffer_1014 () at marshal.ml:32 
#12 0x00000000004112e8 in camlIntext__test_buffer_1102 () at intext.ml:235 
#13 0x000000000041398e in camlIntext__main_1242 () at intext.ml:533 
#14 0x000000000042461d in camlPrintexc__catch_1060 () at printexc.ml:77
ML/C-side interface(amd64 기준) 
• ML-side에서 try with 구문으로 trap(exception pointer) 
가 설치되었고 
• C-side에서 caml_failwith_r 함수로 exception 
raise가 발생하였다. 
• with exception handler 구문(ML-side)을 실행하기 위해 
서 다시 ML-side 진입하는데 
amd64.S : caml_raise_exception_r 을 거쳤다. 
• gdb backtrace에서는 caml_c_call_r과 
caml_raise_exception_r 같은 인터페이스의 frame 
은 잠시 나타나고, callee함수로 넘어가면 보이지 않 
는다. 여기 예제에서는 caml_raise_exception_r 실 
행 중에 잠시 나타난 call frame을 표시한 것이다. 
testsuites/tests/lib-marshal 
257 (try marshal_to_buffer s 0 512 verylongstring []; false 
258 with Failure "Marshal.to_buffer: buffer overflow" -> 
257 let _ = Gc.get_th_num() in true); 
#0 caml_context_num (ctx=0x67a070, v=1) at context.c:193 
----------------- caml_c_call_r is hidden --------------------- 
#1 0x00000000004112a1 in camlIntext__test_buffer_1102 () at intext.ml:258 
--------------------------------------------------------------- 
#0 0x0000000000440be4 in caml_raise_exception_r () 
#1 0x000000000044148a in caml_raise_r at fail.c:94 
#2 0x0000000000441756 in caml_raise_with_arg_r at fail.c:139 
#3 0x00000000004418e3 in caml_raise_with_string_r at fail.c:181 
#4 0x0000000000441905 in caml_failwith_r at fail.c:192 
#5 0x00000000004344d2 in extern_failwith at extern.c:277 
#6 grow_extern_output (required=<optimized out>) at extern.c:227 
#7 0x000000000043485a in writeblock at extern.c:296 
#8 0x0000000000434b56 in extern_rec at extern.c:432 
#9 0x000000000043502c in extern_value at extern.c:557 
#10 0x0000000000435456 in caml_output_value_to_buffer_r at extern.c:697 
----------------- caml_c_call_r is hidden --------------------- 
#11 0x000000000041c57c in camlMarshal__to_buffer_1014 () at marshal.ml:32 
#12 0x00000000004112e8 in camlIntext__test_buffer_1102 () at intext.ml:235 
#13 0x000000000041398e in camlIntext__main_1242 () at intext.ml:533 
#14 0x000000000042461d in camlPrintexc__catch_1060 () at printexc.ml:77 
#5 0x000000000040d48c in caml_program () 
#6 0x0000000000440af9 in caml_start_program_r () 
#7 0x0000000000440d29 in caml_start_program_r_wrapper () 
--------------------------------------------------------------- 
#8 0x00007ffff76c4e9a in start_thread () from /lib/x86_64-linux-gnu/ 
libpthread.so.0
Ocaml systhread, master_lock, enter/leave_blocking_section 
test/comp1.ml 
메모리 할당이 없이 연산만 반복한다. 
Thread.create로 동일한 연산 함수를 두 개 실행 
257 #0 0x0000000000431338 in caml_leave_blocking_section () 
258 #1 0x000000000042b175 in caml_thread_yield () 
259 #2 0x00000000004117e7 in camlThread__preempt_1024 () at thread.ml:51 
260 #3 0x000000000044143a in caml_start_program () 
---------------- hidden caml_callback_exn --------------------- 
257 #4 0x000000000043126e in caml_execute_signal () 
258 #5 0x000000000043132e in caml_process_pending_signals () 
259 #6 0x0000000000431355 in caml_enter_blocking_section () 
260 #7 0x0000000000437688 in do_write () 
---------------- hidden caml_c_call --------------------------- 
257 #8 0x000000000043786b in caml_flush_partial () 
258 #9 0x00000000004378c2 in caml_flush () 
259 #10 0x000000000043813e in caml_ml_flush () 
260 ... 
otherlibs/systhread/thread.ml 
thread 모듈을 포함시키면 caml_program -> camlThread__entry를 실행하여 
다음과 같이 preempt_signal의 handle로 preempt 함수가 설정된다. 
60 let _ = 
61 Sys.set_signal preempt_signal (Sys.Signal_handle preempt); 
62 thread_initialize(); 
otherlibs/systhreads/st_posix.h 
주기적으로 preemption signal을 밯생시키는 함수(thread모드에서는 pthread로 생성되 
어항상 돌아간다) 
unix signal과 구분되는 내부적인 signal으로 바로 실행되지 않고 
caml_process_pending_signals에서 처리된다. 
327 static void * caml_thread_tick(void * arg) 
328 { 
329 struct timeval timeout; 
330 sigset_t mask; 
331 ... 
337 while(1) { 
340 timeout.tv_sec = 0; 
341 timeout.tv_usec = Thread_timeout * 1000; 
342 select(0, NULL, NULL, NULL, &timeout); 
346 caml_record_signal(SIGPREEMPTION); 
347 } 
348 return NULL; 
349 } 
otherlibs/systhreads/st_stubs.c 
yield에서 caml_master_lock을 놓아주어 다른 thread로 context switching이 가능하 
게 한다. 
741 CAMLprim value caml_thread_yield(value unit) /* ML */ 
742 { 
743 if (st_masterlock_waiters(&caml_master_lock) == 0) return Val_unit; 
744 enter_blocking_section(); 
745 st_thread_yield(); 
746 leave_blocking_section(); 
747 return Val_unit; 
748 } 
순서 
1. 프로그램 시작할때 signal handler thread.ml:preempt 등록 
2. caml_thread_tick함수를 pthread로 생성하고 background에서 계 
속 동작하도록한다(masterlock과 무관) 
3. Thread.create으로 ocaml thread 생성 
4. ocaml threa는 한 시점에 하나만(masterlock 소유) 동작한다. 
5. GC나 IO 작업에서 enter_blocking_section(ocaml thread 동작을 
block시킨다는 의미)을 호출한다. 여기서 preempt signa을 확인하 
고 handler를 호출한다. 
6. handler가 caml_thread_yield를 호출하기에 이르고 
masterlock을 놓아주어 pthread context switching(ocaml 
thread도 동시에) 발생한다. 
7. yield 바로 다음엔 otherlibs/systhreads/st_stubs.c: 
caml_thread_leave_blocking_section 함수에서 
st_masterlock_acquire (&caml_master_lock); 으로 현재 실행중인 ocaml 
thread가 yield하기를 기다린다.
Tag re-arrange 
• changing constant numbers assigned to tag types 
• No_scan_tag 251, Forward_tag 250, Infix_tag 249 ... => No_scan_tag 241, Forward_tag 240, Infix_tag 239 ... 
• cross-compilation 
compile ocamlopt using separated ocaml compiler 
• byterun/mlvalues.h 
#define No_scan_tag 251 
#define Forward_tag 250 
#define Infix_tag 249 
#define Object_tag 248 
#define Closure_tag 247 
#define Lazy_tag 246 
#define Abstract_tag 251 
#define String_tag 252 
#define Double_tag 253 
#define Double_array_tag 254 
#define Custom_tag 255 
• bytecomp/matching.ml 
let inline_lazy_force_cond arg loc = 
... 
Lprim(Pintcomp Ceq, 
[Lvar tag; Lconst(Const_base(Const_int Obj.forward_tag))]), ... 
... 
Lprim(Pintcomp Ceq, 
[Lvar tag; Lconst(Const_base(Const_int Obj.lazy_tag))]), ... 
let inline_lazy_force_switch arg loc = 
... 
(varg, 
{ sw_numconsts = 0; sw_consts = []; 
sw_numblocks = (max Obj.lazy_tag Obj.forward_tag) + 1; 
sw_blocks = 
[ (Obj.forward_tag, Lprim(Pfield 0, [varg])); 
(Obj.lazy_tag, 
Lapply(force_fun, [varg], loc)) ]; 
sw_failaction = Some varg } ))))
Tag re-arrange 
• bytecomp/translcore.ml 
(* other cases compile to a lazy block holding a function *) 
| _ -> 
let fn = Lfunction (Curried, [Ident.create "param"], transl_exp e) in 
Lprim(Pmakeblock(Config.lazy_tag, Immutable), [fn]) 
• utils/config.ml 
let max_tag = 245 
let lazy_tag = 246 
• topdir/Makefile(cross compilation) 
cross-phc: 
@echo "phc cross-compiles reentrant multi runtime ocamlopt" 
cd stdlib; make all CAMLC=ocamlc 
make runtimeopt 
make compilerlibs/ocamlcommon.cma 
cp ~/.opam/4.00.1/bin/ocamlrun ./byterun/ 
make compilerlibs/ocamloptcomp.cma 
make driver/optmain.cmo 
ocamlc -o ocamlopt compilerlibs/ocamlcommon.cma compilerlibs/ocamloptcomp.cma driver/optmain.cmo 
cd stdlib; make allopt RUNTIME=ocamlrun 
• testing(topdir/testsuite/tests/misc/hamming.ml) 
> ../../../ocamlopt -nostdlib -I ../../../stdlib/ hamming.ml 
> ./a.out 
Segmentation fault (core dumped)
Tag re-arrange 
• testing(topdir/testsuite/tests/misc/hamming.ml) 
0x40902c <camlHamming__mul_1018+108> movq $0x8ff,-0x8(%rax) x 
0x409034 <camlHamming__mul_1018+116> mov 0x22983d(%rip),%rdx # 0x632878 x 
0x40903b <camlHamming__mul_1018+123> mov %rdx,(%rax) x 
0x40903e <camlHamming__mul_1018+126> mov %rcx,0x8(%rax) x 
0x409042 <camlHamming__mul_1018+130> mov 0x8(%rbx),%rdxx x 
0x409046 <camlHamming__mul_1018+134> lea 0x18(%rax),%rcx x 
0x40904a <camlHamming__mul_1018+138> movq $0x8ff,-0x8(%rcx)bx x 
0x409052 <camlHamming__mul_1018+146> mov 0x22981f(%rip),%rbx # 0x632878 x 
0x409059 <camlHamming__mul_1018+153> mov %rbx,(%rcx) x 
> 0x40905c <camlHamming__mul_1018+156> mov 0x8(%rdx),%rdx x 
0x409060 <camlHamming__mul_1018+160> mov 0x8(%rdi),%rbx x 
0x409064 <camlHamming__mul_1018+164> imul %rdx,%rbx x 
0x409068 <camlHamming__mul_1018+168> mov %rbx,0x8(%rcx) x 
0x40906c <camlHamming__mul_1018+172> lea 0x30(%rax),%rbx x 
0x409070 <camlHamming__mul_1018+176> movq $0x8ff,-0x8(%rbx)dx x 
0x409078 <camlHamming__mul_1018+184> mov 0x2297f9(%rip),%rdx # 0x632878 x 
0x40907f <camlHamming__mul_1018+191> mov %rdx,(%rbx) x 
0x409082 <camlHamming__mul_1018+194> mov 0x8(%rax),%rdx x 
0x409086 <camlHamming__mul_1018+198> mov 0x8(%rcx),%rax x 
0x40908a <camlHamming__mul_1018+202> add %rdx,%rax x 
0x40908d <camlHamming__mul_1018+205> mov %rax,0x8(%rbx) rax x 
0x409091 <camlHamming__mul_1018+209> mov 0x229d30(%rip),%rax # 0x632dc8 x 
mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj 
Starting program: /home/phc/ocaml_internal/tag_test_svn/testsuite/tests/misc/a.out 
Program received signal SIGSEGV, Segmentation fault. 
0x000000000040905c in camlHamming__mul_1018 () 
(gdb) bt 
bt 
#0 0x000000000040905c in camlHamming__mul_1018 () 
#1 0x0000000000408c34 in camlHamming__fun_1153 () 
#2 0x000000000040fd09 in camlCamlinternalLazy__force_lazy_block_1010 () at camlinternalLazy.ml:27 
#3 0x0000000000408ca9 in camlHamming__fun_1162 () 
#4 0x000000000040fd09 in camlCamlinternalLazy__force_lazy_block_1010 () at camlinternalLazy.ml:27 
#5 0x0000000000409418 in camlHamming__iter_interval_1054 () 
#6 0x0000000000409a9c in camlHamming__entry () 
#7 0x0000000000407979 in caml_program () 
#8 0x0000000000423f72 in caml_start_program () 
#9 0x0000000000424435 in caml_main () 
#10 0x00000000004151a8 in main () 
(gdb)
closure, caml_curry, caml_curry_app, caml_tuplify 
• closure block 
Hd(closure_tag) caml_curry9_8 arity 
3 = int(1) 
arg9 closure value 
Hd(closure_tag) fun ptr (symbol in asm) arity 
- # of remaining args 
arg1, arg2 ... 
fun(arg1, arg2, ... this closure) 
Hd(closure_tag) caml_curry9_2 arity 
15 = int(7) 
caml_curry9_2_app arg2 closure value 
Hd(closure_tag) caml_curry9_1 arity 
17 = int(8) 
caml_curry9_1_app arg1 closure value 
Hd(closure_tag) caml_curry9 arity 
19 = int(9) 
fun ptr (symbol in asm) 
Hd(closure_tag) caml_curry9_7 arity 
5 = int(2) 
caml_curry9_7_app arg8 closure value
closure, caml_curry, caml_curry_app, caml_tuplify 
• closure block 
Hd(closure_tag) caml_curry9_3 arity 
13 = int(6) = 9-3 
caml_curry9_3_app arg3 closure value 
Hd(closure_tag) caml_tuplify2 arity 2 
Hd(closure_tag) camlCtest__fun_1020 
(arg1 - w, this closure block) 
= arg1 * 7 
arity 
1 
7(arg2 - z) 
13 let mult_sum (x,y) = 
14 let z = x+y in 
15 fun w -> w*z;; 
-------------- 
camlCtest__fun_1020 
-int(2) = -5 
camlCtest__add_1014(arg1,arg2) 
= arg1+arg2 
let add (a,b) = a+b;; 
Hd(closure_tag) fun ptr (symbol in asm) arity 
- # of remaining args 
arg1, arg2 ... 
fun(arg1, arg2, ... this closure) 
Hd(closure_tag) caml_curry9_2 arity 
15 = int(7) 
caml_curry9_2_app arg2 closure value
closure, caml_curry, caml_curry_app, caml_tuplify 
• caml_curryN_M(arg, clos) (caml_curryN, caml_curryN_1, ... camlN_(N-1)) 
clos[caml_curryN_(M+1) ; N-M-1; caml_curryN_(M+1)_app; arg; close] 
cmm of caml_curry6_3 
(function caml_curry6_3 (arg/1291: addr clos/1292: addr) 
(alloc 5367 "caml_curry6_4" 5 "caml_curry6_4_app" arg/1291 clos/1292)) 
• caml_apply2(arg1, arg2, clos[caml_curry3; 3; fun_ptr]) 
clos의 arity 3과 현재함수의 arity2를 비교 
– 다르면 : 
call caml_curry3(arg1, clos[caml_curry3; 3; fun_ptr]) 
return clos[caml_curry3_1; 2; caml_curry3_1_app; arg1; clos[caml_curry3; 3; fun_ptr] ] 
call caml_curry3_1(arg2, clos[caml_curry3_1; ...]) 
return clos[caml_curry3_2; 1; arg2; clos[caml_curry3_1; ...]) 
return clos[caml_curry3_2; 1; ...] 
– 같으면 : 
call fun_ptr(arg1, arg2, clos) 
• caml_curryN_(N-1)(arg, clos) 
let clos1 = clos[3]; let clos2 = clos1[4]; let clos3 = clos2[4] ... let clos(N-1) = clos(N-2)[4]; 
app clos(N-1)[2](clos(N-2)[3], clos(N-3)[3] ... , clos1[2], arg, clos(N-1)) 
• caml_curry3_2(arg3, clos) 
let clos1 = clos[3]; let clos2 = clos1[4] 
app clos2[2](clos1[3], clos1[2], arg3, clos2) 
clos [caml_curry3_2 ; 1; arg2; clos1] 
clos1[caml_curry3_1 ; 2; caml_curry3_1_app; arg1; clos2] 
clos2[caml_curry3 ; 3; fun_ptr] 
call fun_ptr(arg1, arg2, arg3)
closure, caml_curry, caml_curry_app, caml_tuplify 
• caml_curryN_M_app(arg(M+1), .. argN, clos) 
let clos1 = clos[4]; let clos2 = clos1[4]; ...; let closM = clos(M-1)[4] 
app closM[2] (clos(M-1)[3], ..., clos1[3], clos[3], arg(M+1), arg(M+2), .. argN, clos) 
(function caml_curry5_3_app (arg4/1144: addr arg5/1145: addr clos/1143: addr) 
(let 
(clos/1146 (load (+a clos/1143 32)) clos/1147 (load (+a clos/1146 32)) 
clos/1148 (load (+a clos/1147 32))) 
(app (load (+a clos/1148 16)) (load (+a clos/1147 24)) 
(load (+a clos/1146 24)) (load (+a clos/1143 24)) arg4/1144 arg5/1145 
clos/1148 addr))) 
call fun_ptr (arg1, arg2, arg3, arg4, arg5, closM) 
• 정리 
– closure block 구조 
[Hd; 실행할 함수; 추가로 입력받을 매개변수의 개수 arity; 현재 클로져가 저장하는 매개변수들 ...] 
– curry closure block 구조 
[Hd; caml_curryN_m; (N-m) arity; caml_curryN_m_app; arg_m; clos[Hd; caml_curryN_(m-1);..] ] 
[Hd; caml_curryN_(N-1); 1 arity; arg(N-1); clos[Hd; ...] ] 
[Hd; caml_curryN; N arity; fun_ptr] 
– caml_curryN caml_curryN_1, .. caml_curryN_(N-2) 
N클로져에 argument를 하나 추가하기 위해서, 새로운 블록을 만들고, 기존에 클로져는 링크 
– caml_curryN_m_app(arg(m+1), ... argN, closure[arg_m, clsore[arg_(m-1) ....]) 
m이후 매개변수를 적용하여 (N-m)클로져 실행, 즉 뒤쪽 매개변수를 이 함수에서 적용, 앞에 매개변수는 클로져에 저장되어 있다 
고 가정, call f(arg1, arg2, ... arg(m+1), ... argN) 
– caml_applyN(arg1, arg2, .. argN, closure) 
클로져가 N arity면 바로 실행 f(arg1, arg2, ..., argN, arg1_clos, ...)

More Related Content

Featured

Everything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTEverything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTExpeed Software
 
Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsPixeldarts
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthThinkNow
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfmarketingartwork
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024Neil Kimberley
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)contently
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024Albert Qian
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsKurio // The Social Media Age(ncy)
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Search Engine Journal
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summarySpeakerHub
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next Tessa Mero
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentLily Ray
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best PracticesVit Horky
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project managementMindGenius
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...RachelPearson36
 
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Applitools
 

Featured (20)

Everything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTEverything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPT
 
Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage Engineerings
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental Health
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
 
Skeleton Culture Code
Skeleton Culture CodeSkeleton Culture Code
Skeleton Culture Code
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie Insights
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search Intent
 
How to have difficult conversations
How to have difficult conversations How to have difficult conversations
How to have difficult conversations
 
Introduction to Data Science
Introduction to Data ScienceIntroduction to Data Science
Introduction to Data Science
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best Practices
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project management
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
 
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
 

Ocaml internal (description of runtime system in Korean)

  • 1. Ocaml Internal Hyunchul Park, PL lab, POSTECH
  • 2. mlvalue (Ocaml value, mlvalues.h) • Immediate value • Block (a pointer into the Ocaml heap, structured value) • No scan tag (a pointer pointing outside Ocaml heap)
  • 3. mlvalue Word Immediate value 1 Word Address to heap block 0 0 Immediate value Block Pointer to heap block Heap 할당시 align 만큼 0으로 셋팅
  • 4. Ocaml heap block structure Header : Hd_val(v) Value v Field는 또 다른 value Field (v, 0) … Field (v, Wosize_val(v)-1 ) Address to heap block 0 0 Block Word – 10bit 2bit 8bit Size : Wosize_hd(hd) Color : Color_hd(hd) Tag : Tag_hd(hd)
  • 5. Young heap caml_young_start caml_young_ptr caml_young_end  Grow Allocated • caml_aligned_malloc함수가 할당한 메모리 Minor_heap_def 크기로 할당됨 • Page_size 기준으로 align 된 주소 • Memory.h:Alloc_small 매크로 함수를 참고하면 간단한 할당 과정을 알수있다.
  • 6. Old heap Heap chunk Heap block (alive) Empty blocks (blue color) Heap chunk chain Heap chunk dependency (Block field pointing to anoth er block)
  • 7. caml_ref_table, caml_weak_ref_table table.base table.ptr table.threshold(??limit) addresses of value  Increase by one word p1 (address in old heap) Value v = p2 p2 (address in young heap) Value in young heap • old heap에서 young heap으로 가리키는 경우 table에 추가해 둔다. Mutable ref 때문에 old heap에서 young heap을 참조하는 경우가 발생 • caml_alloc_table에서 caml_stat_alloc으로 할당 caml_realloc_ref_table참고 • value(pointer to block)을 저장하는 것이 아니라, value를 저장하는 word address를 저장한다. Oldify에서 필요로 하기때문
  • 8. Oldify • Oldify는 promote와 같은 개념, young heap에 있는 block을 old heap으로 옮긴다. • Oldify와 관련된 함수, 변수들 – void caml_oldify_one (value v, value *p) • p(pointer to value) --> v(value) --> block in young heap • 실제로 블록의 promot를 실행한다. 블록의 모든 필드를 따라가면서 처리하지는 않고 oldify_todo_list에 보관한다. caml_oldify_mopup 참고 – void caml_oldify_mopup (void) – #define Oldify(p) • 먼저 value가 young heap에 있는 블록인지 확인하고, caml_oldify_one을 호출한다. • p --> __oldify__v__ --> block in young heap – static value oldify_todo_list = 0; – void caml_oldify_mopup (void)
  • 9. void caml_oldify_one (value v, value *p) void caml_oldify_one (value v, value *p) { value result; header_t hd; mlsize_t sz, i; tag_t tag; tail_call: if (Is_block (v) && Is_young (v)){ Assert (Hp_val (v) >= caml_young_ptr); hd = Hd_val (v); if (hd == 0){ /* If already forwarded */ *p = Field (v, 0); /* then forward pointer is first field. */ }else{ tag = Tag_hd (hd); if (tag < Infix_tag){/* 첫번째 일반적인 케이스 */ value field0; sz = Wosize_hd (hd); result = caml_alloc_shr (sz, tag); *p = result; field0 = Field (v, 0); Hd_val (v) = 0; /* Set forward flag */ Field (v, 0) = result; /* and forward pointer. */ if (sz > 1){ Field (result, 0) = field0; Field (result, 1) = oldify_todo_list; /* Add this block */ oldify_todo_list = v; /* to the "to do" list. */ }else{ Assert (sz == 1); p = &Field (result, 0); v = field0; goto tail_call; } }else if (tag >= No_scan_tag){ /* tag >= No_scan_tag 경우는 다음 슬라이드에서 */ }else if (tag == Infix_tag){ mlsize_t offset = Infix_offset_hd (hd); caml_oldify_one (v - offset, p); /* Cannot recurse deeper than 1. */ *p += offset; }else{ /* 다음 슬라이드에서 */ } }else{ *p = v; } } • v : 현재 young heap 할당된 블록으로 oldify 대상이다 • result : old heap의 블록을 가리키는 value • p : result를 저장할 어떤 word를 가리킨다. 최종적으로 p(word)result • otl(oldify_todo_list) : oldify는 필드에 대해서 재귀적으로 수행해 주어야 하는 데 otl linked list에 넣어두고 caml_oldify_mopup에서 처리한다. Oldify_todo_list는 linked list의 해드에 해당한다. • next_otl : 코드에서 oldify_todo_list에 추가할 해드 노드를 마련했다. 원래 otl 은 linked list의 next node로 가리킨다. p val0 v val1 *p hd Field(v,0) … Field(v,sz-1) result val2 hd … p val1 val0 v *p val2 위의 그림은 result = caml_alloc_shr (sz, tag); 코드를 실행한 시점 밑에 그림은 첫번째 케이스로 실행한 최종 결과 oldify_todo_list val1 0 val2 Field(v,i) … result val2 hd Field(v,0) next_otl uninit 0 oldify_todo_list의 다음 노드(young heap)
  • 10. void caml_oldify_one (value v, value *p) • Forward란 이전 슬라이드에서 old heap에 caml_alloc_shr 로 할당하고 young heap 블록에서 가리키는 관 계를 설정해 놓은 경우, young heap 블록을 forwarded 라고 한다. – Old heap value(r), young heap value(orig_v, v), pointer to target value(p) – Forwarded condition 이전 슬라이드 최종상태와 같이, Hd(v)==0이고 Field(v,0)==r으로 old heap을 가리키고, Field(r,0)==Field(orig_v, 0)으로 첫번째 필드는 먼저 옮 겨 놓음. Field(r,1)==next node of otl • hd == 0 이전 슬라이드에서 처럼 forward 되었기 때문에, *p만 old heap value인 Field(v,0)로 assign • Tag >= No_scan_tag 필드를 따라 가야할 필요가 없으므로 caml_alloc_shr으로 old heap에 할당하고 Field에 index마다 one-to-one assign 여기서도 forward tag으로 설정했다. • Infix_tag value v 블록을 포함하는 바깥 블록을 v-offset으로 잡는다. 바깥 블록 value으로 caml_oldify 재귀호출하고 *p+=offse으로 다시 infix 블록을 가리키도록 바꾸어 놓는다. void caml_oldify_one (value v, value *p) { value result; header_t hd; mlsize_t sz, i; tag_t tag; tail_call: if (Is_block (v) && Is_young (v)){ Assert (Hp_val (v) >= caml_young_ptr); hd = Hd_val (v); if (hd == 0){ /* If already forwarded */ *p = Field (v, 0); /* then forward pointer is first field. */ }else{ tag = Tag_hd (hd); if (tag < Infix_tag){ /* 이전 슬라이드에서 */ }else if (tag >= No_scan_tag){ sz = Wosize_hd (hd); result = caml_alloc_shr (sz, tag); for (i = 0; i < sz; i++) Field (result, i) = Field (v, i); Hd_val (v) = 0; /* Set forward flag */ Field (v, 0) = result; /* and forward pointer. */ *p = result; }else if (tag == Infix_tag){ mlsize_t offset = Infix_offset_hd (hd); caml_oldify_one (v - offset, p); /* Cannot recurse deeper than 1. */ *p += offset; }else{ } }else{ *p = v; } }
  • 11. void caml_oldify_one (value v, value *p) • oldify_todo_list의 head node를 꺼내서 v로 갖 는다. v(young heap value)와 old heap value의 구조는 앞에서 forwarded block 조건을 따른다. • Field(new_v,0)으로 먼저 옮긴 Field(orig_v,0) 을 caml_oldify_one으로 처리하고 • 나머지 필드들도 마찬가지로 oldify 한다. • block이 아닌 경우라면 단순히 old heap block의 필드에 대응하는 young heap block 필드를 assig 한다. /* Finish the work that was put off by [caml_oldify_one]. Note that [caml_oldify_one] itself is called by oldify_mopup, so we have to be careful to remove the first entry from the list before oldifying its fields. */ void caml_oldify_mopup (void) { value v, new_v, f; mlsize_t i; while (oldify_todo_list != 0){ v = oldify_todo_list; /* Get the head. */ Assert (Hd_val (v) == 0); /* It must be forwarded. */ new_v = Field (v, 0); /* Follow forward pointer. */ oldify_todo_list = Field (new_v, 1); /* Remove from list. */ f = Field (new_v, 0); if (Is_block (f) && Is_young (f)){ caml_oldify_one (f, &Field (new_v, 0)); } for (i = 1; i < Wosize_val (new_v); i++){ f = Field (v, i); if (Is_block (f) && Is_young (f)){ caml_oldify_one (f, &Field (new_v, i)); }else{ Field (new_v, i) = f; } } } } v val1 oldify_todo_list 0 val2 new_v val2 hd Field(v,0) = val3 next_otl uninit val1 f val3 caml_oldify_one v caml_oldify_one p
  • 12. void caml_oldify_one (value v, value *p) • lazy_tag, forward_tag란? lazy.ml 주석참고 – lazy_tag붙은 블록은 size=1 이며, 필드는 unit->’a type의 closure이다. ‘a type의 블록의 evaluatio을 delay해 놓은 블록 이다. – forward_tag 블록은 size=1이며, 필드는 ‘a type 이다. 즉 computed value를 가리키는 포인터 • f : forward_tag 블록이 가리키는 value • ft : f의 tag • vv = 0 when Is_block(f) && !Is_young(f) && !Is_in_value_area(f) • ft in [Forward_tag, Lazy_tag, Double_tag] 이 면 forward_tag 블록을 생략하고 short-circuiting 할 수 없다. TODO 구체적인 이유는?? • 이런 경우에는 forward_tag 블록도 old heap에 잡 아놓고 f 블록도 oldify한다. • 그외의 경우에는 short-circuit(중간에 indirectio으로 놓은 forward_tag 블록을 무시하 고 바로 oldify 해서연결) void caml_oldify_one (value v, value *p) { value result; header_t hd; mlsize_t sz, i; tag_t tag; tail_call: if (Is_block (v) && Is_young (v)){ Assert (Hp_val (v) >= caml_young_ptr); hd = Hd_val (v); if (hd == 0){ /* If already forwarded */ *p = Field (v, 0); /* then forward pointer is first field. */ }else{ tag = Tag_hd (hd); if (tag < Infix_tag){ /* 이전 슬라이드에서 */ }else if (tag >= No_scan_tag){ }else if (tag == Infix_tag){ }else{ value f = Forward_val (v); tag_t ft = 0; int vv = 1; Assert (tag == Forward_tag); if (Is_block (f)){ if (Is_young (f)){ vv = 1; ft = Tag_val (Hd_val (f) == 0 ? Field (f, 0) : f); }else{ vv = Is_in_value_area(f); if (vv){ ft = Tag_val (f); } } } if (!vv || ft == Forward_tag || ft == Lazy_tag || ft == Double_tag){ /* Do not short-circuit the pointer. Copy as a normal block. */ Assert (Wosize_hd (hd) == 1); result = caml_alloc_shr (1, Forward_tag); *p = result; Hd_val (v) = 0; /* Set (GC) forward flag */ Field (v, 0) = result; /* and forward pointer. */ p = &Field (result, 0); v = f; goto tail_call; }else{ v = f; /* Follow the forwarding */ goto tail_call; /* then oldify. */ } } }else{ *p = v; } }
  • 13. Heap chunk structure heap_chunk_head request padding1 heap_chunk_head Chunk body padding2 typedef struct { hp (hp%Page_size==0) void *block; /* address of the malloced block this chunk live in */ asize_t alloc; /* in bytes, used for compaction */ asize_t size; /* in bytes */ char *next; } heap_chunk_head; • hp 주소는 Page_size단위로 align • chunck body부분의 Page_size 단위를 caml_page_table이라는 hash table로 관리하 며, 주어진 주소 공간의 페이지가 어떤 용도로 사용되고 있는지 알아내는데 쓰인다. • Heap_chunk_head.next 로 연결하여 linked list를 구성한다. Linked list head는 caml_heap_start • caml_alloc_shr, expand_heap 등 할당관련 함수 참고, call graph 참고 • 실제 malloc, free가 발생하는 단위가 heap chunk 할당된 chunk 안에서 block을 구성하여 사용한다.
  • 14. Free list(old heap) Block Block • Chunk단위가 아니라, Block 단위로 free list를 관리한다. Free list는 사용중이지 않은 block들을 엮은 linked list이다. • Major GC에서 해제할 block으로 확인되면 caml_fl_merge_block 호출하여 freelist에 추가 • caml_alloc_shr으로 블록을 할당할 때, caml_fl_allocate으로 free list에서 사용가 능한 크기의 블록을 찾아본다. • Sentinel은 전역변수로 선언되어 있으며 free list linked list의 head역할을 한다. Linked list는 주소를 기준으로 정렬되어 있고 sentinel은 항상 가장낮은주소를 갖는다. • Flp는 할당할때 효율성을 위해서 block 크기 순으로 증가하는 순서로 골라둔다. First_fit_polic에서만 사용됨 • Caml_fl_merge, beyond 같은 free list block 포인터의 역할 참고 Block Block Block Block Sentinel flp table
  • 15. freelist.c(First_fit_policy 기준) flp table Fl_head sentinel flp table • Sentinel은 static global 변수로 잡혀 있으므로 항상 다른 블록보다 작은 주소 값을 갖는다. • 나머지 블록들은 free block이다. 즉, old heap의 블록이면서, 할당되지 못한 빈 공간에 해 당하는 블록들이다. • free block들은 sentinel을 head로 하여 linked list chain을 구성한다. 다음 노드를 접근하는 Next(b) 매크로를 제공한다. 블록의 주소 순서대로 정렬되어 있다. • flp table에는 주소가 증가하는 순서이면서 동시에 크기가 증가하는 순서로 블록을 골라두 었다. • 정확히는 증가하는 크기로 선택된 블록의 바로 이전 블록들을 저장한다. 위에 그림에서 보 면 빨간 선으로 연결된 블록들이 크기순을 선택된 블록이고, flp table은 그 바로 이전 블록 들을 모아두었다. • 또한, flp table은 항상 모든 freelist에 대해서 관리되는 것은 아니다. 위에 그림처럼 뒷부분 은 가리키지 않는 상태일 수 있다. 코드 내에 extend the flp array 부분 참고 항상 모든 조건을 만족하도록 모든 freelist를 확인하지 않는다. flp는 일종의 cache 기능을 한다. 만약 flp 탐색으로 실패하면 freelist를 하나씩 순회하며 찾는다.
  • 16. freelist.c(First_fit_policy 기준) 함수 설명 • static void fl_check (void) freelist가 제대로 관리되고 있는지 검사하는 디버그 코드 • static char *allocate_block (mlsize_t wh_sz, int flpi, char *prev, char *cur) 할당하기 사용할 블록을 flp table에서 찾았다. table에서 flpi 번째 블 록이며, 바로전 블록은 prev, 찾은 대상블록은 cur이다. 블록을 사용할 만 큼 잘라서 사용하고, 블록 헤더를 셋팅해 준다. • char *caml_fl_allocate (mlsize_t wo_sz) 외부에서 호출하는 함수이다. wo_sz 크기의 블록을 freelist에서 찾아오라 는 뜻이다. 없으면 NULL 리턴하고, 블록을 만들면 그 주소를 리턴 • static void truncate_flp (char *changed) changed 주소 이후의 블록들을 flp table에서 빼버린다. • char *caml_fl_merge_block (char *bp) 외부에서 호출하는 함수. freelist에 추가하고 싶은 블록을 넘기면 알아서 처리한다. 필요시 연속된 blue 블락들을 merge한다. • Void caml_fl_add_blocks (char *bp) caml_alloc_shr에서 호출한다. 새로 할당한 (blue color의) 블록 체인을 끼워 넣는다. Flp table은 업데이트 하지 않는듯하다(TODO로 아직 구현안 함) • void caml_make_free_blocks (value *p, mlsize_t size, int do_merge, int color) old heap chunk만 만들어 놓았을때, 이거를 free block으로 쪼개서 free list에 넣는 작업까지 한다. caml_fl_add_blocks의 작업을 포함해서 조금 더 일을 한다.
  • 17. freelist.c(First_fit_policy 기준) caml_fl_allocate selected Fl_head sentinel flp table • char *caml_fl_allocate (mlsize_t wo_sz) flp table after update 외부에서 호출하는 함수이다. wo_sz 크기의 블록을 freelist에서 찾아오라 는 뜻이다. 없으면 NULL 리턴하고, 블록을 만들면 그 주소를 리턴 • flp table을 따라가면서 사이즈가 맞는 블록을 찾으면 allocate_block 호 출하고 결과를 반환 • 못 찾는다면, flp_tab 마지막 블록 이후로 따라가면서 flp table을 늘려 나간다(extend the flp array) 그러다가 찾으면 마찬가지로 allocate_block호출하여 반환 • flp table을 늘려가면서 찾는중에 flp table 크기에 제한되어 멈추는 경 우 flp table을 늘리지는 않고 계속 free list 쫓아가면서 찾는다. • 반환하기전 공통작업(update_flp label) allocate_block에서는 flp table update하지 않기 때문에 후속 작업이 필요하다. 위 그림에서 처럼 flp table에서 블록이 선택되었다고 하자(selected) 그 블록을 제거하고도 flp table이 구성되어야 한다. array 중간에 variable sized sub array를 끼워 넣어야한다. 때문에 buffer공간과 memmove함수가 필요하다.
  • 18. freelist.c(First_fit_policy 기준) allocate_block Fl_head sentinel flp table • static char *allocate_block (mlsize_t wh_sz, int flpi, char *prev, char *cur) 할당하기 사용할 블록을 flp table에서 찾았다. table에서 flpi 번째 블록이며, 바로전 블록은 prev, 찾은 대상블록은 cur이다. 블록을 사용할 만큼 잘라서 사용하고, 블록 헤더를 셋팅해 준다. • 할당하려는 사이즈 wh_sz는 free block보다 작거나 같다. – Case 0 : 크기가 정확히 일치하는 블록 전체를 쓴다. – Case 1 : 1 word만큼 남는 경우 1 word는 size==0 해더만 을 갖는 free block을 남는다. 135 Hd_op (cur) = Make_header (0, 0, Caml_white); – Case 2 : 필요한 크기만큼 블록을 뒤쪽에 잡고, 앞에 헤더 를 줄어든 크기로 갱신한다(빨간색 블록이 남음) 146 Hd_op (cur) = Make_header (Wosize_hd (h) - wh_sz, 0, Caml_blue);
  • 19. freelist.c(First_fit_policy 기준) caml_fl_merge_block Fl_head sentinel added flp table • char *caml_fl_merge_block (char *bp) 외부에서 호출하는 함수. freelist에 추가하고 싶은 블록을 넘기면 알아서 처리한다. • added block이 추가되는 위치의 앞뒤로 블록을 확인한다. 만약 앞이나 뒤 중에 연속된 블 록이 있으면 합친다. 만약 합쳐서 크기가 제한 범위를 넘치면(Max_wosize) 합치지 않는다. • 앞 뒤 블록 외에도 1 word 짜리 블록 last_fragment와도 비교하여 가능하면 합친다. • 만약 합치는 것이 불가능하다면 그냥 끼워넣는다. 446 Next (bp) = cur; 447 Next (prev) = bp; • bp 블록이 sz==0이면 여기서 last_fragment로 등록된다. last_fragment가 합쳐지지 못하 고 남겨질 수도 있다는 뜻. 나중에 GC에서 일괄 처리될 것이다. • 추가되는 블록 뒤로는 flp table 정보를 날려버린다. (truncate_flp)
  • 20. freelist.c(First_fit_policy 기준) caml_fl_add_blocks sentinel • void caml_fl_add_blocks (char *bp) caml_alloc_shr에서 호출한다. 새로 할당한 blue color의 블록 체인을 끼워 넣는다. flp table은 업데이트 하지 않는듯하다(TODO로 아직 구현안함) • caml_fl_allocate 호출하고 실패하여 NULL 받았을 때만 호출되는 함수이다. bp > fl_last이면, 즉 추가될 블록 체인이 freelist 이후의 주소라면, flp table에 바로 추가 한다. 주소 순서도 맞고, 크기 순서도 맞으니깐 • 추가되는 블록은 expand_heap에 의해서 할당된 chunk의 블록들이므로 마지막 블록의 크기 는 Max_wosize이하이다. flp table Fl_head remainder bp(Max_wosize) (Max_wosize)
  • 21. Address Align • 주소를 align하여 address % mod = 0 만 족하도록 한다 • Bitwise 연산이 더 빠르고, bit수도 아낄 수 있으므로 align하여 사용하는 경우가 많다. • Block address % 4 = 0 • Page address % Page_size = 0
  • 22. Garbage Collection • OCAML GC의 특징들 Generational, incremental, write barriered, stop&copy(young heap), mark&sweep(old heap), freelist • Generational young heap과 old heap으로 나누어 관리한다. 처음에 블록할당은 young heap 에서 한다. Young heap이 차면, young heap에서 살아남은 블록을 old heap으 로 넘긴다. Young heap(minor GC)와 old heap(major GC)는 다른 GC 알고리즘 사용 • Incremental Old heap을 처리하는 major GC는 incremental하다. Major GC가 실행되었을때 모든 작업을 한번에 끝내는 것이 아니라, 작업량을 분할하여 여러 번에 걸쳐 수 행한다. GC작업이 길어져서 프로그램의 real time 반응을 떨어뜨리지 않도록 GC작업 중간중간에 본래 프로그램(mutator)을 동작시키는 것이다. • Write barriered 블록의 필드들도 value이다. Value에 따라 포인터일 수도 있다. 블록의 필드에 기록(write, assign)하는 작업을 함부로 할 경우에, GC의 invariant를 위반할 수 있다. 또한, old heap에서 young heap으로 가리키는 backward pointing은 따로 관리해 주어야 한다. 기록에 앞서 필요한 작업을 수행해야 한다는 의미에서 write barriered라고 한다.
  • 23. Garbage Collection • OCAML GC의 특징들 Generational, incremental, write barriered, stop&copy(young heap), mark&sweep(old heap), freelist • Stop&copy(young heap, minor GC) young heap에서 사용중인 블록들을 major로 넘긴다(oldify) oldify가 완료되면, young heap은 비운다. • Mark&sweep(old heap, major GC) 사용중인 블록을 가려내기 위해서 블록에 색칠을 한다. 바로 접근가능 한 블록들(roots)부터 시작해서 블록이 가리키는(pointer) 다른 포인터들 을 DFS 탐색으로 순회하며 모두 칠한다(darken). Old heap을 모두 순회하면서, 칠해지지 않은 블록을 freelist에 포함시켜 관리한다. • Freelist old heap은 정해진 크기의 heap chunk 단위로 malloc된다. 블록은 heap chunk내에서 논리적으로 쪼개서 쓰는 것이다. 그렇기 때문에 GC 에서 지우기로 결정한 블록은 바로 free하는 것이 아니라 freelist에 넣어 관리한다.
  • 24. GC Compact algorithm • Ocaml에서 관리하는 메모리 단위는 크게 두 가지로 나눌 수 있다. – Block : Ocaml 객체와 대응된다. Block header와 Field들이 연속 되어 있다. Minor, major GC에서 관리하는 대상이다. – Chunk : Ocaml이 malloc API를 통해서 실제로 할당해 둔 메모리 공간이다. Chunk 하나에 여러 Block들이 저장되기도 한다. Block 과 Chunk가 일치할 수도 있지만, Chunk 안에 여러 Block들이 저 장되고 해제되어 free list도 관리되다가 다른 Block이 잡히기도 한다. • GC에서는 Block 단위로 사용중인 Block과 불필요한 Block을 구분한다. 그러나, 메모리 free는 chunk 단위로 일어난다. • Chunk의 사용 비율이 일정 기준 이하로 낮아지면 chunk 들 안에 할당된 block의 위치를 이동시켜(compact)하고 • 빈 chunk들을 대상으로 free 시킨다.
  • 25. GC Compact algorithm • Compact 알고리즘에서는 사용중인 block들을 모아서 앞 쪽 chunk에서 부터 채워 넣는다. 물 론, chunk의 남는 공간이 block 크기보다 작다면, 맞는 빈 공간을 다른 chunk에서 찾는다. • Block을 chunk내의 다른 위치로 copy 해야 하는데, 복잡한 문제들이 있다. – 하나의 block을 가리키는 pointer가 여러 개 있을 수 있다. Block A를 옮기려면 A를 가리 키는 모든 포인터들을 알고, 그들이 가리키는 주소도 옮겨 줘야 한다. – Infix_tag를 갖는 block이 있다. Infix block은 block 내부에서 자신을 포함하는 외부 블록 을 가리킨다. Infix block의 Header.size는 offset 을 의미한다. – Infix_tag를 포함하는 block은 closure_tag이며, 따라서 Infix offset으로 밖의 block을 쫓아 나가는 것은 1회만 적용 가능하다고 추측한다. Oldify 코드 참고, interp 에서 재귀함수 closure에서 재귀호출을 위해서 라고 추측 Infix hd val2 Closure hd Field 0 offset = Bosize_hd(Infix hd) Header Field New block Header Field
  • 26. Inverted pointer • 블록을 가리키는 포인터들의 목록을 만드는 방법으로 inverted pointer chain을 사용한다. • 하나의 포인터에 대해서 inversion을 하는것을 invert_pointer_at 함수로 실행한다. • Root 포인터들을 순회하면서 inversion을 하고, Chunck에 있는 block 순회를 하면서 inversion 을 한다. • Pointer inversion이 끝나면, 해당 블록을 옮길 공간을 정해놓고(실제로 복사는 하지 않는다) 그 쪽 주소로 가리키도록, chain을 따라가면서 바꾼다. Header Field Header Field Header Field New block New block Header Field
  • 27. Double inversion • Double inversion은 좀더 복잡하다. Double inversion은 infix tag때문에 생기는 문제를 해결하 기 위해서 도입되었다. Infix block은 block 내부에 있다. 때문에 chunk block 순회할 때는 검사 되지 않는다. 대신 포인터에 의해서 block 내부에 있는 infix block을 가리키는 경우가 발생한 다. • Infix tag는 하나의 블록에 여러 개 존재할 수 있으며, 하나의 infix block은 여러 개의 포인터가 가리킬 수 있다. 이 때문에 2차원의 chain을 구성한다. • Infix block은 header의 size를 offset으로 하여, infix가 밖의 closure안에 몇 바이트 떨어져있는 지 표시한다. 그러므로 약간 다른 방법으로 포인팅한다고 볼 수 있다. 점선으로 표시 • 아래 그림의 모든 포인터들은 clousre block이 할당되는 주소 dependent하다. 그러므로 묶어 서 관리해야 한다. Double inversion chain으로 관리한다. Closure hd Field 0 Infix1 hd Infix2 hd
  • 28. Double inversion • 모든 포인터의 inversion을 실행한 결과는 아래와 같다. • 빨간색으로 0~3번을 붙이는데(encoded heade의 마지막 2bit color), inversion에서 중요한 분 류 번호이다. – color 00 : 마지막 2bit를 제외하고 포인터로 해석, Infix block이 아닌 일반 block에 쓰는 chain의 포인터 – color 01 : Infix header 임을 알려준다, encoded heade가 아님 – color 10 : Infix block에 쓰는 chain의 포인터 – color 11 : 일반 block에 쓰는 chain의 마지막과 double inversion chain의 끝에 있는 closure header를 표시 • Inversion 하기 전에, header encoding을 한다. 미처 인코딩되지 못한 헤더와 포인터, 이미 encoding된 포인터들을 구분하기 위해 번호를 붙이는 것이다. • 새로 할당된 위치로 virtually reallocate하면서 포인터를 다시 돌려놓는다(revert), 헤더를 원래 대로 돌려놓는다(decoding) 0 0 0 0 0 0 0 0 0 Closure hd 3 Field 0 Infix1 hd 1 Infix2 hd 1 3 0 0 Closure hd 3 2 2 1 2 2 0 Field 0 2 2
  • 29. compact.c:invert_pointer_at case 0, 3 static void invert_pointer_at (word *p) { word q = *p; Assert (Ecolor ((intnat) p) == 0); /* Use Ecolor (q) == 0 instead of Is_block (q) because q could be an inverted pointer for an infix header (with Ecolor == 2). */ if (Ecolor (q) == 0 && (Classify_addr (q) & In_heap)){ switch (Ecolor (Hd_val (q))){ case 0: case 3: /* Pointer or header: insert in inverted list. */ *p = Hd_val (q); Hd_val (q) = (header_t) p; break; case 1: /* Infix header: make inverted infix list. */ /* Double inversion: the last of the inverted infix list points to the next infix header in this block. The last of the last list contains the original block header. */ { /* This block as a value. */ value val = (value) q - Infix_offset_val (q); /* Get the block header. */ word *hp = (word *) Hp_val (val); while (Ecolor (*hp) == 0) hp = (word *) *hp; Assert (Ecolor (*hp) == 3); if (Tag_ehd (*hp) == Closure_tag){ /* This is the first infix found in this block. */ /* Save original header. */ *p = *hp; /* Link inverted infix list. */ Hd_val (q) = (header_t) ((word) p | 2); /* Change block header's tag to Infix_tag, and change its size to point to the infix list. */ *hp = Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); }else{ Assert (Tag_ehd (*hp) == Infix_tag); /* Point the last of this infix list to the current first infix list of the block. */ *p = (word) &Field (val, Wosize_ehd (*hp)) | 1; /* Point the head of this infix list to the above. */ Hd_val (q) = (header_t) ((word) p | 2); /* Change block header's size to point to this infix list. */ *hp = Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); } } break; case 2: /* Inverted infix list: insert. */ *p = Hd_val (q); Hd_val (q) = (header_t) ((word) p | 2); break; } } } p val0 q val1 val1 val3 val2 val3 *p = Hd_val (q); val0 Hd_val (q) = (header_t) p; p val0 q val1 val3 val0 val2 val3 *p = Hd_val (q); val0 Hd_val (q) = (header_t) p;
  • 30. compact.c:invert_pointer_at case 1, Tag_ehd (*hp) == Closure_tag static void invert_pointer_at (word *p) { word q = *p; Assert (Ecolor ((intnat) p) == 0); /* Use Ecolor (q) == 0 instead of Is_block (q) because q could be an inverted pointer for an infix header (with Ecolor == 2). */ if (Ecolor (q) == 0 && (Classify_addr (q) & In_heap)){ switch (Ecolor (Hd_val (q))){ case 0: case 3: /* Pointer or header: insert in inverted list. */ *p = Hd_val (q); Hd_val (q) = (header_t) p; break; case 1: /* Infix header: make inverted infix list. */ /* Double inversion: the last of the inverted infix list points to the next infix header in this block. The last of the last list contains the original block header. */ { /* This block as a value. */ value val = (value) q - Infix_offset_val (q); /* Get the block header. */ word *hp = (word *) Hp_val (val); while (Ecolor (*hp) == 0) hp = (word *) *hp; Assert (Ecolor (*hp) == 3); if (Tag_ehd (*hp) == Closure_tag){ /* This is the first infix found in this block. */ /* Save original header. */ *p = *hp; /* Link inverted infix list. */ Hd_val (q) = (header_t) ((word) p | 2); /* Change block header's tag to Infix_tag, and change its size to point to the infix list. */ *hp = Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); }else{ Assert (Tag_ehd (*hp) == Infix_tag); /* Point the last of this infix list to the current first infix list of the block. */ *p = (word) &Field (val, Wosize_ehd (*hp)) | 1; /* Point the head of this infix list to the above. */ Hd_val (q) = (header_t) ((word) p | 2); /* Change block header's size to point to this infix list. */ *hp = Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); } } break; case 2: /* Inverted infix list: insert. */ *p = Hd_val (q); Hd_val (q) = (header_t) ((word) p | 2); break; } } } p val0 q val1 val1 val3 val2 New encoded header (offset, Infix_tag, 3) Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); val8 *p = *hp; offset = Bosize_hd(val3) val val5 val6_0 val4 hp val7_i val6_1 val6_2 while (Ecolor (*hp) == 0) hp = (word *) *hp; … hp val9 Assert (Ecolor (*hp) == 3); val0 | 2 Hd_val (q) = ((word) p | 2); val8 the first infix header Closure_tag
  • 31. compact.c:invert_pointer_at case 1, Tag_ehd (*hp) != Closure_tag static void invert_pointer_at (word *p) { word q = *p; Assert (Ecolor ((intnat) p) == 0); /* Use Ecolor (q) == 0 instead of Is_block (q) because q could be an inverted pointer for an infix header (with Ecolor == 2). */ if (Ecolor (q) == 0 && (Classify_addr (q) & In_heap)){ switch (Ecolor (Hd_val (q))){ case 0: case 3: /* Pointer or header: insert in inverted list. */ *p = Hd_val (q); Hd_val (q) = (header_t) p; break; case 1: /* Infix header: make inverted infix list. */ /* Double inversion: the last of the inverted infix list points to the next infix header in this block. The last of the last list contains the original block header. */ { /* This block as a value. */ value val = (value) q - Infix_offset_val (q); /* Get the block header. */ word *hp = (word *) Hp_val (val); while (Ecolor (*hp) == 0) hp = (word *) *hp; Assert (Ecolor (*hp) == 3); if (Tag_ehd (*hp) == Closure_tag){ /* This is the first infix found in this block. */ /* Save original header. */ *p = *hp; /* Link inverted infix list. */ Hd_val (q) = (header_t) ((word) p | 2); /* Change block header's tag to Infix_tag, and change its size to point to the infix list. */ *hp = Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); }else{ Assert (Tag_ehd (*hp) == Infix_tag); /* Point the last of this infix list to the current first infix list of the block. */ *p = (word) &Field (val, Wosize_ehd (*hp)) | 1; /* Point the head of this infix list to the above. */ Hd_val (q) = (header_t) ((word) p | 2); /* Change block header's size to point to this infix list. */ *hp = Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); } } break; case 2: /* Inverted infix list: insert. */ *p = Hd_val (q); Hd_val (q) = (header_t) ((word) p | 2); break; } } } p val0 q val1 val1 val3 val2 val5 + Bosize_ehd(val8) *p = (word) &Field (val, Wosize_ehd (*hp)) | 1; offset = Bosize_hd(val3) New encoded header (offset, Infix_tag, 3) Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3); val val5 val6_0 val4 hp val7_i val6_1 val6_2 while (Ecolor (*hp) == 0) hp = (word *) *hp; … hp val9 Assert (Ecolor (*hp) == 3); val0 | 2 Hd_val (q) = ((word) p | 2); val8 the first infix header
  • 32. compact.c:do_compaction 3rd pass /* Third pass: reallocate virtually; revert pointers; decode headers. Rebuild infix headers. */ { init_compact_allocate (); ch = caml_heap_start; while (ch != NULL){ word *p = (word *) ch; chend = ch + Chunk_size (ch); while ((char *) p < chend){ word q = *p; if (Ecolor (q) == 0 || Tag_ehd (q) == Infix_tag){ /* There were (normal or infix) pointers to this block. */ size_t sz; tag_t t; char *newadr; word *infixes = NULL; while (Ecolor (q) == 0) q = * (word *) q; sz = Whsize_ehd (q); t = Tag_ehd (q); if (t == Infix_tag){ /* Get the original header of this block. */ infixes = p + sz; q = *infixes; Assert (Ecolor (q) == 2); while (Ecolor (q) != 3) q = * (word *) (q & ~(uintnat)3); sz = Whsize_ehd (q); t = Tag_ehd (q); } newadr = compact_allocate (Bsize_wsize (sz)); q = *p; while (Ecolor (q) == 0){ word next = * (word *) q; * (word *) q = (word) Val_hp (newadr); q = next; } *p = Make_header (Wosize_whsize (sz), t, Caml_white); if (infixes != NULL){ /* Rebuild the infix headers and revert the infix pointers. */ while (Ecolor ((word) infixes) != 3){ infixes = (word *) ((word) infixes & ~(uintnat) 3); q = *infixes; while (Ecolor (q) == 2){ word next; q = (word) q & ~(uintnat) 3; next = * (word *) q; * (word *) q = (word) Val_hp ((word *) newadr + (infixes - p)); q = next; } Assert (Ecolor (q) == 1 || Ecolor (q) == 3); *infixes = Make_header (infixes - p, Infix_tag, Caml_white); infixes = (word *) q; } } p += sz; }else{ Assert (Ecolor (q) == 3); /* This is guaranteed only if caml_compact_heap was called after a nonincremental major GC: Assert (Tag_ehd (q) == String_tag); */ *p = Make_header (Wosize_ehd (q), Tag_ehd (q), Caml_blue); p += Whsize_ehd (q); } } ch = Chunk_next (ch); } } } p val0 q0 val1 val1 val2_0 val2_1 while (Ecolor (q) == 0) q = * (word *) q; … val4 q1 val4 val5 sz = Whsize_ehd(val4) infixes val6 q2 val5 val7_0 q = * (word *) (q & ~(uintnat)3); val7_1 while (Ecolor (q) != 3) q = * (word *) (q & ~(uintnat)3); … q3 val8 val8 newadr val10 Infix_tag compact_allocate (Bosize_ehd(val8)); Make_header of val8 (Wosize_whsize (sz), t, Caml_white); val10+4 * (word *) q = (word) Val_hp (newadr); val5 val7_0 val7_1 … val8 while (Ecolor (q) == 2) * q = newadr + (infixes - p) + 4; Ecolor (q) != 2 && while (Ecolor ((word) infixes) != 3){ Make_header (infixes - p, Infix_tag, Caml_white);
  • 33. compact.c:do_compaction 4th pass /* Fourth pass: reallocate and move objects. Use the exact same allocation algorithm as pass 3. */ { init_compact_allocate (); ch = caml_heap_start; while (ch != NULL){ word *p = (word *) ch; chend = ch + Chunk_size (ch); while ((char *) p < chend){ word q = *p; if (Color_hd (q) == Caml_white){ size_t sz = Bhsize_hd (q); char *newadr = compact_allocate (sz); memmove (newadr, p, sz); p += Wsize_bsize (sz); }else{ Assert (Color_hd (q) == Caml_blue); p += Whsize_hd (q); } } ch = Chunk_next (ch); } } • 3번 pass에서 init_compact_allocate도 하고 compact allocate도 실행하여 포인터들이 새 로 할당된 주소를 가리키도록 바꾼다. • 하지만 새로 할당된 주소로 데이터를 카피하 는 것은 4단계에서 하고, 4단계에서 또 다시 init_compact_allocate, compact_allocate를 실행한다. • 여기서 가정은 3,4 단계에서 할당되는 메모리 공간과 위치가 정확하게 일치한다는 점이다. 3단계에서 다른 코드를 지우고, chunk block 순회하면서 compact_allocate(sz) 호출하는 부분만 보면 동일하다. • 그리고, 카피하는 대상과 원본의 주소 영역이 같은 chunk 공간이라서 일부 데이터가 overwrite되어 소실될 수 있을 것 같으나, 그 런 문제도 없다. • 왜냐하면 compact을 하면, 원본주소 >= 대상주소 따라서, overwrite 된 메모리 영역은 이미 복 사한(순회하고 있는 메모리 주소) 보다 작은 주소값 영역이다. Chunk block 순회 자체가 주소값이 증가하는 순서로 실행됨.
  • 34. caml__roots_block next ntables = 1 nitems = 1 tables[5] caml_local_roots caml__roots_block next ntables = 1 nitems = 1 tables[5] Roots.c caml_local_roots val0 struct caml__roots_block { struct caml__roots_block *next; intnat ntables; intnat nitems; value *tables [5]; }; caml__roots_block next ntables = 1 nitems = 4 tables[5] CAMLprim value caml_natdynlink_open(value filename, value global) { CAMLparam1 (filename); CAMLlocal1 (res); ... res = caml_alloc_tuple(2); Field(res, 0) = (value) handle; Field(res, 1) = (value) (sym); CAMLreturn(res); } caml__roots_block // CAMLparam1 (filename); struct caml__roots_block *caml__frame = caml_local_roots; // - CAMLxparam1 (filename); struct caml__roots_block caml__roots_filename; caml__roots_filename.next = caml_local_roots; caml_local_roots = &caml__roots_filename; caml__roots_filename.nitems = 1; caml__roots_filename.ntables = 1; caml__roots_filename.tables [0] = &filename; // CAMLlocal1 (res); value res = 0; // - CAMLxparam1 (res); struct caml__roots_block caml__roots_res; caml__roots_res.next = caml_local_roots; caml_local_roots = &caml__roots_res; caml__roots_res.nitems = 1; caml__roots_res.ntables = 1; caml__roots_res.tables [0] = &res; // CAMLreturn(res); value caml__temp_result = (res); caml_local_roots = caml__frame; return (caml__temp_result); res filename next ntables = N nitems = 1 tables[5] CAMLxparamN CAMLxparam4 Macro preprocessing
  • 35. Roots.c caml_frame_descriptors natdynlink.c caml_natdynlink_run (sym) roots.c caml_init_frame_descriptors (caml_frametable[i]) roots.c caml_register_frametable frametables 추 가 struct link struct link struct link struct link roots.c caml_init_frame_descriptors /* Allocate the hash table 해쉬테이블 구성 */ caml_frame_descriptors = (frame_descr **) caml_stat_alloc(tblsize * sizeof(frame_descr *)); caml_frame_descriptors Hash table of frame_descr natdynlink.c getsym, flexdll_dlsym cmmgen.ml let frame_table namelist = let mksym name = Csymbol_address (Compilenv.make_symbol ~unitname:name (Some "frametable")) in Cdata(Cglobal_symbol "caml_frametable" :: Cdefine_symbol "caml_frametable" :: List.map mksym namelist @ [cint_zero]) typedef struct { uintnat retaddr; unsigned short frame_size; unsigned short num_live; unsigned short live_ofs[1]; } frame_descr; typedef struct { uintnat len; frame_descr * ptr_fd; char* ; short ; short ; short [ptr_fd->num_live] ; frame_descr *; }; sizeof(frame_descr*) round down align
  • 36. caml allocation alloc.c caml_alloc memory.h Alloc_small minor heap에 할당 시도 i386.S:caml_alloc1,caml_alloc2,caml_alloc3,caml_allocN io.c:caml_alloc_channel custom.c:caml_alloc_custom alloc.c:caml_alloc,caml_alloc_small,caml_alloc_tuple,caml_alloc_string, caml_alloc_final,caml_alloc_array,caml_alloc_dummy, caml_alloc_dummy_float Memory.c:caml_alloc_for_heap,caml_alloc_dependent_memory minor_gc.c:caml_alloc_table memory.c caml_alloc_shr freelist 찾아보고 없으면 expand heap하고 다시 꺼내온다 mlvalues.h Atom Wosize 0 짜리 block들을 보관 하는 caml_atom_table 참조 freelist.c caml_fl_allocate freelist에서 맞는 block을 찾 아본다 minor_gc.h caml_minor_collection minor heap을 비우고, major heap으로 넘긴다. memory.c expand_heap chunk를 할당하고, align 작 업,chunk linked list에 추가 freelist.c caml_fl_add_blocks freelist에 새로운 블록을 추가 alloc.c caml_alloc_small alloc.c caml_alloc_tuple, caml_alloc_array, caml_alloc_dummy, caml_alloc_dummy_float weak.c:caml_weak_get_copy natdynlink.c:caml_natdynlink_run_toplevel backtrace.c:caml_get_exception_backtrace debugger.c:caml_debugger_init signal.c:caml_install_signal_handler obj.c:caml_obj_block,caml_obj_dup,read_debug_info array.c caml_make_vect caml_array_gather
  • 37. Minor GC roots and olidfy i386.S:caml_alloc1,caml_alloc2,caml_alloc3,caml_allocN io.c:caml_alloc_channel custom.c:caml_alloc_custom alloc.c:caml_alloc,caml_alloc_small,caml_alloc_tuple,caml_alloc _string, caml_alloc_final,caml_alloc_array,caml_alloc_dummy, caml_alloc_dummy_float Memory.c:caml_alloc_for_heap,caml_alloc_dependent_memory minor_gc.c:caml_alloc_table minor_gc.c caml_empty_minor_heap minor_gc.c caml_oldify_one roots.c caml_oldify_local_roots caml_globals array of value asmcomp/cmmgen.ml|2134| Cdefine_symbol "caml_globals" … 어떻게 생성되는지 아직 확인된바 없음 caml_dyn_globals linked list, node->data = value asmrun/roots.c|136| static link * caml_dyn_globals = NULL; OCAML-side stack local var saved reg values, stack stored values follow stack frames starting with caml_bottom_of_stack caml_local_roots linked list, node->tables[i][j] = value byterun/roots.c|28| CAMLexport struct caml__roots_block *caml_local_roots = NULL; caml_global_roots(_young) linked list, node->root = value byterun/globroots.c|171| struct global_root_list caml_global_roots = { NULL, { NULL, }, 0 }; final_table linked list, node->fun, node->val = value asmrun/finalise.c|222| final_table = caml_stat_alloc (new_size * sizeof (struct final));
  • 38. call graph to malloc misc.c caml_aligned_malloc 실제 malloc 호출하여 Page_size 이 상의 heap chunk를 만든다. misc.c caml_alloc_for_heap 실제 malloc 호출하여 Page_size 이상의 heap chunk를 만든다. minor_gc.c caml_set_minor_heap_size memory.c expand_heap fl_allocate 실패하면 old heap으 로 chunk를 할당하여 확장 major_gc.c caml_init_major_heap 처음부터 old heap으로 하나의 chunk를 잡아두고 시작한다. Heap chunk linked list의 head intern.c intern_alloc unmarshalling중에 내부적으로 사용하는 공 간을 할당 page table 관리는 없다. compact.c caml_compact_heap compact하고나서 old heap에 적정 비율의 빈 공 간이 있도록 해야한다. 부족할 경우 할당한다. memory.c caml_add_to_heap 페이지 테이블에 추가, caml_heap_start heap chunk chaing에 추가 memory.c caml_page_table_add Heap chunk linked list에 추가 Chunk_next (chunk) = caml_heap_start; caml_heap_start = chunk;
  • 39. gc trigger amd64.S caml_call_gc signals_asm.c caml_garbage_collection minor_gc.c caml_minor_collection major_gc.c caml_major_collection_slice major_gc.c mark_slice major_gc.c sweep_slice compact.c caml_compact_heap_maybe compact.c caml_compact_heap_maybe Mark phase Sweep phase Idle phase after sweep amd64.S caml_alloc1 .. N asmcomp/emit.ml let emit_instr fallthrough i = ... | Lop(Ialloc n) -> if !fastcode_flag then begin let lbl_redo = new_label() in `{emit_label lbl_redo}: sub r15, {emit_int n}n`; ` cmp r15, {emit_symbol "caml_young_limit"}n`; let lbl_call_gc = new_label() in let lbl_frame = record_frame_label i.live Debuginfo.none ` jb {emit_label lbl_call_gc}n`; ` lea {emit_reg i.res.(0)}, [r15+8]n`; call_gc_sites := { gc_lbl = lbl_call_gc; gc_return_lbl = lbl_redo; gc_frame = lbl_frame } :: !call_gc_sites end else begin begin match n with 16 -> ` call {emit_symbol "caml_alloc1"}n` asmcomp/emit.ml in List.iter emit_call_gc !call_gc_sites; asmcomp/amd64/emit_nt.mlp let emit_call_gc gc = `{emit_label gc.gc_lbl}: call {emit_symbol "caml_call_gc"}n`; `{emit_label gc.gc_frame}: jmp {emit_label gc.gc_return_lbl}n` emit된 asm코드에서 call byterun/memory.h Alloc_small caml_minor_collection ();
  • 40. Infix tag f1 0x4034972c Closure_tag head 8 bytes let rec f1 n = if n=0 then 0 else f2 (n-1) and f2 n = if n=0 then 0 else f1 (n-1);; inspect f1;; inspect f2;; f2 0x40349734 Code ptr Infix_tag(size=2) f3 0x40349714 f4 0x4034971c Closure_tag head Code ptr1 Infix_tag(size=2) Code ptr2 f5 let rec f3 n m = if n+m<=0 then 0 else f4 (n-1) m and f4 n m = if n+m<=0 then 1 else f5 m (n-1) and f5 n m = if n+m<=0 then 2 else f3 m (n-1) ;; inspect f3;; inspect f4;; inspect f5;; 0x40349724 Infix_tag(size=4) Code ptr3
  • 41. caml_globals &camlPervasives &camlSimple &camlStd_lib 0 caml_globals label in data section camlPervasives label in data section block header(size) camlPervasives__84 fail_with label Tag=0, size=1 0x0061f3b8 String_tag, size=2 “Pervasives.Exitx00” camlPervasives__85 invalid_args label exc Exit heap block addr camlPervasives__83 min label camlPervasives__82 max label camlPervasives__82 max label … infinity heap block addr Double_tag, size=1 0x00000000 0x7ff00000 (gdb) x/8x 0x00007ffff7519fe0 0x7ffff7519fe0: 0x000007fd 0x00000000 0x00000000 0x7ff00000 0x7ffff7519ff0: 0x00000400 0x00000000 0x0061f3b8 0x00000000
  • 42. frametable • frametable은 ML-side에서 생성한 모든 함수들의 frame 정보를 담는 frame descriptor를 가지 고 있다. • frame descriptor는 ML-side에서 생성되어 assembly code의 data section에 symbol로 정의된 다. 하지만 데이터에 대한 해석은 C-side의 stack.h : struct frame_descr 으로 접근한다. // stack.h typedef struct { uintnat retaddr; unsigned short frame_size; unsigned short num_live; unsigned short live_ofs[1]; } frame_descr; • retaddr : 해당하는 함수를 호출한 instruction의 다음 instruction pointer 즉 return addr • frame_size : 해당하는 stack activation frame의 크기 • num_live : 해당 activation frame에 저장된 value 중에서 root로 사용될 value의 개수 • live_ofs[1] : variable sized array로 간주한다. 각 항목은 root로 사용될 value에 해당하며 두 가 지 종류(reg, stack)을 함께 저장한다. • live_ofs[i] % 2 == 1 root : regs[ live_ofs[i]/2 ] • live_ofs[i] % 2 == 0 root : sp + live_ofs[i] stack pointer + offset (스택에 저장된 value)
  • 43. frametable • 스택에 저장된 activation record는 두 가지 종류이다. C-side/ML-side • asmrun/roots.c : caml_oldify_local_roots 함수에서 stack을 backtrace하면서 각 함수 호출에 대응하는 activation record (frame)들을 모두 순회한다. • C-side의 지역변수들은 CAMLlocal 매크로 등을 이용하여 모두 등록해 두고 관리하기 때문에 stack backtrace에서 고려해 주지 않아도 된다. C-side frame은 스킵한다. • ML-side frame에 대해서는 frame table의 정보를 참고하여 root 로 사용할 value들을 얻어낸 다. • C-side frame을 스킵하는 방법 – 스택에는 C-side/ML-side 스택이 번갈아 나올수가 있다. 그러나 C-side나 ML-side을 넘나들 때는 꼭 interface에 해당하 는 함수를 거쳐야만 한다. – C-side -> ML-side (amd64.S : RECORD_STACK_FRAME 참고) amd64.S : caml_start_program amd64.S : caml_callback_exn – ML-side -> C-side amd64.S : caml_c_call amd64.S : caml_call_gc amd64.S : caml_raise_exn – bottom_of_stack, last_retaddr C-side 진입할때 직전 ML-side frame의 stack pointer, return address를 전역변수에 저장한다. – ML-side로 진입할 때는 스택에 bottom_of_stack, last_return_address 를 push하여 기억해 둔다. – backtrace를 하다가 C-side frame을 만나면(정확히는 L107 caml_start_program) 스택에 저장된 bottom_of_stack을 얻어서 C-side frame chunk(연속된 C-side frame들)을 한번에 뛰어 넘을 수 있다.
  • 44. frametable • C-side / ML-side frame 구분 방법 – stack backtrace해서 만나게 되는 C-side frame은 L107 caml_start_program이 유일하다. – L107에 대응하는 frame descriptor는 amd64.S 에 정의되어 있다. – caml_oldify_local_roots 에서는 frame_size가 -1(0xFFF) 인지 확인하여 L107 frame 을 확인한다. G(caml_system__frametable): .quad 1 /* one descriptor */ .quad LBL(107) /* return address into callback */ .value -1 /* negative frame size => use callback link */ .value 0 /* no roots here */ .align EIGHT_ALIGN • Frame descriptor를 찾는 방법 – 현재 frame에 저장된 return address를 hash key로 삼아서 찾는다. – frame descriptor 마다 대응하는 return address를 적어두었기 때문에 매칭하는 descriptor를 찾을 수 있다. – return address로 frame descriptor를 찾는다는 것은 call instruction을 포함하는 함수의 frame 정보를 찾는 것이다. – 아래 그림과 다음 슬라이드의 자세한 그림 참고 camlNqueen1__loop_1032: .... movq %rsi, %rbx call ... .L161: // return address movq 24(%rsp), %rbx movq %rbx, %rdi addq $2, %rbx movq %rbx, 24(%rsp) movq 32(%rsp), %rax cmpq %rax, %rdi jne .L133 .globl camlNqueen__frametable camlNqueen__frametable: .quad 17 .... .quad .L161 // return address .word 48 // frame size .word 3 // # of local var .word 16 // var0 : *(sp+16) .word 24 // var1 : *(sp+24) .word 32 // var2 : *(sp+32) .align 8 ===> frame descriptor for camlNqueen1__loop_1032
  • 45. frametables, caml_frametable caml_frametable symbol .data .globl caml_frametable caml_frametable: .quad caml_startup__frametable .quad caml_system__frametable .quad camlPervasives__frametable .quad camlNqueen__frametable .quad camlStd_exit__frametable .quad 0 .globl camlNqueen__frametable camlNqueen__frametable: .quad 17 .quad .L175 .word 16 .word 0 .align 8 ... .quad .L165 // ret addr .word 16 // frame size .word 1 // # of local var .word 9 // var0 : *reg4 .align 8 ... .quad .L161 // ret addr .word 48 // frame size .word 3 // # of local var .word 16 // var0 : *(sp+16) .word 24 // var1 : *(sp+24) .word 32 // var2 : *(sp+32) .align 8 camlNqueen_frametable symbol assembly instructions of the function camlNqueen1__loop_1032 camlNqueen1__loop_1032: .cfi_startproc subq $40, %rsp .cfi_adjust_cfa_offset 40 .L124: movq %rax, %rdx movq $3, %rax movq 24(%rdi), %rsi cmpq %rsi, %rax jg .L120 movq %rsi, 0(%rsp) movq %rax, 8(%rsp) movq %rdi, 24(%rsp) movq %rbx, 16(%rsp) movq %rdx, 32(%rsp) .L121: movq %rax, %rdi addq $-2, %rdi movq %rax, %rsi .... movq 32(%rax), %rdi movq $5, %rax movq %rsi, %rbx call camlNqueen1__loop_1032@PLT .L161: movq 24(%rsp), %rbx movq %rbx, %rdi addq $2, %rbx movq %rbx, 24(%rsp) movq 32(%rsp), %rax cmpq %rax, %rdi jne .L133
  • 46. finalise • Gc.finalise : (‘a -> unit) -> ‘a -> unit • 어떤 블록이 해제되기 전에 꼭 호출 되어야 하는 함수를 등록해 둔다. 블록이 unreachable해지는 순간과 실 제 GC에서 해제하는 순간 사이에 final함수가 호출된다. • final_table [0..old) : old set (마지막 GC에서 reachable으로 확인됨) [old..young) : recent set (아직 GC에서 확인되지 않음) [young..size) : free space • todo list 마지막 major GC에서 unreachable로 확인됨, final_table에서 빠지고 todo list로 추가됨 final 함수가 호출될 예정임 • finalise 등록을 하면 final_table에 추가된다. final_table은 GC의 root set으로 사용되기 때문에 GC 진행 결과 바로 해제되지 않는다. 특히 minor GC에서는 finalise 등록된 블록이 해제되지 않는다. caml_final_do_young_roots(Oldify 호출로 final 함수와 대상블록을 oldify 시도한다. • major GC에서 sweeping이 끝나고 caml_final_update를 호출하여 해제되어야 할 블록(white)은 todo list에 넣어 버리고, 남아야 하는 블록은 old set에 넣는다. todo list의 블록들도 당장은 해제되면 안되기 때문에 caml_darken 실행한다. 즉, 실제 final 함수 호출은 major GC 종료 후로 미루고, 블록 해제는 다음 GC로 미 룬다. • major GC 종료 단계에서 caml_final_do_calls를 호출하여 todo list에 있는 final함수를 모두 호출한다. ML-side 호출이므로 caml_callback_exn을 이용한다. 이벤트 (시간순서) 블록 생성 v final함수 등록 minor GC major GC sweep phase after major GC major GC 소속 young/old heap young/old heap final_table(recent set) young/old -> old heap final_table(old set) old heap final_table(old) -> todo list todo list -> X 해제됨 담당 함수 caml_final_register caml_final_do_young_roots (caml_oldify_one, v) caml_final_do_young_roots (caml_oldify_one, f) caml_final_update caml_final_do_calls (final함수 호출)
  • 47. startup(amd64 기준) • 실행순서 asmrun/startup.c : caml_main(C 코드) -> amd64.S : caml_start_program(asm 코드) -> caml_program(컴파일러 생성) -> caml[모듈이름]__entry(모듈별로 컴파일러가 생성) • caml_main – parse_camlrunparam CAMLRUNPARAM 환경변수를 파싱하여 전역변수 설정(POcaml에서 몇가지 설정에 사용) – caml_init_gc young heap, old heap 할당, 관련 구조/상수 초기화 – init_atoms 필드 길이 0인 블록은 한 copy만 만들어 두고 공유한다. 길이 0이며 다양한 tag번호를 갖는 블록을 만들고 보관 – caml_init_signals signal handler 시스템에 등록 – caml_termination_jmpbuf 프로그램 종료전 호출할 훅(caml_termination_hook) 등록가능 ML-side에도 유사한 at_exit 함수가 있음(pervasive 모듈 참고) – caml_start_program (amd64.S asm 코드 호출) – caml_fatal_uncaught_exception unhandled exceptio으로 종료되는 경우 처리 • caml_start_program – 각종 인터페이스 작업(reg save, 전역변수 save, 전역변수를 registe로 로드) – caml_program 호출 ( ‘call *%r12’ ) • caml_program – module 마다 entry 포인트 호출 (camlPervasives__entry), module dependency 순서에 따라 하나씩 호출 – 마지막에는 camlStd_exit__entry 호출 – 각 모듈 entry를 호출할 때마다 전역변수 caml_globals_inited를 카운트하여 현재까지 실행된 모듈의 수를 표시한다. (각 모듈이 실행되어야, 모듈의 전역변수가 설정되고 caml_globals를 통해 접근이 가능해 짐, roots.c 참고)
  • 48. ML/C-side interface(amd64 기준) • C-side assembly code C 코드는 gcc로 컴파일된다. C-side 코드의 어셈블리규약(convention)은 gcc 컴파일러를 따른다. • ML-side assembly code ML-side는 ocaml/asmcomp에 있는 컴파일 프로그램을 거쳐서 assembly instruction으로 생성된다. 그러므로 ocaml 컴파일러가 정한 convention을 따른다. • C/ML-side interface 양쪽의 규약이 다르기 때문에, 넘어갈 때는 interface 함수를 거쳐야 한다. – C -> ML • caml_start_program(처음 프로그램 시작하여 caml_program 진입, caml_callback_exn으로 다시 ML-side 진입) • caml_callback_exn(C-side 실행 중에 ML-side 함수 호출) 등록된 finalise, signal 함수 실행, at_exit 함수 실행, ML-side에서 인자로 받은 clousure를 C-side에서 호출 • fail.c:caml_fail_with -> fail.c:caml_raise -> amd64.S:caml_raise_exception 보통 ML-side에서 trap(exception handler) 설치 및 raise 할 때 caml_raise_exceptio을 사용한다. 그러나 C-side에서도 caml_fail_with함수를 통해 raise 할 수 있다. – ML -> C • caml_call_gc ML-side의 메모리 할당(alloc)에서 실패할 경우 호출됨, signals_asm.c : caml_garbage_collection 호출 • caml_c_call ML코드에 external으로 선언된 함수를 호출 때 사용됨, 또는 asmcomp에서 직접 삽입됨 • caml_ml_array_bound_error array index범위 오류 시 호출됨, C-side로 넘어가서 caml_raise를 호출 • external 선언이며 noalloc 옵션 interface 를 거치지 않고 바로 C 함수가 호출됨 • interface 작업 – register 저장(PUSH/POP_CALLEE_SAVE_REGS, proc.ml : destroyed_at_c_call) – 전역변수 설정(caml_young_ptr : r15, caml_exception_pointer : r14, caml_bottom_of_stack, caml_last_return_address, caml_gc_regs) – argument passing(C_ARG_*, proc.ml: calling_conventions)
  • 49. ML/C-side interface(amd64 기준) • proc.ml : int_reg_name register들의 이름과 번호를 맵핑해준다. C-side에서도 그 순서를 준수해야 한다. "rax"; "rbx"; "rdi“ .... • caml_gc_regs ML-side에서 사용하던 register가 GC root values를 가지고 있을 수도 있으므로, caml_call_gc에서 모든 register를 저장한다. 아래 코드처럼 모든 register를 스택에 넣고 스택포인터주소($rsp)를 array처럼 사용가능 ... 262 pushq %rdi 263 pushq %rbx 264 pushq %rax 265 STORE_VAR(%rsp, caml_gc_regs) • caml_young_ptr(r15) young heap의 할당 위치를 표시하는 포인터, caml_young_ptr을 감소 시키는 것만으로 young heap에 alloc이 가능하다. ML-side에서 young heap alloc이 빈번하므로, caml_young_ptr를 r15 레지스터에 저장해서 사용한다. C-side에 진입할 때는 caml_young_ptr<-r15 으로 저장하고, 전역변수 caml_young_ptr가 r15 레지스터의 할당 포인터 역할을 대신하고, ML-side로 복귀할때는 반대로 r15<-caml_young_ptr • caml_exception_pointer(r14) ML-side에서 trap(exception handler) frame이 저장된 주소(stack pointer)를 저장, raise가 ML-side 또는 C-side에서 발생하면 stack pointer를 trap frame으로 설정하여 등록된 exception handler를 실행한다. asmcomp/emit.ml 662 | Lpushtrap -> 663 cfi_adjust_cfa_offset 8; 664 (emit_string " pushq %r14n"); 665 cfi_adjust_cfa_offset 8; 666 (emit_string " movq %rsp, %r14n"); 667 stack_offset := !stack_offset + 16 ... 674 | Lraise -> 675 if !Clflags.debug then begin 676 (emit_char ' '; emit_call "caml_raise_exn"; emit_char 'n'); 677 record_frame Reg.Set.empty i.dbg 678 end else begin 679 (emit_string " movq %r14, %rspn"); 680 (emit_string " popq %r14n"); 681 (emit_string " retn") 682 end asmrun/amd64.S 723 /* Raise an exception from C */ 724 725 FUNCTION(G(caml_raise_exception)) 726 TESTL_VAR($1, caml_backtrace_active) 727 jne LBL(111) 728 movq C_ARG_1, %rax 729 LOAD_VAR(caml_exception_pointer, %rsp) /* Cut stack */ 730 popq %r14 /* Recover previous exception handler */ 731 LOAD_VAR(caml_young_ptr, %r15) /* Reload alloc ptr */ 732 ret 733 LBL(111): ...
  • 50. ML/C-side interface(amd64 기준) • caml_last_return_address, caml_bottom_of_stack stack backtrace(stack에 저장된 call frame을 따라가기)에서 사용된다. roots.c에서 stack backtrace를 실행하는데, 여기서는 C-side frame은 건너뛰 고 trace한다. (C-side에서는 CAML_local/param함수로 root value들을 명시 적으로 관리하기때문) 두 변수는 C-side에서 봤을때 마지막 ML-side frame의 return addr, stack pointer를 의미한다. • arguments in registers C/ML-side의 함수 호출 규약이 다르다. C-side는 함수 매개변수를 rdi, rsi, rdx ... 순서로 사용하는데 반해 ML-side는 rax, rbx, ... 순서(proc.ml의 주석을 참조) interface에서는 매개변수를 재배치하여 규약에 맞도록 한다.(C_ARG_1, C_ARG_2 .. 참조) roots.c:caml_oldify_local_roots C-side call frames ... callee C-fun ML->C interface(Ex. caml_c_call) ML-side call frames ... callee ML-fun C->ML interface(Ex.caml_start_program) caller C-fun(Ex. caml_callback) C-side call frames ... ... ML->C interface(Ex. caml_c_call) ... caml_last_return_address caml_bottom_of_stack roots.c - skip C call frames next_ctx = Callback_link(sp); sp = next_ctx->bottom_of_stack; retaddr = next_ctx->last_retaddr; #0 0x0000000000440be4 in caml_raise_exception_r () #1 0x000000000044148a in caml_raise_r (ctx=0x67a070, v=140737340385952) at fail.c:94 #2 0x0000000000441756 in caml_raise_with_arg_r (ctx=0x67a070, tag=6665128, arg=140737340385976) at fail.c:139 #3 0x00000000004418e3 in caml_raise_with_string_r (ctx=0x67a070, tag=6665128, msg=<optimized out>) at fail.c:181 #4 0x0000000000441905 in caml_failwith_r (ctx=<optimized out>, msg=<optimized out>) at fail.c:192 #5 0x00000000004344d2 in extern_failwith (msg=0x4479f0 "Marshal.to_buffer: buffer overflow") at extern.c:277 #6 grow_extern_output (required=<optimized out>) at extern.c:227 #7 0x000000000043485a in writeblock ( data=0x65c620 ..., len=496) at extern.c:296 #8 0x0000000000434b56 in extern_rec (v=6669856) at extern.c:432 #9 0x000000000043502c in extern_value (v=6669856, flags=<optimized out>) at extern.c:557 #10 0x0000000000435456 in caml_output_value_to_buffer_r (ctx=<optimized out>, ...>) at extern.c:697 #11 0x000000000041c57c in camlMarshal__to_buffer_1014 () at marshal.ml:32 #12 0x00000000004112e8 in camlIntext__test_buffer_1102 () at intext.ml:235 #13 0x000000000041398e in camlIntext__main_1242 () at intext.ml:533 #14 0x000000000042461d in camlPrintexc__catch_1060 () at printexc.ml:77
  • 51. ML/C-side interface(amd64 기준) • ML-side에서 try with 구문으로 trap(exception pointer) 가 설치되었고 • C-side에서 caml_failwith_r 함수로 exception raise가 발생하였다. • with exception handler 구문(ML-side)을 실행하기 위해 서 다시 ML-side 진입하는데 amd64.S : caml_raise_exception_r 을 거쳤다. • gdb backtrace에서는 caml_c_call_r과 caml_raise_exception_r 같은 인터페이스의 frame 은 잠시 나타나고, callee함수로 넘어가면 보이지 않 는다. 여기 예제에서는 caml_raise_exception_r 실 행 중에 잠시 나타난 call frame을 표시한 것이다. testsuites/tests/lib-marshal 257 (try marshal_to_buffer s 0 512 verylongstring []; false 258 with Failure "Marshal.to_buffer: buffer overflow" -> 257 let _ = Gc.get_th_num() in true); #0 caml_context_num (ctx=0x67a070, v=1) at context.c:193 ----------------- caml_c_call_r is hidden --------------------- #1 0x00000000004112a1 in camlIntext__test_buffer_1102 () at intext.ml:258 --------------------------------------------------------------- #0 0x0000000000440be4 in caml_raise_exception_r () #1 0x000000000044148a in caml_raise_r at fail.c:94 #2 0x0000000000441756 in caml_raise_with_arg_r at fail.c:139 #3 0x00000000004418e3 in caml_raise_with_string_r at fail.c:181 #4 0x0000000000441905 in caml_failwith_r at fail.c:192 #5 0x00000000004344d2 in extern_failwith at extern.c:277 #6 grow_extern_output (required=<optimized out>) at extern.c:227 #7 0x000000000043485a in writeblock at extern.c:296 #8 0x0000000000434b56 in extern_rec at extern.c:432 #9 0x000000000043502c in extern_value at extern.c:557 #10 0x0000000000435456 in caml_output_value_to_buffer_r at extern.c:697 ----------------- caml_c_call_r is hidden --------------------- #11 0x000000000041c57c in camlMarshal__to_buffer_1014 () at marshal.ml:32 #12 0x00000000004112e8 in camlIntext__test_buffer_1102 () at intext.ml:235 #13 0x000000000041398e in camlIntext__main_1242 () at intext.ml:533 #14 0x000000000042461d in camlPrintexc__catch_1060 () at printexc.ml:77 #5 0x000000000040d48c in caml_program () #6 0x0000000000440af9 in caml_start_program_r () #7 0x0000000000440d29 in caml_start_program_r_wrapper () --------------------------------------------------------------- #8 0x00007ffff76c4e9a in start_thread () from /lib/x86_64-linux-gnu/ libpthread.so.0
  • 52. Ocaml systhread, master_lock, enter/leave_blocking_section test/comp1.ml 메모리 할당이 없이 연산만 반복한다. Thread.create로 동일한 연산 함수를 두 개 실행 257 #0 0x0000000000431338 in caml_leave_blocking_section () 258 #1 0x000000000042b175 in caml_thread_yield () 259 #2 0x00000000004117e7 in camlThread__preempt_1024 () at thread.ml:51 260 #3 0x000000000044143a in caml_start_program () ---------------- hidden caml_callback_exn --------------------- 257 #4 0x000000000043126e in caml_execute_signal () 258 #5 0x000000000043132e in caml_process_pending_signals () 259 #6 0x0000000000431355 in caml_enter_blocking_section () 260 #7 0x0000000000437688 in do_write () ---------------- hidden caml_c_call --------------------------- 257 #8 0x000000000043786b in caml_flush_partial () 258 #9 0x00000000004378c2 in caml_flush () 259 #10 0x000000000043813e in caml_ml_flush () 260 ... otherlibs/systhread/thread.ml thread 모듈을 포함시키면 caml_program -> camlThread__entry를 실행하여 다음과 같이 preempt_signal의 handle로 preempt 함수가 설정된다. 60 let _ = 61 Sys.set_signal preempt_signal (Sys.Signal_handle preempt); 62 thread_initialize(); otherlibs/systhreads/st_posix.h 주기적으로 preemption signal을 밯생시키는 함수(thread모드에서는 pthread로 생성되 어항상 돌아간다) unix signal과 구분되는 내부적인 signal으로 바로 실행되지 않고 caml_process_pending_signals에서 처리된다. 327 static void * caml_thread_tick(void * arg) 328 { 329 struct timeval timeout; 330 sigset_t mask; 331 ... 337 while(1) { 340 timeout.tv_sec = 0; 341 timeout.tv_usec = Thread_timeout * 1000; 342 select(0, NULL, NULL, NULL, &timeout); 346 caml_record_signal(SIGPREEMPTION); 347 } 348 return NULL; 349 } otherlibs/systhreads/st_stubs.c yield에서 caml_master_lock을 놓아주어 다른 thread로 context switching이 가능하 게 한다. 741 CAMLprim value caml_thread_yield(value unit) /* ML */ 742 { 743 if (st_masterlock_waiters(&caml_master_lock) == 0) return Val_unit; 744 enter_blocking_section(); 745 st_thread_yield(); 746 leave_blocking_section(); 747 return Val_unit; 748 } 순서 1. 프로그램 시작할때 signal handler thread.ml:preempt 등록 2. caml_thread_tick함수를 pthread로 생성하고 background에서 계 속 동작하도록한다(masterlock과 무관) 3. Thread.create으로 ocaml thread 생성 4. ocaml threa는 한 시점에 하나만(masterlock 소유) 동작한다. 5. GC나 IO 작업에서 enter_blocking_section(ocaml thread 동작을 block시킨다는 의미)을 호출한다. 여기서 preempt signa을 확인하 고 handler를 호출한다. 6. handler가 caml_thread_yield를 호출하기에 이르고 masterlock을 놓아주어 pthread context switching(ocaml thread도 동시에) 발생한다. 7. yield 바로 다음엔 otherlibs/systhreads/st_stubs.c: caml_thread_leave_blocking_section 함수에서 st_masterlock_acquire (&caml_master_lock); 으로 현재 실행중인 ocaml thread가 yield하기를 기다린다.
  • 53. Tag re-arrange • changing constant numbers assigned to tag types • No_scan_tag 251, Forward_tag 250, Infix_tag 249 ... => No_scan_tag 241, Forward_tag 240, Infix_tag 239 ... • cross-compilation compile ocamlopt using separated ocaml compiler • byterun/mlvalues.h #define No_scan_tag 251 #define Forward_tag 250 #define Infix_tag 249 #define Object_tag 248 #define Closure_tag 247 #define Lazy_tag 246 #define Abstract_tag 251 #define String_tag 252 #define Double_tag 253 #define Double_array_tag 254 #define Custom_tag 255 • bytecomp/matching.ml let inline_lazy_force_cond arg loc = ... Lprim(Pintcomp Ceq, [Lvar tag; Lconst(Const_base(Const_int Obj.forward_tag))]), ... ... Lprim(Pintcomp Ceq, [Lvar tag; Lconst(Const_base(Const_int Obj.lazy_tag))]), ... let inline_lazy_force_switch arg loc = ... (varg, { sw_numconsts = 0; sw_consts = []; sw_numblocks = (max Obj.lazy_tag Obj.forward_tag) + 1; sw_blocks = [ (Obj.forward_tag, Lprim(Pfield 0, [varg])); (Obj.lazy_tag, Lapply(force_fun, [varg], loc)) ]; sw_failaction = Some varg } ))))
  • 54. Tag re-arrange • bytecomp/translcore.ml (* other cases compile to a lazy block holding a function *) | _ -> let fn = Lfunction (Curried, [Ident.create "param"], transl_exp e) in Lprim(Pmakeblock(Config.lazy_tag, Immutable), [fn]) • utils/config.ml let max_tag = 245 let lazy_tag = 246 • topdir/Makefile(cross compilation) cross-phc: @echo "phc cross-compiles reentrant multi runtime ocamlopt" cd stdlib; make all CAMLC=ocamlc make runtimeopt make compilerlibs/ocamlcommon.cma cp ~/.opam/4.00.1/bin/ocamlrun ./byterun/ make compilerlibs/ocamloptcomp.cma make driver/optmain.cmo ocamlc -o ocamlopt compilerlibs/ocamlcommon.cma compilerlibs/ocamloptcomp.cma driver/optmain.cmo cd stdlib; make allopt RUNTIME=ocamlrun • testing(topdir/testsuite/tests/misc/hamming.ml) > ../../../ocamlopt -nostdlib -I ../../../stdlib/ hamming.ml > ./a.out Segmentation fault (core dumped)
  • 55. Tag re-arrange • testing(topdir/testsuite/tests/misc/hamming.ml) 0x40902c <camlHamming__mul_1018+108> movq $0x8ff,-0x8(%rax) x 0x409034 <camlHamming__mul_1018+116> mov 0x22983d(%rip),%rdx # 0x632878 x 0x40903b <camlHamming__mul_1018+123> mov %rdx,(%rax) x 0x40903e <camlHamming__mul_1018+126> mov %rcx,0x8(%rax) x 0x409042 <camlHamming__mul_1018+130> mov 0x8(%rbx),%rdxx x 0x409046 <camlHamming__mul_1018+134> lea 0x18(%rax),%rcx x 0x40904a <camlHamming__mul_1018+138> movq $0x8ff,-0x8(%rcx)bx x 0x409052 <camlHamming__mul_1018+146> mov 0x22981f(%rip),%rbx # 0x632878 x 0x409059 <camlHamming__mul_1018+153> mov %rbx,(%rcx) x > 0x40905c <camlHamming__mul_1018+156> mov 0x8(%rdx),%rdx x 0x409060 <camlHamming__mul_1018+160> mov 0x8(%rdi),%rbx x 0x409064 <camlHamming__mul_1018+164> imul %rdx,%rbx x 0x409068 <camlHamming__mul_1018+168> mov %rbx,0x8(%rcx) x 0x40906c <camlHamming__mul_1018+172> lea 0x30(%rax),%rbx x 0x409070 <camlHamming__mul_1018+176> movq $0x8ff,-0x8(%rbx)dx x 0x409078 <camlHamming__mul_1018+184> mov 0x2297f9(%rip),%rdx # 0x632878 x 0x40907f <camlHamming__mul_1018+191> mov %rdx,(%rbx) x 0x409082 <camlHamming__mul_1018+194> mov 0x8(%rax),%rdx x 0x409086 <camlHamming__mul_1018+198> mov 0x8(%rcx),%rax x 0x40908a <camlHamming__mul_1018+202> add %rdx,%rax x 0x40908d <camlHamming__mul_1018+205> mov %rax,0x8(%rbx) rax x 0x409091 <camlHamming__mul_1018+209> mov 0x229d30(%rip),%rax # 0x632dc8 x mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj Starting program: /home/phc/ocaml_internal/tag_test_svn/testsuite/tests/misc/a.out Program received signal SIGSEGV, Segmentation fault. 0x000000000040905c in camlHamming__mul_1018 () (gdb) bt bt #0 0x000000000040905c in camlHamming__mul_1018 () #1 0x0000000000408c34 in camlHamming__fun_1153 () #2 0x000000000040fd09 in camlCamlinternalLazy__force_lazy_block_1010 () at camlinternalLazy.ml:27 #3 0x0000000000408ca9 in camlHamming__fun_1162 () #4 0x000000000040fd09 in camlCamlinternalLazy__force_lazy_block_1010 () at camlinternalLazy.ml:27 #5 0x0000000000409418 in camlHamming__iter_interval_1054 () #6 0x0000000000409a9c in camlHamming__entry () #7 0x0000000000407979 in caml_program () #8 0x0000000000423f72 in caml_start_program () #9 0x0000000000424435 in caml_main () #10 0x00000000004151a8 in main () (gdb)
  • 56. closure, caml_curry, caml_curry_app, caml_tuplify • closure block Hd(closure_tag) caml_curry9_8 arity 3 = int(1) arg9 closure value Hd(closure_tag) fun ptr (symbol in asm) arity - # of remaining args arg1, arg2 ... fun(arg1, arg2, ... this closure) Hd(closure_tag) caml_curry9_2 arity 15 = int(7) caml_curry9_2_app arg2 closure value Hd(closure_tag) caml_curry9_1 arity 17 = int(8) caml_curry9_1_app arg1 closure value Hd(closure_tag) caml_curry9 arity 19 = int(9) fun ptr (symbol in asm) Hd(closure_tag) caml_curry9_7 arity 5 = int(2) caml_curry9_7_app arg8 closure value
  • 57. closure, caml_curry, caml_curry_app, caml_tuplify • closure block Hd(closure_tag) caml_curry9_3 arity 13 = int(6) = 9-3 caml_curry9_3_app arg3 closure value Hd(closure_tag) caml_tuplify2 arity 2 Hd(closure_tag) camlCtest__fun_1020 (arg1 - w, this closure block) = arg1 * 7 arity 1 7(arg2 - z) 13 let mult_sum (x,y) = 14 let z = x+y in 15 fun w -> w*z;; -------------- camlCtest__fun_1020 -int(2) = -5 camlCtest__add_1014(arg1,arg2) = arg1+arg2 let add (a,b) = a+b;; Hd(closure_tag) fun ptr (symbol in asm) arity - # of remaining args arg1, arg2 ... fun(arg1, arg2, ... this closure) Hd(closure_tag) caml_curry9_2 arity 15 = int(7) caml_curry9_2_app arg2 closure value
  • 58. closure, caml_curry, caml_curry_app, caml_tuplify • caml_curryN_M(arg, clos) (caml_curryN, caml_curryN_1, ... camlN_(N-1)) clos[caml_curryN_(M+1) ; N-M-1; caml_curryN_(M+1)_app; arg; close] cmm of caml_curry6_3 (function caml_curry6_3 (arg/1291: addr clos/1292: addr) (alloc 5367 "caml_curry6_4" 5 "caml_curry6_4_app" arg/1291 clos/1292)) • caml_apply2(arg1, arg2, clos[caml_curry3; 3; fun_ptr]) clos의 arity 3과 현재함수의 arity2를 비교 – 다르면 : call caml_curry3(arg1, clos[caml_curry3; 3; fun_ptr]) return clos[caml_curry3_1; 2; caml_curry3_1_app; arg1; clos[caml_curry3; 3; fun_ptr] ] call caml_curry3_1(arg2, clos[caml_curry3_1; ...]) return clos[caml_curry3_2; 1; arg2; clos[caml_curry3_1; ...]) return clos[caml_curry3_2; 1; ...] – 같으면 : call fun_ptr(arg1, arg2, clos) • caml_curryN_(N-1)(arg, clos) let clos1 = clos[3]; let clos2 = clos1[4]; let clos3 = clos2[4] ... let clos(N-1) = clos(N-2)[4]; app clos(N-1)[2](clos(N-2)[3], clos(N-3)[3] ... , clos1[2], arg, clos(N-1)) • caml_curry3_2(arg3, clos) let clos1 = clos[3]; let clos2 = clos1[4] app clos2[2](clos1[3], clos1[2], arg3, clos2) clos [caml_curry3_2 ; 1; arg2; clos1] clos1[caml_curry3_1 ; 2; caml_curry3_1_app; arg1; clos2] clos2[caml_curry3 ; 3; fun_ptr] call fun_ptr(arg1, arg2, arg3)
  • 59. closure, caml_curry, caml_curry_app, caml_tuplify • caml_curryN_M_app(arg(M+1), .. argN, clos) let clos1 = clos[4]; let clos2 = clos1[4]; ...; let closM = clos(M-1)[4] app closM[2] (clos(M-1)[3], ..., clos1[3], clos[3], arg(M+1), arg(M+2), .. argN, clos) (function caml_curry5_3_app (arg4/1144: addr arg5/1145: addr clos/1143: addr) (let (clos/1146 (load (+a clos/1143 32)) clos/1147 (load (+a clos/1146 32)) clos/1148 (load (+a clos/1147 32))) (app (load (+a clos/1148 16)) (load (+a clos/1147 24)) (load (+a clos/1146 24)) (load (+a clos/1143 24)) arg4/1144 arg5/1145 clos/1148 addr))) call fun_ptr (arg1, arg2, arg3, arg4, arg5, closM) • 정리 – closure block 구조 [Hd; 실행할 함수; 추가로 입력받을 매개변수의 개수 arity; 현재 클로져가 저장하는 매개변수들 ...] – curry closure block 구조 [Hd; caml_curryN_m; (N-m) arity; caml_curryN_m_app; arg_m; clos[Hd; caml_curryN_(m-1);..] ] [Hd; caml_curryN_(N-1); 1 arity; arg(N-1); clos[Hd; ...] ] [Hd; caml_curryN; N arity; fun_ptr] – caml_curryN caml_curryN_1, .. caml_curryN_(N-2) N클로져에 argument를 하나 추가하기 위해서, 새로운 블록을 만들고, 기존에 클로져는 링크 – caml_curryN_m_app(arg(m+1), ... argN, closure[arg_m, clsore[arg_(m-1) ....]) m이후 매개변수를 적용하여 (N-m)클로져 실행, 즉 뒤쪽 매개변수를 이 함수에서 적용, 앞에 매개변수는 클로져에 저장되어 있다 고 가정, call f(arg1, arg2, ... arg(m+1), ... argN) – caml_applyN(arg1, arg2, .. argN, closure) 클로져가 N arity면 바로 실행 f(arg1, arg2, ..., argN, arg1_clos, ...)