본문 바로가기

무언가 만들기 위한 지식/SPARC Assembler

SPARC Architecture Register Set and Stack Frame

이부분은 약간 이론적인 부분으로 프로그래밍할때는 딱히 몰라도 프로그래밍은 가능하다.
그러나 기본적인 구조를 알아야 좀더 효율적으로 프로그래밍을 할 수 있는건 당연한 일이다.

여기서 알아 볼 것은 레지스터 셋과 스택구조에 대한 이야기이다.~
이 두가지를 같이 설명하는 이유가 처음 이 두가지가 상당히 혼동되고(필자의 경우..) 겹쳐지는 부분과 떨어지는 부분이 은근히 오묘하여  대충 알아두면, 나중에 다시 책을 볼때 힘들어진다.

SPARC의 Register File Structure에 대해 먼저 알아보자.
SPARC의 레지스터는 총 128개의 Register로 구성되어 있다. (Global Register 8개는 따로 취급)
128개의 레지스터는 (8*2)*8로 구성된다. 이는 그림으로 보면, 아래와 같다.


CWP는 Current Window Point(현재 레지스터 집합)의 약자이고,
WIM은 Window Invalid Mask(마지막 가용 레지스터 집합)의 약자이다.
※ 위 그림에서 레지스터셋은 아래쪽으로 쌓인다.(위 그림은 Stack과는 아무련 연관이 없다.)

