프로시저 : 특정 업무 수행을 위한 절차들의 기술
함수 : 각 절차의 구현
(프로시저와 함수는 코드의 가독성과 재사용성을 높이는 추상화 방법 중 하나다)
인수 : 입력 겸, 데이터와 프로시저를 잇는 인터페이스
프로시저의 실행 과정
1. 프로시저에 인수 입력
2. 프로시저에 제어 넘기기
3. 프로시저는 필요한만큼 메모리를 가져가서 작업을 수행한다
4. 프로시저는 리턴
5. 사용한 프로시저를 원래 위치에 되돌려 놓기
이때 쓰이는 레지스터들의 역할
x0 : 0만 담음
x1 : 다음 실행될 명령어의 주소를 담음
x2 : 다른 레지스터들의 값을 보존 (=sp, stack pointer)
x5~7 : 보존 X
x8(=fp), x9 : 보존 O
x10~17 : 인수용
x18~27 : 보존 O
x28~31 : 보존 X
프로시저용 명령어
jal (= jump and link )
기능 = jal의 다음 명령어 주소를 rd(여기선 x1)에 저장하고 지정된 주소로 이동한다
사용예 = jal x1, ProcedureAddress
또한, 복귀를 위해 아래 명령어를 사용한다
jalr x0, 0(x1)
하지만 이름만 복귀주소고 실제로는 다음 주소를 넣는다. (다음 주소 = 복귀주소 + 4)
컴파일러의 프로시저 명령어 번역시에 인수 레지스터 8개만으로 부족한 경우
위의 해결책 = 레지스터 스필링
그것이 필요로 하는 것 = 스택
그것이 필요로 하는 것 = 스택 포인터 (x2(=sp) 사용)
(ArrayStack의 NowPosition과 같다)
(자료구조 카테고리의 Stack 게시글 참고)
f = ( g + h ) - ( i + j );를 C프로시저로 쓰면 아래와 같다
long long int leaf_example ( long long int g, long long int h, long long int i, long long int j ){
long long int f;
f = ( g + h ) - ( i + j );
return f;
}
_____________________________________
f ,g, h, i, j = x20, x10, x11, x12, x13
위 C프로시저는 아래 명령어들을 쓰게 된다
addi sp, sp, -24 // 스택포인터에 3바이트 할당
sd x5, 16(sp) // 스택포인터[2]에 x5 저장
sd x6, 8(sp) // 스택포인터[1]에 x6 저장
sd x20, 0(sp) // 스택포인터[0]에 x20 저장
add x5, x10, x11 // ( g + h )를 x5에 저장
add x6, x12, x13 // ( i + j )를 x6에 저장
sub x20, x5, x6 // ( g + h ) - ( i + j )를 x20에 저장
addi x10, x20, 0 // x20을 x10에 저장
ld x20, 0(sp) // x20의 값 복구
ld x6, 8(sp) // x6의 값 복구
ld x5, 16(sp) // x5의 값 복구
addi sp, sp, 24 // 스택포인터 복구
jalr x0, 0(x1) // 복귀주소로 돌아가기
그런데 쓰지도 않는 프로시저에 위 작업을 하는 비효율이 발생할 수 있다. 그래서 이를 위해 아래 관례를 사용한다.
x5 ~ x7, x28 ~ x31 = 값 보존 안되는 임시 레지스터
x8 ~ x9, x18 ~ x27 = 값 보존이 되는 임시 레지스터
중첩 레지스터
말단 레지스터 : 다른 프로시저를 안호출하는 프로시저
그런데 대부분은 말단 레지스터가 아니라서 같은 레지스터를 중복해서 사용할 수도 있다. 이를 방지하기 위해 보존되어야 모든 레지스터는 아까처럼 스택포인터를 이용해서 보존한다
어셈블리어로 중첩 프로시저의 구현 예
long long int Get_factorial (long long int n){
if (n < 1)
return 1;
else
return n * Get_factorial(n - 1);
}// n = x10
_____________________________________
factorial:
addi sp, sp, -16 // 기존 데이터 유지를 위해 2바이트의 스택포인터 생성
sd x1, 8(sp) // 스택포인터[2]에 복귀주소 저장
sd x10, 0(sp) // 스택포인터[1]에 n의 값 저장
addi x5, x10, -1 // if (n < 1)의 변형식 if (n - 1 < 0)의 구현을 위해 n - 1을 만들고 x5에 저장
bge x5, x0, L1 // if (n - 1 < 0)가 else 되는 경우 (n - 1 >= 0)에 else구문으로 이동
addi x10, x0, 1 // if (n < 1)가 true인 경우에 실행될 내용 ( n = 1 )
addi sp, sp, 16 // 스택포인터 삭제
jalr x0, 0(x1) // 복귀
L1:
addi x10, x10, -1 // n에서 1 빼기
jal x1, Get_factorial // 빼진 n으로 함수 재실행
addi x6, x10, 0 // 빼진 n을 x6에 저장
ld x10, 0(sp) // n 복구
ld x1, 8(sp) // 복귀주소 복구
addi sp, sp, 16 // 스택포인터 삭제
mul x10, x10, x6 // n * (n - 1)
jalr x0, 0(x1) // 복귀
프로시저를 위한 스택의 공간 할당
프로시저 프레임(=activition record) : 프로시저의 보존 레지스터와 지역변수를 가진 스택 영역
프레임 포인터(=fp) : 프로시저 프레임의 내부 값의 위치를 표시하는 값
RISC-V의 메모리 할당방식
스택의 할당 방식 : 최상위 주소에서 시작해서 최하위쪽으로 추가
기계어 코드의 할당방식 : 스택 다음에 할당 (이 할당할 곳을 text segment라고 부른다)
(그 중 위쪽에 있는 것은 static data segment로, 상수와 기타 정적 변수, 배열이 들어간다)
(아래에 있는 것은 static data segment (=heap)으로
C의 할당 방식 : 위 기계어 코드의 할당을 구현하기 위해 malloc()을 이용해서 힙에 공간을 할당하고
tree()를 이용해서 공간을 반납한다. 반납을 계속 하지 않으면 메모리 부족으로 운영체제가 붕괴할 수 있다.
자바는 가비지 컬렉터, 자동 메모리 할당이 대신한다
어셈블리어로 합을 누적시키는 프로시저의 구현 예
long long int sum (long long int n, long long int acc) {
if (n > 0)
return sum( n - 1, acc + n );
else
return acc;
} // x10 = n, x11 = acc, x12 = return용 값
____________________________________________________________
sum: ble x10, x0, sum_exit // { if (n > 0)
add x11, x11, x10 // acc + n
addi x10, x10, -1 // n - 1
jal x0, sum // sum(n - 1, acc + n)
sum_exit: // else
addi x12, x11, 0 // return acc;
jalr x0, 0(x1) // }
'BackEnd > Computer Architecture' 카테고리의 다른 글
1.2.6 어셈블리어로 긴 수치와 주소를 쓰는 법 (0) | 2022.03.02 |
---|---|
1.2.5 어셈블리어로 문자와 문자열 사용해보기 (0) | 2022.02.27 |
1.2.3 어셈블리어 구현 (0) | 2022.01.07 |
1.2.2 숫자 구현 (0) | 2022.01.06 |
1.2.1 어셈블리어 문법과 종류 (0) | 2022.01.02 |
댓글