ocaml internal (description of runtime system in korean)

59
Ocaml Internal Hyunchul Park, PL lab, POSTECH

Upload: hyungchul-park

Post on 26-May-2015

190 views

Category:

Software


1 download

DESCRIPTION

Description of OCaml Runtime system In Korean

TRANSCRIPT

Page 1: Ocaml internal (description of runtime system in Korean)

Ocaml Internal

Hyunchul Park, PL lab, POSTECH

Page 2: Ocaml internal (description of runtime system in Korean)

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)

Page 3: Ocaml internal (description of runtime system in Korean)

mlvalue

Immediate value 1

Word

Address to heap block 0 0

Word

Immediate value

BlockPointer to heap block

Heap 할당시 align 만큼 0 으로 셋팅

Page 4: Ocaml internal (description of runtime system in Korean)

Ocaml heap block structure

Header: Hd_val(v)

Field (v, 0) … Field (v, Wo-size_val(v)-1 )

Address to heap block 0 0

Value v

Block

Size : Wosize_hd(hd) Color: Color_hd(hd)

Tag: Tag_hd(hd)

8bitWord – 10bit 2bit

Field 는 또 다른 value

Page 5: Ocaml internal (description of runtime system in Korean)

Young heap

• caml_aligned_malloc 함수가 할당한 메모리 Minor_heap_def 크기로 할당됨

• Page_size 기준으로 align 된 주소• Memory.h:Alloc_small 매크로 함수를 참고하면 간단한

할당 과정을 알수있다 .

Grow Allocated

caml_young_start caml_young_ptr caml_young_end

Page 6: Ocaml internal (description of runtime system in Korean)

Old heap

Heap chunk

Heap block (alive)Empty blocks (blue color)

Heap chunk chain

Heap chunk dependency(Block field pointing to an-other block)

Page 7: Ocaml internal (description of runtime system in Korean)

caml_ref_table, caml_weak_ref_table

• 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 에서 필요로 하기때문

addresses of value Increase by one word

table.base table.ptr table.threshold(??limit)

Value v = p2

p1 (address in old heap)

Value in young heap

p2 (address in young heap)

Page 8: Ocaml internal (description of runtime system in Korean)

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 를 실행한다 . 블록의 모든 필드를 따라가면서 처리하지는 않고 old-

ify_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)

Page 9: Ocaml internal (description of runtime system in Korean)

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; } }

val0

p

val1

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

hd Field(v,0) …Field(v,sz-

1)

val2

result

hd …

val0

p val1

v

val2

*p

0 val2 Field(v,i) …

val2

result

hd Field(v,0) next_otl uninit

val1

oldify_todo_list

위의 그림은 result = caml_alloc_shr (sz, tag); 코드를 실행한 시점

밑에 그림은 첫번째 케이스로 실행한 최종 결과

0

oldify_todo_list 의 다음 노드 (young heap)

Page 10: Ocaml internal (description of runtime system in Korean)

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) 로 as-sign

• Tag >= No_scan_tag필드를 따라 가야할 필요가 없으므로caml_alloc_shr 으로 old heap 에 할당하고Field 에 index 마다 one-to-one assign여기서도 forward tag 으로 설정했다 .

• Infix_tagvalue 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; } }

Page 11: Ocaml internal (description of runtime system in Korean)

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; } } }} 

val1

v

0 val2

val2

new_v

hdField(v,0) =

val3next_otl uninit

val1

oldify_todo_list

val3

f

caml_oldify_one v

caml_oldify_one p

Page 12: Ocaml internal (description of runtime system in Korean)

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( 중간에 indirec-tio 으로 놓은 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; } }

Page 13: Ocaml internal (description of runtime system in Korean)

Heap chunk structure

• 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 을 구성하여 사용한다 .

pad-ding1

heap_chunk_head

Chunk body pad-ding2

heap_chunk_head request