전에 언급했듯이 하나의 Register Set에는 in과 out, local 그리고 global이 있다.(global은 위에서 보시다시피 unmapped이기 때문에 단 하나의 셋만이 존재하며, mapped된 어느 레지스터 셋에서나 접근이 가능하다.

그림을 잘보면 전의 레지스터 셋의 out과 다음 레지스터 셋의 in이 겹치는 부분이 보일 것이다.
이 부분이 가장 중요한 부분으로
하나의 레지스터 셋의 out과 다음의 레지스터 셋의 in이 서로 연결되어 있다는 것이다.
어셈블리어에서 함수 실행시 새로운 스택 영역을 확보하면서 새로운 레지스터 셋을 사용하는데 그때, Register와 Register간에 통신, 매개변수전달을 바로 이 out과 in 레지스터를 이용하여 가능하다.


실제로 우리가 알고 있었던 in, out, local이라는 레지스터에서 out과 in은 표현하는 방법과 다를뿐이지, 실제로는 같은 공간을 사용한다.
엄밀히 말하자면 하나의 Register Set에서 in, local, out의 공간을 사용하는 것은 맞지만, 전체적인 Register Map을 보았을때는 전의 out과 후의 in을 공유(같은 메모리 공간을 사용)한다.
그렇기 때문에 Mapping되는 레지스터의 총 개수는 (3*8)*8=192 개가 아닌 (2*8)*8=128 개가 되는 것이다.
(물론 g레지스터는 제외한 수)


위 그림은 조금 지저분하기는 하지만 실제로 o Register와 i Register가 어떻게 Mapping되어 연결되는지 확실하게 보여준다. 위 그림에서 가장 확실히 알아두어야 할 것은 %o6인 %sp와 %i6인 fp가 서로 교환된다는 사실이다. 이부분은 이 포스팅의 후반부(Stack Frame)에 다시 언급할 것이다.

SPARC는 위와 같이 단지 0~7까지의 8개의 Mapping된 REGISTER SET을 사용한다.
이 8개의 레지스터 셋을 돌려서 사용하는데 문제점이 발생된다. 만약 8개 이상의 스택이 호출되어 9개 이상의 Register Set이 사용될 경우 어떻게 해야 할 것인가에 대한 것이다. 즉 CWP가 WIM를 넘어서면 어떻게 처리하느냐에 관한 문제이다.

그 상황을 Register Window OverFlow, UnderFlow라고 한다.
예를들면 첫번째 그림의 레지스터 0번 Set까지 사용하고나서 한번의 Stack의 호출이 생기면 다음 레지스터 셋을 사용하여야 한다. 이때 다음 레지스터가 없기 때문에 다시 초기의 7번 레지스터를 사용하게 된다.
이런식으로 환영으로 처음과 끝이 연결되어 연속으로 돌려가며 사용되게 된다.
단, 각 레지스터 Set을 돌려쓰기 위해서는 기존 레지스터를 다른곳에 저장해 두어야 하는데, 그 레지스터셋을 Stack에 저장한다. 이것은 Stack Spill이라고 한다. 확실히 알아두어야 할 것은 레지스터영역에 있는 변수를 메모리(Stack영역)에 옮겨 저장해 놓는 다는 사실이다.
스택의 메모리에 저장할때는 총 64byte의 공간이 사용된다.
레지스터 하나가 4byte(32bit)가 사용되는 점을 감안할때 총 16개의 레지스터가 저장되는 것이고, 16개의 레지스터는 즉 8개로 구성된 2개의 묶음을 말한다.
한마디로 Local Register와 Out Register(=In Register)가 저장되는 것이다.

SPARC Stack의 구조를 보면 스택이 쌓일 때마다 64byte의 기본 공간이 필수적으로 요구되는데, 그공간이 바로 이 Register를 위한 공간이다.
레지스터를 Stack과 연관되어 생각해야하기 때문에 이외로 헷갈린다. 하지만 Register Set과 Stack Structure는 분명히 다른 것이다.

SPARC Stack의 쌓이는 구조를 보면 다음과 같다.

 sp+n  sp  Register Window Saving Area (64 byte)
 Return Structure Point (4 byte)
 First 6 Parameters (24 byte)
 Rest of Parameters (as needed)
 Callee
 fp-n  sp  Locals (as needed)  Callee
 fp+n  fp  Register Window Saving Area (64 byte)
 Return Structure Point (4 byte)
 First 6 Parameters (24 byte)
 Rest of Parameters (as needed)
 Caller


처음 보면 이해가 안가는 것이 당연하지만, 계속보면 정말 도움되는 표가 될 것이다. 
먼저 Stack을 기준으로 Caller와 Callee가 구분된다.
Caller는 함수등을 호출하는 곳이고 Calle는 호출당하는 쪽이다. 즉 위에 쌓이는 부분이 Callee이다.

Stack구조를 생각할때는 어느쪽이 Memory 주소번지가 낮은지 확실히 하는 편이 나중에 편하다.
위의 표의 경우 위로 갈수록 낮은 메모리 번지이고, 아래로 갈수록 높은 메모리 번지이다.
SPARC의 메모리 구조는 높은 곳에서 낮은 곳으로 향한다.
Stack의 경우에도 위로 쌓일 수록 낮은 메모리 번지로 향한다.
그렇기 때문에 Save %sp, -96, %sp 같은 경우에도 -를 사용하여 아래 번지로 공간을 잡는 것이다.
fp를 이용하여 +로 접근 할 경우에는 반대로 fp를 기준으로 높은 메모리 번지로 올라가게 된다.

보통 제공되는 표나 그림을 보면 거의 아래 쪽이 높은 주소, 위쪽이 낮은 주소로 Stack이 아래쪽에서 위쪽으로 쌓여져 가는 방향으로 설명이 되어 있다. (가끔 반대로 된 표를 보면 필자도 조금 헷갈린다.)

생각을 많이 하다보면 fp와 sp에 대해서 정말 헷갈릴때가 많다. 필자도 개념을 잡느라 원서잡고 하루 낭비했다.
(좀 무식해서..읽다가 짜증나서 자버리기도 했음 -.-;;)

fp는 Frame Pointer의 약자이고, sp는 Stack Pointer의 약자이다.
sp의 경우 보통 코드등에서 공간을 할당하고 전체 크기를 조율하는데 이용되고, fp의 경우 크기가 할당전 sp의 값을 받아서 현재 쌓인 Stack의 처음 주소를 가리킨다.
그리고 이 sp와 fp를 통하여 메모리 접근이 가능해진다. 주로 자신이 할당한 주소접근은 fp와 -n 연산으로 접근한다.
(반드시 잊지 말아야 할 것은 sp와 fp는 각각 %o6, %i6 Register라는 사실이다. 메모리와 헷갈리지 말아야 한다.)

Stack을 할당하는 과정에 대해 이해하면 궁금증이 모두 풀린다.
그럼 그 과정을 위의 표와 같이 살펴보자.
위의 표는 현재 Stack에 2칸이 쌓인 모습이다.
명령어들을 보면 save라는 명령어를 본적이 있을 것이다.
save는 main이나 각 함수내에서 가장 먼저 호출되는 명령어 이다.
주로 [save %sp, -n&8, %sp] 의 형식으로 접할 것이다.
save가 하는 일은 다음과 같다.
1. Stack간의 Register를 Mapping시킨다. 즉 Caller와 Callee의 O와 i의 Register를 서로 연결시키는 역할을 한다. save가 수행되는 시점에서 mapping이 수행된다.
2. save나 restore에서는 ALU의 ADD기능이 포함되어 있다. 여기서 이 두개의 ADD명령어의 첫번째 Source인 %sp의 경우 Caller의 %sp를 말하고, 세번째의 %sp인 경우 Callee의 %sp를 말한다. 즉 save에 나오는 %sp 2개는 서로 완전히 다른 것이다. 그 의미는 전의 %sp에서 n만큼의 공간을 잡은 후 위의 Stack(Callee)의 공간에 속한 레지스터인 %sp에 그 값을 넣는 것이다. 그리고 전 Caller에서 존재하던 %sp의 값을 Callee의 %fp에 넣어준다.

이 과정은 %o Register가 다음에 쌓이는 Stack의 %i Register와 Mapping된다는 사실을 알면 이해가 쉽다.
%sp와 %fp또한 레지스터이기 때문에 전 스택(caller)의 %sp와 쌓이는 스택(caller)의 %fp와 연결된다는 사실은 자명한 일이다. (%sp=%o6, %fp=%i6)

결국 save 명령어도 공간을 할단하고 Register를 Mapping하는 동안 fp와 sp는 모두 한단계 위쪽으로 변화하게 된다.

stack이 위에서 삭제될때는 restore명령어가 사용되는 이데 이는 save명령어의 완전 반대라고 생각하면 된다.
restore의 경우 자동으로 쌓인 메모리가 반납되고, 동시에 Register Mapping도 일어난다.

위 표에서 sp는 스택의 가장 상단이고, fp는 두개의 쌓인 Stack의 가운데 지점을 가리킨다.
그리하여 우리가 가장 많이 쓸때 fp를 이용하여 가장 스택의 하단부에서 설정한 공간을 -를 이용하여 접근할 수 있다.
또한 fp +를 이용하여 바로 전층의 Stack의 메모리에도 접근하다.

표에서 fp+n, sp+n으로 표시한 부분은 Register Window Area 및 Structure Point, 및 기타 parameters에 접근하기 위해서이다. (참고로 Register Window Area는 포스트의 상단부에 언급했던 SPARC의 8개의 Register 돌려쓰기에 사용되는 메모리 공간이다.)

우리가 사용하는 함수의 인자전달에는 각 o와 i 레지스터를 통하여 전달하였는데, 매개변수가 더 많을 경우(6개 이상), fp를 이용한 접근으로 그 문제점을 충분히 해결할 수 있다.



fp, sp를 이용한 주소접근은 마치 C의 포인터와 같다. 이를 염두하여 여러번 실습하는 것이 중요한 것 같다.
fp와 sp의 이해가 있어야, 자유롭게 값을 가져다가 사용할 수 있게 된다.


※ 잡담
여담으로 Register Window가 8개(0~7)인 이유는
일단 8이 2의 승수 이고, SPARC 머신을 설계할때 가장 효율적인 수이기 때문이라고 한다.
Sun사에서 여러번의 실험을 했지만, 정해진 Register내에서 사용하는 것이 가장 효율적이기 때문이다. 스택이 무진장 쌓일 경우는 사실상 재귀함수일때고 그이외는 아주 많이 쓰이지 않고, 여러번의 시도 끝에 판단했을 것이라고 한다.

또한 main의 경우도 여러번 쌓인 stack중의 하나인데 아마 5~4번 Register set중 하나 일 것이라고 한다. 기본적을 시스템에 관한 전반적인 것들이 쌓이기 때문이다. 즉 main 맨 처음 쌓이는 것이 아니라는 것이다.~~

About SparcStack 도움되는 Site : http://www.sics.se/~psm/sparcstack.html