본문 바로가기
Computer Architecture/ISA

[RISC-V] 컴퓨터구조 - Procedure call과 스택포인터

by FastBench 2024. 3. 11.
반응형

사전 참고

1. [RISC-V] 컴퓨터구조 - Instructions 개요  https://microelectronics.tistory.com/38

2. [RISC-V] 컴퓨터구조 - Instruction to Machine Code https://microelectronics.tistory.com/39


 

2.8 Supporting Procedure in Computer Hardware

procedure (절차)/ 함수 를 실행하기 위해서 프로그램은 반드시 아래 6가지 단계를 밟아야 한다.

  1. procedure 가 접근할 수 있는 곳에 파라미터(매개변수)를 둔다
  2. procedure 을 제어,전달한다.
  3. procedure 에 필요한 storage resource 를 획득한다.
  4. 해당하는 task 를 수행한다.
  5. procedure 를 call 한 프로그램(다른 procedure) 이 접근할 수 있는 곳에 결과 값을 둔다.
  6. call 되기 이전에 원래 있던 위치로 돌아온다.

레지스터에 접근하는것이 메모리에 접근하는 것 보다 빠르기 때문에, RISC-V 는 32개의 레지스터 중 일부를 procedure 와 관련하여 사용한다.

  • x10-x17 : 8개의 parameter. 또는 procedure의 return value. 주로 x10,x11이 return value 로 사용됨
  • x1 : 원점의 위치에 해당하는 return address.

 

procedure 로 이동하기 위해 사용되는 RISC-V 어셈블리어를 살펴보자.

jal 은 caller 가 callee 를 호출하기 위해, jalr 은 callee 에서 caller 로 돌아가기 위해 사용된다.

x1 에는 return address 가 들어있을 것이기 때문이다. (jal 하면서 x1 에 넣어둔다.)

 

Using More Registers

만약 caller 가 callee 를 호출 하기전에, (그 또한 누군가의 callee 여서) x10-x17 레지스터를 사용중이었다고 가정하자.

그럼 jalr 을 통해 return address 로 복귀하기 전, x10-x17 레지스터를 복구할 필요가 있다.

이때 stack 메모리를 사용하면 아주 쉽고 효율적으로 store, re-store 할 수 있다.

RISC-V 에서 stack pointer 는 x2 이고 sp 라고 어셈블리어에서 사용하기도 한다. 

 

위 예시에서 x5,x6,x20 이 사용되기 전에 값이 저장되고 나중에 복구되어야 한다고 가정했는데.

복구될 필요가 없는 데이터일 경우도 있다.

 

RISC-V 는 이를 위해 temporary registers 와 saved registers 를 분리한다.

  • x5−x7 and x28−x31: temporary registers that are not preserved by the callee (called procedure) on a procedure call
  • x8−x9 and x18−x27: saved registers that must be preserved on a procedure call (if used, the callee saves and restores them)

즉 위 예시에서 x5,x6 은 leaf_example 을 호출한 caller 가 temp 값의 저장에 사용했을 것이기 때문에, store-restore 를 생략해도 된다.

 

Nested Procedures

위 예시와 같이, 다른 procedure 를 호출하지 않는 procedure 를 'leaf procedure' 라고 한다.

non-leaf procedure 의 경우에는 데이터 뿐만아니라 return address 까지 어딘가에 보관하고, 복구시켜야 한다.

예를들어, 별다른 저장 없이 jal 이 두번 연속 일어난다면 x1의 return address 는 오버랩 될 것이다.

재귀함수 예

아래 표는 procedure call 에서 보존되는 것과, 보존되지 않는 것을 명시한 표이다.

(쉽게 말해 store, restore 가 필요한 것과 아닌 것.)

Global Pointer

알다시피, c언어에는 static 키워드가 있다.

static 은 함수내의 변수에 선언하느냐, 전역변수에 선언하느냐, 함수에 선언하느냐에 따라 용도에 차이가 있지만

근본적으로 프로그램 실행시 한번만 초기화 되서 프로그램이 종료할 때 까지 메모리에 남아있는 것은 동일하다.

 

이런 static 키워드가 붙은 변수들은 메모리의 특정 영역에 순서대로 저장되는데, RISC-V 는 이러한 static 데이터에 대한 접근을 단순화 하기 위해 global pointer (gp) 로 x3 레지스터를 예약하여 사용한다.

 

Allocating Space for New Data on the Stack

위 예시에서는 sp 를 기준으로 load, store 하는 것만 다뤘지만, 만약 procedure 에서 배열이나, 많은 양의 변수가 선언되어 레지스터에 다 담을 수 없을때도 스택은 이들을 담아야 한다.

이렇게 procedure 의 save register 나 로컬 변수들을 담는 스택의 구역을 'procedure frame' 또는 'activation record' 라고 한다.

 

만약 procedure 코드가 실행되는 동안, procedure frame 에서 스택포인터가 변경된다면 procedure 내의 지역 변수나 매개변수에 대한 offset 참조가 일정하지 않게된다.

 

이에 대한 해결책으로, procedure 내의 메모리 기준을 위한 frame pointer 라는것이 있다. RISC-V 컴파일러는 procedure 의 프레임의 첫번째 word 를 frame pointer 로 사용한다. 이로인해 procedure 내에서 sp 가 바뀌더라도 항상 안정적이고 일관된 접근이 보장된다.

 

RISC-V 에서는 x8 을 frame pointer 로 사용하며 fp 라고 줄여 부르기도 한다.

stack allocation (a) before, (b) during, and (c) after the procedure call

컴파일러에 따라 다르겠지만, 지역변수가 없거나, 따로 레지스터값을 저장할 필요가 없는 경우 procedure call 이 있더라도 시간을 아끼기 위해 fp를 셋팅하지 않는 경우도 있다.  보통 fp 가 사용되면, call이 발생할 때 fp는 sp의 주소로 초기화되고 sp는 추후 fp를 통해 restored 된다.

 

Allocating Space for New Data on the Heap

흔히 볼수 있는 프로그램의 메모리 구조인데, 간략히 설명하면 아래와 같다.

  • stack 은 함수 호출과 로컬변수 할당을 위해 사용되고
  • static data (.bss .data)는 프로그램 실행동안 변경되지 않는 데이터를 저장하는데 사용된다. 
  • Dynamic data (.heap) 은 프로그램 실행동안 데이터 구조의 크기가 변하는, 동적으로 할당되고 해제되는 데이터를 위한 공간이다.
  • Text (.code)는 프로그램 머신코드가 위치한다.

 


Reference

반응형

댓글