지난 글에서는 큰 수 덧셈과 뺄셈에 대하여 정리하였다.
간단하게 요약하면 큰 수를 더하고 뺄 때는 cc 명령어를 사용해 캐리를 넘겨주고, x 명령어를 사용해 캐리를 받아 여러 레지스터를 사용해 큰 수를 연산하였다.
이번 글에서는 SPARC 프로세서의 곱셈과 서브루틴에 대해 정리하고자 한다.
곱셈
곱셈은 기본적으로 bitshift 와 덧셈의 조합이다.
예를 들어 101 x 204 를 한다고 해보자.
초등학교 때로 돌아가 어떻게 큰 수의 곱셈을 했는지 따져보면
101 x 4 x 1
101 x 0 x 10
101 x 2 x 100
이렇게 곱셈을 하고 다 더해서 곱셈을 계산했던 기억이 있을 것이다.
컴퓨터도 마찬가지로 이진수에 대해 위와같은 연산을 함으로써 곱셈을 구현한다.
이때 1, 10 100 처럼 1, 2, 4 로 자릿수를 옮겨갈 때 비트쉬프트를 이용하고,
곱하는 수의 자릿수가 1이면 더해주고 아니면 더하지 않는 방식으로 그림처럼 계산하면 된다.
기본적인 SPARC 의 경우, 다른 언어와 달리 곱셈, 나눗셈 명령어가 따로 존재하지 않는다.
(최신버전 SPARC는 있지만, 학교에서 실습하는 버전은 없다고 한다.. 으아아아)
그래서 함수와 유사한 '서브루틴' 을 이용한 곱셈과 나눗셈 기능을 제공한다.
서브루틴?
서브루틴은 '코드 조각 모음' 이다.
수학의 함수와 비슷하지만, 리턴값이 강제되지 않는다는 점에서 수학의 함수와는 다르다.
프로그래밍에서 '함수' 라고 부르는 것과 대부분 비슷하다.
SPARC 에서 함수를 사용할 때는 다음과 같은 순서로 사용한다.
1. 함수를 호출 할 때 넘길 인자값을 o-register에 세팅
2. 함수 호출 (call <label> 형태로 사용한다. 함수 호출도 분기 명령어기에 호출 후 nop 를 사용하거나, 최적화를 해주어야 한다.)
3. 함수 내부에서 i-register 를 이용해 o-register 값을 받아온다.
4. 함수 내부에서 이것 저것 처리를 하고
5. 리턴할 값을 i-register 에 세팅
6. 리턴한다.
7. 함수를 호출 했던 곳에서 o-register 를 이용해 리턴값을 받아온다.
i 레지스터로 받아오는 동시에, 리턴도 시키고, o 레지스터를 이용해 호출과 동시에 리턴값도 받아올 수 있는 이유는 이전 글에도 적은 레지스터 윈도우 때문이다.
피호출함수의 i 레지스터와 호출 함수의 o 레지스터가 같은 위치 레지스터를 공유하고 있기 때문에 가능하다.
재미있는 점은 SPARC 의 서브루틴을 통해 우리의 커스텀 함수를 만들어 쓰는 것 뿐만 아니라 아래와 같은 작업도 할 수 있다는 것이다.
1. C 언어 라이브러리 함수 사용 (printf, scanf, memset ...)
2. 기본적인 곱셈/나눗셈 함수 사용 (.mul, .umul, .div, .udiv, .rem, .urem)
곱셈과 서브루틴
그렇다면 SPARC 에서 제공하는 .mul, .umul 은 어떻게 사용하는 것일까?
(참고로 .mul 은 signed 숫자에 대해, .umul 은 unsigned 숫자에 대해 사용한다.
몫, 나머지 연산에 대해서도 동일하므로 이후에는 이 설명을 생략한다.)
아까 서브루틴의 호출 과정을 그대로 이용하면 된다.
.mul 서브루틴은 2개의 인자를 받아 곱한 결과를 돌려준다.
%o0 에 곱해질 수 (피승수, multiplicand) 를 넣고, %o1 에 곱할 수 (승수, multiplier) 을 넣는다.
사실 곱셈은 교환법칙이 적용되므로 순서는 상관없다.
결과값은 %o0, %o1 두개로 나온다.
그 이유는 32bit 수 2개를 곱하면 수의 범위가 64bit 까지 나올 수 있기 때문이다.
%o0 에는 0~31번째 비트 값이 담겨있고, %o1 에는 32~63번째 비트값이 담겨있다.
작은 수를 다뤄 결과값도 32bit 이하임을 보장할 수 있을 때는 %o1은 무시해도 된다.
위 이미지는 곱셈 명령어를 사용하는 과정을 간단하게 표현한 것이다.
(branch delay slot 을 최적화 할 때 mul 함수 호출 이후에 인자값을 세팅해도 작동하는 원리는 좀 더 고민을 해봐야겠다.
자세한 건 컴퓨터 구조 전공 수업때 공부한다고도 함.)
나눗셈
나눗셈은 곱셈의 역과정으로, 뺄셈과 비트시프트를 이용한다.
우리가 초등학교 때 나눗셈을 연산하던 과정 그대로 컴퓨터도 연산한다.
divisor 을 뺄 수 있으면 (빼도 음수가 안나오면) 몫에 1을 넣고 비트시프트,
뺀 나머지 값에 대해 다시 divisor 을 뺄 수 있는지 체크해서 빼지면 1 안되면 0을 넣는 과정을 반복한다.
그러면 빼지지 않고 남은 값이 나머지 remaineder가 되고, 빼는 동안 체크었던 1과 0 값들이 몫이 된다.
나눗셈도 곱셈과 마찬가지로 %o0 %o1 에 각각 나누어질 수, 나누는 수를 담는다.
이때는 순서가 중요하다.
그리고 나눈 몫/나머지 결과값을 %o0 에서 받는다.
이때는 곱셈과 달리 %o1 을 사용하지 않는다. (32비트보다 큰 몫/나머지가 나올 수 없으므로)
나눗셈도 서브루틴을 이용하므로 사용 방법은 곱셈과 완전히 동일하기에 설명을 생략한다.
곱셈과 나눗셈의 코드 예시
과제로 곱셈과 나눗셈의 서브루틴을 활용한 연산 문제가 주어졌다.
블로그에는 과제 코드 대신 강의록의 코드를 기재하도록 하겠다.
.global main
:main
save %sp, -96, %sp ! 메인 함수 실행
mov 100, %l4 ! L4 <- 100
mov 15, %l5 ! L5 <- 15
add %l4, %l5, %l0 ! L0 <- L4 + L5
sub %l4, %l5, %l1 ! L1 <- L4 - L5
mov %l4, %o0 ! 곱셈 매개변수 세팅
mov %l5, %o1
call .mul ! 곱셈 함수 호출
nop
mov %o0, %l2 ! L2 <- L4 * L5
mov %l4, %o0 ! 나눗셈 매개변수 세팅
mov %l5, %o1
call .div
nop
mov %o0, %l3 ! L3 <- L4 / L5
ret
restore ! 메인함수 종료
'CS > 어셈블리' 카테고리의 다른 글
[SPARC] 16. 비트 연산 명령어 (0) | 2023.10.15 |
---|---|
[SPARC] 15. 논리 연산 명령어 (0) | 2023.10.14 |
[SPARC] 13. Hardware & 큰 수 연산 (0) | 2023.10.11 |
[SPARC] 12. Unsigned/Signed Integer & Carry / Overflow (0) | 2023.10.06 |
[SPARC] 11. 산술 연산과 Condition Code (0) | 2023.10.05 |