Machine Level Programming : Procedure 2

임승섭·2023년 4월 20일
0

Computer Systems

목록 보기
4/7

Register Saving Conventions

교재 내용

  • 하나의 procedure(caller)가 다른 procedure(callee)를 호출(call)할 때, callee는 caller가 나중에 사용할 계획인 일부 레지스터 값은 덮어쓰지 않는다.
  • 관습적으로 %rbx, %rbp, %r12~ %r15는 피호출자 저장 레지스터로, P가 Q를 호출할 때, Q는 다시 P로 리턴될 때 Q가 호출되었을 때의 값들과 동일하도록 보장할 수 있게 이 레지스터 값들을 보존해야 한다.
  • 이를 위해, 아예 변경하지 않거나, 원래 값을 스택에 push해두고 이 값을 변경한다. 리턴하기 전에 스택에서 이전 값을 pop해오는 방식으로 레지스터를 보존한다.
  • 이러한 방식으로 P에 대한 코드는 피호출자 저장 레지스터에 안전하게 값을 저장할 수 있으며, Q를 호출하고 이 값이 깨지는 위험 없이 레지스터에서 이 값을 사용할 수 있다.
  • 스택 포인터 %rsp을 제외한 다른 모든 레지스터들은 호출자 저장 레지스터로, 이들이 함수에 의해 변경될 수 있다는 것을 의미한다.
  • 이름 자체가 지역데이터를 레지스터에 보관하며, Q를 호출하는 P의 문맥에서 이해될 수 있다. Q는 이 레지스터를 자유롭게 변경할 수 있기 때문에 P가 호출하기 전에 먼저 데이터를 저장해야 할 의무가 있다.

Example

  • C code
long P(long x, long y) {
	long u = Q(y);
    long v = Q(x);
    return u + v;
}
  • Assembly code
P :
	// 먼저 두 레지스터의 값을 스택에 저장한다.
	pushq %rbp			// save %rbp. x를 보관한다.
    pushq %rbx			// save %rbx. Q(y)를 보관한다.
    
    subq $8, %rsp		// allign stack frame
    movq %rdi, %rbp 	// save x
    movq %rsi, %rdi		// move y to first argument
    call Q
    
    movq %rax, %rbx		// save result. Q 호출의 결과를 %rbx에 저장한다.
    movq %rbp, %rdi		// move x to first argument
    call Q
    addq %rbx, %rax		// add saved Q(y) to Q(x)
    addq $8, %rsp		// deallocate last part of stack
    
    // 두 피호출자-저장 레지스터의 값을 스택에서 pop해와서 복원한다. (역순임을 주의하자!!)
    popq %rbx			// restore %rbx
    popq %rbp			// restore %rbp
    ret

PPT 내용

Definition

  • "Caller-saved" registers
    • It is caller's responsibility to save any important data in these registers before calling another porcedure. (i.e. the callee can freely change data in these registers)
    • Caller saves values in its stack frame before calling callee, then restores values after the call
  • "Callee-saved" registers
    • It is the callee's responsibility to save any data in these registers before using the registers. (i.e. the caller assumes the data will be the same across the callee procedure call)
    • Callee saves values in its stack frame before using, then restores them before returning to caller

Type

  • Caller-saved
    • %rax : return value.
    • %rdi, %rsi, %rdx, %rcx, %r8, %r9 : arguments
    • %r10, %r11 : temporary space
  • Callee-saved
    • %rbx, %r12, %r13, %r14, %r15 : temporary space
    • %rbp : may be used as frame pointer.
    • %rsp : do not need to explicitly save. should be restored to original value upon exit from procedure

Example

  • C code
long call_incr2(long x) {
	long v1 = 351;
    long v2 = increment(&v1, 100);
    return x + v2;
}
  • Assembly code
call_incr2 :
	pushq	%rbx		// save %rbx. 먼저 레지스터 값을 스택에 저장
    subq	$16, %rsp	// 2 frame 공간을 만든다
    
    movq	%rdi, %rbx	// %rbx에 x값 저장. (%rdi는 1st argument register)
    movq	$351, 8(%rsp)	// 아까 frame 공간 만들었던 위쪽에 351 저장
    
    // 1st, 2nd argument register에 값을 넣어서 call 준비
    movl	$100, %esi		// %esi에 100 저장 
    leaq	8(%rsp), %rdi	// %rdi에 351 저장 (%rdi는 1st argument register)
  
  	call	increment
    
    addq	%rbx, %rax		// %rax(increment함수의 결과값)과 x 더해서 다시 %rax에 저장
    
    // 다시
    addq	$16, %rsp		// 2 frame 공간 없애
    popq	%rbx			// pop으로 복원

Illustration of Recursion

교재 내용

  • 각 procedure call은 스택 상에 자신만의 사적 공간을 가지며, 다수의 별도 call의 지역변수들은 서로 간섭하지 않는다. 뿐만 아니라, 스택 운영 방식은 procedure가 호출될 때 지역 저장소를 할당하고, 리턴하기 전에 이를 반환하는 적절한 정책을 자연스럽게 제공한다.

Example

  • C code
long rfact(long n) {
	long result;
    if (n <= 1)
    	result = 1;
    else
    	result = n * rfact(n-1);
    return result;
}
  • Assembly code
rfact :
	// 여기도 마찬가지로, %rbx 레지스터 사용하기 위해 일단 stack에 push해서 저장시켜둔다.
	pushq	%rbx
    movq	%rdi, %rbx			// %rbx에 n을 저장한다. (%rdi는 1st argument register)
    movl	$1, %eax			// %eax에 1 저장 (result = 1)
   
    cmpq	$1, %rdi			// n : 1 비교	
    jle		.L35				// 1 ≤ n이면 L35로 jump
    
    leaq	-1(%rdi), %rdi		// n = n-1
    call	rfact
    
    imulq	%rbx, %rax			// %rax(rfact의 return값) = %rax * n

.L35:
	popq	%rbx				// 다시 popq해서 저장해두었던 값 restore. restore %rbx
    ret

cmpq에서는 레지스터 순서를 바꿔서 비교한다는 걸 주의하자!!


PPT 내용

  • C code
long pcount_r(unsigned long x) {
	if (x == 0)
    	return 0;
    else
    	return (x&1) + pcount_r(x>>1);
}
  • Assembly code
pcount_r :
	movl $0, %eax		// %eax에 먼저 0을 저장해둔다. recursive 안들어가면 바로 return 0이니까
    
    testq %rdi, %rdi	// compare x : 0
    jne	.L8				// not equal이면 L8로 jump
    
    rep ret
.L8 :
	pushq %rbx			// %rbx 쓰려고 일단 스택에 저장시킴
    movq %rdi, %rbx		// %rbx에 x 저장
    
    // 함수 호출하기 전 1st argument 만져줌
    shrq %rdi			// x = x >> 1
    
    call pcount_r
    
    andl $1, %ebx		// x = x & 1
    addq %rbx, %rax		// %rax(pcount_r의 결과값) + x&1 더해서 %rax에 저장
    
    popq %rbx			// restore %rbx
    ret

Obervation about Recursion

Works without any special consideration

  • Stack frames mean tha each function call has private storage
    • saved registers & local variables
    • saved return address
  • Register saving conventions prevent one function call from corrupting another's data
    • Unless the code explicitly does so (e.g. buffer overflow)
  • Stack discipline follows call /return pattern
    • If P calls Q, then Q returns before P
    • Last-In, First-Out (LIFO)

0개의 댓글