typedef struct { 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 (hp%Page_size==0)

Page 14: Ocaml internal (description of runtime system in Korean)

Free list(old heap)

• Chunk 단위가 아니라 , Block 단위로 free list 를 관리한다 . Free list 는 사용중이지 않은 block 들을 엮은 linked list 이다 .

• Major GC 에서 해제할 block 으로 확인되면 caml_fl_merge_block 호출하여 freel-ist 에 추가

• 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 포인터의 역할 참고B

lock

Blo

ck

Blo

ck

Blo

ck

Blo

ck

Blo

ck

Sen

-tin

el

flp table

Page 15: Ocaml internal (description of runtime system in Korean)

freelist.c(First_fit_policy 기준 ) 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 를 하나씩 순회하며 찾는다 .

sentin

el

flp table

Fl_head

Page 16: Ocaml internal (description of runtime system in Korean)

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의 작업을 포함해서 조금더 일을 한다 .

Page 17: Ocaml internal (description of runtime system in Korean)

freelist.c(First_fit_policy 기준 )caml_fl_allocate

• char *caml_fl_allocate (mlsize_t wo_sz) 외부에서 호출하는 함수이다 . 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함수가 필요하다 .

sele

cted

sentin

el

flp table

Fl_head

flp table after update

Page 18: Ocaml internal (description of runtime system in Korean)

freelist.c(First_fit_policy 기준 )allocate_block

• 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);

sentin

el

flp table

Fl_head

Page 19: Ocaml internal (description of runtime system in Korean)

freelist.c(First_fit_policy 기준 )caml_fl_merge_block

• 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)

sentin

el

flp table

Fl_head

ad

de

d

Page 20: Ocaml internal (description of runtime system in Korean)

freelist.c(First_fit_policy 기준 )caml_fl_add_blocks

• 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 이하이다 .

sentin

el

flp table

Fl_head

rem

ain

-der

bp(M

ax_w

osize

)

(Max_w

osize

)

Page 21: Ocaml internal (description of runtime system in Korean)

Address Align

• 주소를 align 하여 address % mod = 0 만족하도록 한다

• Bitwise 연산이 더 빠르고 , bit 수도 아낄 수 있으므로 align 하여 사용하는 경우가 많다 .

• Block address % 4 = 0• Page address % Page_size = 0

Page 22: Ocaml internal (description of runtime system in Korean)

Garbage Collection• OCAML GC 의 특징들

Generational, incremental, write barriered, stop&copy(young heap), mark&sweep(old heap), freelist

• Generationalyoung heap 과 old heap 으로 나누어 관리한다 . 처음에 블록할당은 young heap에서 한다 . Young heap 이 차면 , young heap 에서 살아남은 블록을 old heap 으로 넘긴다 . Young heap(minor GC) 와 old heap(major GC) 는 다른 GC 알고리즘 사용

• IncrementalOld 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 라고 한다 .

Page 23: Ocaml internal (description of runtime system in Korean)

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 에 포함시켜 관리한다 .

• Freelistold heap 은 정해진 크기의 heap chunk 단위로 malloc 된다 . 블록은 heap chunk 내에서 논리적으로 쪼개서 쓰는 것이다 . 그렇기 때문에 GC 에서 지우기로 결정한 블록은 바로 free 하는 것이 아니라 freelist 에 넣어 관리한다 .

Page 24: Ocaml internal (description of runtime system in Korean)

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 시킨다 .

Page 25: Ocaml internal (description of runtime system in Korean)

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 에서 재귀호출을 위해서 라고 추측

val2Infix hd

offset = Bosize_hd(Infix hd)

Field 0Closure hd

FieldHeade

r

FieldHeade

rNew block

Page 26: Ocaml internal (description of runtime system in Korean)

Inverted pointer• 블록을 가리키는 포인터들의 목록을 만드는 방법으로 inverted pointer chain 을 사용한다 .• 하나의 포인터에 대해서 inversion 을 하는것을 invert_pointer_at 함수로 실행한다 .• Root 포인터들을 순회하면서 inversion 을 하고 , Chunck 에 있는 block 순회를 하면서 inver-

sion 을 한다 .• Pointer inversion 이 끝나면 , 해당 블록을 옮길 공간을 정해놓고 ( 실제로 복사는 하지 않는다 )

그쪽 주소로 가리키도록 , chain 을 따라가면서 바꾼다 .

FieldHeade

r

Header

Field

FieldHeade

r

New block

FieldHeade

rNew block

Page 27: Ocaml internal (description of runtime system in Korean)

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 으로 관리한다 .

Infix1 hd

Field 0Closure hd

Infix2 hd

Page 28: Ocaml internal (description of runtime system in Korean)

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)

Infix1 hd 1

Field 0Closure hd 3Infix2 hd

1

0 0 0 0 0 00 0 0

2Field 00 2

Closure hd 3 2 2 1 2 23 0 0

Page 29: Ocaml internal (description of runtime system in Korean)

compact.c:invert_pointer_at case 0, 3static 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; } }}

val0

p

val1

q

val1

