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
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 순회 자체가
주소값이 증가하는 순서로 실행됨.
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
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하기를 기다린다.
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