val2val3

val3*p = Hd_val (q);

val0Hd_val (q) = (header_t) p;

val0

p

val1

q

val3

val2val0

val3*p = Hd_val (q);

val0Hd_val (q) = (header_t) p;

Page 30: Ocaml internal (description of runtime system in Korean)

compact.c:invert_pointer_at case 1, Tag_ehd (*hp) == Closure_tagstatic 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; } }}

val0

p

val1

q

val1

val2val3

New encoded header (offset, Infix_tag, 3)Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3);

val8*p = *hp;

offset = Bosize_hd(val3)

val5

val

val4val6_

0

val7_i

hp

val6_1

val6_2

val9

hp

while (Ecolor (*hp) == 0) hp = (word *) *hp; Assert (Ecolor (*hp) == 3);

val0 | 2Hd_val (q) = ((word) p | 2);

val8

the first infix headerClosure_tag

Page 31: Ocaml internal (description of runtime system in Korean)

compact.c:invert_pointer_at case 1, Tag_ehd (*hp) != Closure_tagstatic 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; } }}

val0

p

val1

q

val1

val2val3

New encoded header (offset, Infix_tag, 3)Make_ehd (Wosize_bhsize (q - val), Infix_tag, 3);

val5 + Bosize_ehd(val8)*p = (word) &Field (val, Wosize_ehd (*hp)) | 1;

offset = Bosize_hd(val3)

val5

val

val4val6_

0

val7_i

hp

val6_1

val6_2

val9

hp

while (Ecolor (*hp) == 0) hp = (word *) *hp; Assert (Ecolor (*hp) == 3);

val0 | 2Hd_val (q) = ((word) p | 2);

val8

the first infix header

Page 32: Ocaml internal (description of runtime system in Korean)

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); } }}

val0

p

val1

q0

val1

val2_0

val2_1

val4

val4

q1

while (Ecolor (q) == 0) q = * (word *) q;

val5

sz = Whsize_ehd(val4)

val6

infixes

val5

q2

val7_0

val7_1

while (Ecolor (q) != 3) q = * (word *) (q & ~(uintnat)3);

q = * (word *) (q & ~(uintnat)3);

val8 val8

q3

compact_allocate (Bosize_ehd(val8));

val10

newadr

Infix_tag

val10+4* (word *) q = (word) Val_hp (newadr);

Make_header of val8 (Wosize_whsize (sz), t, Caml_white);

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);

Page 33: Ocaml internal (description of runtime system in Korean)

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

Page 34: Ocaml internal (description of runtime system in Korean)

nextntables = 1nitems = 1tables[5]

caml__roots_block

nextntables = 1nitems = 1tables[5]

caml__roots_block

Roots.c caml_local_roots

val0

caml_local_roots

struct caml__roots_block { struct caml__roots_block *next; intnat ntables; intnat nitems; value *tables [5];};

nextntables = 1nitems = 4tables[5]

caml__roots_block

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);}

// 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);

filenameres

nextntables = Nnitems = 1tables[5]

caml__roots_block

CAMLxparamN

CAMLxparam4

Macro pre-processing

Page 35: Ocaml internal (description of runtime system in Korean)

Roots.c caml_frame_descriptors

natdynlink.ccaml_natdynlink_run

(sym)roots.c

caml_init_frame_descriptors(caml_frametable[i])

roots.ccaml_register_frametab

le

frametables

struct link

struct link

struct link

struct link

추가

roots.ccaml_init_frame_descriptors

/* Allocate the hash table 해쉬테이블 구성 */caml_frame_descriptors = (frame_descr **) caml_stat_alloc(tblsize * sizeof(frame_descr *));

caml_frame_descriptors

Hash table offrame_descr

natdynlink.cgetsym, flexdll_dlsym

cmmgen.ml

let frame_table namelist = let mksym name = Csymbol_address (Compilenv.make_symbol ~unitname:name (Some "fram-etable")) 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

Page 36: Ocaml internal (description of runtime system in Korean)

caml allocation

memory.hAlloc_small

minor heap 에 할당 시도

alloc.ccaml_alloc

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.ccaml_alloc_shrfreelist 찾아보고 없으면

expand heap 하고 다시 꺼내온다

mlvalues.hAtom

Wosize 0 짜리 block 들을 보관하는 caml_atom_table

참조

freelist.ccaml_fl_allocatefreelist 에서 맞는 block 을

찾아본다

minor_gc.hcaml_minor_collectionminor heap 을 비우고 , major heap

으로 넘긴다 .

memory.cexpand_heap

chunk 를 할당하고 , align 작업 ,chunk linked list 에 추가

freelist.ccaml_fl_add_block

sfreelist 에 새로운 블록을 추가

alloc.ccaml_alloc_small

alloc.ccaml_alloc_tuple, caml_alloc_array,

caml_alloc_dummy, caml_alloc_dummy_float

weak.c:caml_weak_get_copynatdynlink.c:caml_natdynlink_run_toplevelbacktrace.c:caml_get_exception_backtracedebugger.c:caml_debugger_initsignal.c:caml_install_signal_handlerobj.c:caml_obj_block,caml_obj_dup,read_debug_info

array.ccaml_make_vect

caml_array_gather

Page 37: Ocaml internal (description of runtime system in Korean)

Minor GC roots and olidfyi386.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.ccaml_empty_minor_heap

minor_gc.ccaml_oldify_one

roots.ccaml_oldify_local_roots

caml_globalsarray of value

asmcomp/cmmgen.ml|2134| Cde-fine_symbol "caml_globals" …어떻게 생성되는지 아직 확인된바 없음

caml_dyn_globalslinked list, node->data = valueasmrun/roots.c|136| static link

* caml_dyn_globals = NULL;

OCAML-side stack local varsaved reg values, stack stored

valuesfollow stack frames starting with

caml_bottom_of_stack

caml_local_rootslinked list, node->tables[i][j] = valuebyterun/roots.c|28| CAMLexport struct

caml__roots_block *caml_local_roots = NULL;

caml_global_roots(_young)linked list, node->root = valuebyterun/globroots.c|171| struct

global_root_list caml_global_roots = { NULL, { NULL, }, 0 };

final_tablelinked list, node->fun, node->val = value

asmrun/finalise.c|222| final_table = caml_stat_alloc (new_size * sizeof

(struct final));

Page 38: Ocaml internal (description of runtime system in Korean)

call graph to malloc

misc.ccaml_aligned_malloc실제 malloc 호출하여 Page_size

이상의 heap chunk 를 만든다 .

misc.ccaml_alloc_for_heap

실제 malloc 호출하여 Page_size 이상의 heap chunk 를 만든다 .

minor_gc.ccaml_set_minor_heap_size

memory.cexpand_heap

fl_allocate 실패하면 old heap으로 chunk 를 할당하여 확장

major_gc.ccaml_init_major_heap처음부터 old heap 으로 하나의 chunk 를 잡아두고 시작한다 .

Heap chunk linked list 의 head

intern.cintern_alloc

unmarshalling 중에 내부적으로 사용하는 공간을 할당 page table 관리는 없다 .

compact.ccaml_compact_heap

compact 하고나서 old heap 에 적정 비율의 빈 공간이 있도록 해야한다 . 부족할 경우 할당한다 .

memory.ccaml_add_to_heap

페이지 테이블에 추가 , caml_heap_start heap chunk chaing 에 추가

memory.ccaml_page_table_add

Heap chunk linked list 에 추가Chunk_next (chunk) = caml_heap_start; caml_heap_start = chunk;

Page 39: Ocaml internal (description of runtime system in Korean)

gc trigger

signals_asm.ccaml_garbage_collecti

on

amd64.Scaml_call_gc

minor_gc.ccaml_minor_collection

major_gc.ccaml_major_collection_sli

ce

major_gc.cmark_slice

major_gc.csweep_slice

compact.ccaml_compact_heap_mayb

e

compact.ccaml_compact_heap_mayb

e

Mark phase

Sweep phase

Idle phaseafter sweep

amd64.Scaml_alloc1 .. N

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`

asmcomp/emit.ml

List.iter emit_call_gc !call_gc_sites;

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 Debugin-fo.none in ` 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`

emit 된 asm 코드에서 call

byterun/memory.h

Alloc_small caml_minor_collection ();

Page 40: Ocaml internal (description of runtime system in Korean)

Infix tag

0x4034972c

f1

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;;

Code ptr

Infix_tag(size=2)

0x40349734

f2

0x40349714

f3

Closure_tag head

Code ptr1

Infix_tag(size=2)

Code ptr2

0x4034971c

f4

Infix_tag(size=4)

0x40349724

f5

Code ptr3

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;;

Page 41: Ocaml internal (description of runtime system in Korean)

caml_globals

&camlPervasives &camlSimple &camlStd_lib 0caml_globals labelin data section

camlPervasives labelin data section

Tag=0, size=1

0x0061f3b8

String_tag, size=2 “Pervasives.Exit\x00”

block header(size)

camlPervasives__84fail_with label

camlPervasives__85invalid_args label

exc Exitheap block addr

camlPervasives__83

min label

camlPervasives__82

max label

camlPervasives__82

max label…

infinityheap block addr

Double_tag, size=1 0x00000000 0x7ff00000

(gdb) x/8x 0x00007ffff7519fe00x7ffff7519fe0: 0x000007fd 0x00000000 0x00000000 0x7ff000000x7ffff7519ff0: 0x00000400 0x00000000 0x0061f3b8 0x00000000

Page 42: Ocaml internal (description of runtime system in Korean)

frametable

• frametable 은 ML-side 에서 생성한 모든 함수들의 frame 정보를 담는 frame descriptor 를 가지고 있다 .

• frame descriptor 는 ML-side 에서 생성되어 assembly code 의 data section 에 symbol로 정의된다 . 하지만 데이터에 대한 해석은 C-side 의 stack.h : struct 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)

// stack.htypedef struct { uintnat retaddr; unsigned short frame_size; unsigned short num_live; unsigned short live_ofs[1];} frame_descr;

Page 43: Ocaml internal (description of runtime system in Korean)

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_programamd64.S : caml_callback_exn

– ML-side -> C-sideamd64.S : caml_c_callamd64.S : caml_call_gcamd64.S : caml_raise_exn

– bottom_of_stack, last_retaddrC-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 들 ) 을 한번에 뛰어 넘을 수 있다 .

Page 44: Ocaml internal (description of runtime system in Korean)

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 을 확인한다 .

• Frame descriptor 를 찾는 방법– 현재 frame 에 저장된 return address 를 hash key 로 삼아서 찾는다 .– frame descriptor 마다 대응하는 return address 를 적어두었기 때문에 매칭하는 descriptor 를 찾을 수 있다 .– return address 로 frame descriptor 를 찾는다는 것은 call instruction 을 포함하는 함수의 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

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__frametablecamlNqueen__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

Page 45: Ocaml internal (description of runtime system in Korean)

frametables, caml_frametable

caml_frametable symbol

.globl camlNqueen__frametablecamlNqueen__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

.data .globl caml_frametablecaml_frametable: .quad caml_startup__frametable .quad caml_system__frametable .quad camlPervasives__frametable .quad camlNqueen__frametable .quad camlStd_exit__frametable .quad 0

camlNqueen_frametablesymbol

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 [email protected]: movq 24(%rsp), %rbx movq %rbx, %rdi addq $2, %rbx movq %rbx, 24(%rsp) movq 32(%rsp), %rax cmpq %rax, %rdi jne .L133

assembly instructions of the functioncamlNqueen1__loop_1032

Page 46: Ocaml internal (description of runtime system in Korean)

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 GCsweep phase

after major GC major GC

소속 young/old heap

young/old heapfinal_table(recent set)

young/old -> old heapfinal_table(old set)

old heapfinal_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 함수 호출 )

Page 47: Ocaml internal (description of runtime system in Korean)

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 참고 )

Page 48: Ocaml internal (description of runtime system in Korean)

ML/C-side interface(amd64 기준 )

• C-side assembly codeC 코드는 gcc 로 컴파일된다 . C-side 코드의 어셈블리규약 (convention) 은 gcc 컴파일러를 따른다 .

• ML-side assembly codeML-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)

Page 49: Ocaml internal (description of runtime system in Korean)

ML/C-side interface(amd64 기준 )

• proc.ml : int_reg_nameregister 들의 이름과 번호를 맵핑해준다 . C-side 에서도 그 순서를 준수해야 한다 ."rax"; "rbx"; "rdi“ ....

• caml_gc_regsML-side 에서 사용하던 register 가 GC root values 를 가지고 있을 수도 있으므로 , caml_call_gc 에서 모든 register 를 저장한다 .아래 코드처럼 모든 register 를 스택에 넣고 스택포인터주소 ($rsp) 를 array 처럼 사용가능

• 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 를 실행한다 .

...262 pushq %rdi263 pushq %rbx264 pushq %rax265 STORE_VAR(%rsp, caml_gc_regs)

asmcomp/emit.ml

662 | Lpushtrap ->663 cfi_adjust_cfa_offset 8;664 (emit_string " pushq %r14\n");665 cfi_adjust_cfa_offset 8;666 (emit_string " movq %rsp, %r14\n");667 stack_offset := !stack_offset + 16... 674 | Lraise ->675 if !Clflags.debug then begin676 (emit_char ' '; emit_call "caml_raise_exn"; emit_char '\n');677 record_frame Reg.Set.empty i.dbg678 end else begin679 (emit_string " movq %r14, %rsp\n");680 (emit_string " popq %r14\n");681 (emit_string " ret\n")682 end

asmrun/amd64.S

723 /* Raise an exception from C */724725 FUNCTION(G(caml_raise_exception))726 TESTL_VAR($1, caml_backtrace_active)727 jne LBL(111)728 movq C_ARG_1, %rax729 LOAD_VAR(caml_exception_pointer, %rsp) /* Cut stack */730 popq %r14 /* Recover previous excep-tion handler */731 LOAD_VAR(caml_young_ptr, %r15) /* Reload alloc ptr */732 ret733 LBL(111):...

Page 50: Ocaml internal (description of runtime system in Korean)

ML/C-side interface(amd64 기준 )

• caml_last_return_address, caml_bottom_of_stackstack 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 registersC/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_addresscaml_bottom_of_stack

roots.c- skip C call framesnext_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

Page 51: Ocaml internal (description of runtime system in Korean)

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" -> 258 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 intex-t.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 mar-shal.ml:32#12 0x00000000004112e8 in camlIntext__test_buffer_1102 () at intex-t.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

Page 52: Ocaml internal (description of runtime system in Korean)

Ocaml systhread, master_lock, enter/leave_blocking_sectiontest/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:51260 #3 0x000000000044143a in caml_start_program ()---------------- hidden caml_callback_exn ---------------------261 #4 0x000000000043126e in caml_execute_signal ()262 #5 0x000000000043132e in caml_process_pending_signals ()263 #6 0x0000000000431355 in caml_enter_blocking_section ()264 #7 0x0000000000437688 in do_write ()---------------- hidden caml_c_call ---------------------------265 #8 0x000000000043786b in caml_flush_partial ()266 #9 0x00000000004378c2 in caml_flush ()267 #10 0x000000000043813e in caml_ml_flush ()268 ...

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 하기를 기다린다 .

Page 53: Ocaml internal (description of runtime system in Korean)

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

• bytecomp/matching.ml

#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

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 } ))))

Page 54: Ocaml internal (description of runtime system in Korean)

Tag re-arrange

• bytecomp/translcore.ml

• utils/config.ml

• topdir/Makefile(cross compilation)

• testing(topdir/testsuite/tests/misc/hamming.ml)

> ../../../ocamlopt -nostdlib -I ../../../stdlib/ hamming.ml> ./a.outSegmentation fault (core dumped)

let max_tag = 245let lazy_tag = 246

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/opt-main.cmo cd stdlib; make allopt RUNTIME=ocamlrun

(* 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])

Page 55: Ocaml internal (description of runtime system in Korean)

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) btbt#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)

Page 56: Ocaml internal (description of runtime system in Korean)

closure, caml_curry, caml_curry_app, caml_tuplify

• closure block

Hd(closure_tag) caml_curry9_8 arity3 = 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 arity15 = int(7)

caml_curry9_2_app arg2 closure value

Hd(closure_tag) caml_curry9_1 arity17 = int(8)

caml_curry9_1_app arg1 closure value

Hd(closure_tag) caml_curry9 arity19 = int(9)

fun ptr (symbol in asm)

Hd(closure_tag) caml_curry9_7 arity5 = int(2)

caml_curry9_7_app arg8 closure value

Page 57: Ocaml internal (description of runtime system in Korean)

closure, caml_curry, caml_curry_app, caml_tuplify

• closure block

Hd(closure_tag) caml_curry9_3 arity13 = int(6) = 9-3

caml_curry9_3_app arg3 closure value

Hd(closure_tag) camlCtest__fun_1020(arg1 - w, this closure block)= arg1 * 7

arity1

7(arg2 - z)

13 let mult_sum (x,y) = 14 let z = x+y in 15 fun w -> w*z;; -------------- camlCtest__fun_1020

Hd(closure_tag) caml_tuplify2 arity 2-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 arity15 = int(7)

caml_curry9_2_app arg2 closure value

Page 58: Ocaml internal (description of runtime system in Korean)

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]

• 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)

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))

Page 59: Ocaml internal (description of runtime system in Korean)

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)

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, ...)

(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)))