📕Nest Procedures
📗Leaf 프로시저는 다른 함수를 호출하지 않지만, nonleaf 프로시저는 다른 함수를 호출한다.
📗중첩된 호출에서, Caller는 stack에 밑의 예시와 같은 것들을 저장해야 한다.
- Return address
- Any arguments and temporaries needed after the call
📗호출 후 스택에서 Restore 한다.
📗한 가지 해결책은 스택에 보존되어야 하는 다른 모든 레지스터를 푸시하는 것이다.
- Caller pushes x10~x17 (i.e,, argument registers), x5~x7 and x28~x31
- Callee does x1 (i.e., return address register), x8~x9, and x18~x27
📕Compiling a Recursive Procedure
📗Factorial
- fact 프로시저 안에서 사용될 item을 stack에다가 할당한다.
- return address -> x1
- n -> x10
- x5 레지스터에 n-1 한 값을 넣음.
- x5와 0을 비교해서 만약 x5가 크다면 else문[L1]으로 이동해야 함.
- 작다면 x10에다가 1을 더함.
- 사용한 스택 포인터들을 다 반환함.
- return address로 돌아감.
- L1
- x10에다가 n-1을 저장함.
- fact(n-1)을 호출함.
- 그다음 실행은 fact(n-1)이 끝났고 그 값을 x6 레지스터에 넣는다.
- n과 return address를 다시 꺼내고 스택 포인터를 쓴 만큼 다시 반환한다.
- x10에 x10(n)*x6(fact(n-1))을 넣는다.
- return address로 돌아간다.
📕Calling Convention (Revisited)
Preserved | Not preserved |
Save register : x8~x9, x18~x27 | Temporary registers : x5~x7 , x28~x31 |
Stack pointer register : x2(sp) | Argument/result registers : x10~x17 |
Frame pointer : x8(fp) | |
Return address : x1(ra) | |
Stack above the stack pointer | Stack below the stack pointer |
📕Allocating Space for New Data on the Stack
📗스택은 또한 로컬 배열 및 구조와 같은 프로시저에 로컬 변수를 저장하는 데 사용됩니다.
📗저장된 레지스터와 로컬 변수를 포함하는 스택의 세그먼트를 프로시저 프레임 또는 활성화 레코드라고 합니다.
📗프레임 포인터(fp 또는 x8)는 프로시저 프레임의 첫 번째 word를 가리킵니다.
- Stack pointer는 프로시저 내에서 변경될 수 있습니다.
- Frame pointer는 로컬 메모리 참조를 위한 절차 내에서 안정적인 기본 레지스터를 제공합니다.
📕Allocating Space for New Data on the Heap
📗메모리 할당을 위한 RISC-V 규칙
- Text : RISC-V machine code의 홈
- Static data : 상수와 다른 정적 변수들을 위한 공간
- Heap(Dynamic data) : 동적 할당된 메모리를 위한 공간
- Stack : 지역변수와 레지스터가 저장을 위한 장소
- 충돌 전에 스토리지 사용을 극대화하기 위해 스택과 힙이 서로 확장됩니다.
- heap은 위로 확장, stack은 아래로 확장
📕Wide Immediate Operands
📗Constants는 종종 짧고 12비트 필드에 적합하지만 때로는 더 큽니다. [addi로는 12bit 이상 불가능]
- RISC-V 명령 집합은 레지스터의 비트 12에서 31까지 20비트 상수를 로드하기 위한 명령 로드 즉시(lui: U-type)를 포함합니다.
- 맨 오른쪽의 12비트는 0으로 채워집니다.
📗example
💡즉 lui 12~31 bit를 채우고 addi는 0부터 채운다.라고 생각하면 편할 듯!
📕Addressing in Branches
📗RISC-V branch 인스트럭션은 SB-type 형식을 사용한다.
- 이 형식은 4096~4094까지의 branch address를 나타낼 수 있다. 그 branch address는 2의 배수로만 표현 가능하다.
📗example
[opcode : 1100011, funct3 : code 001)
우리가 점프를 하고자 하는 주소인 2000을 나눠서 저장한다.
하지만 0111 1101 0000이 아닌 0111 1010 0000이 저장되는 걸까?
위에서 주소의 형태는 항상 짝수라는 것을 배웠다.
그렇다면 0번째에 있는 숫자는 당연하게 0일 것이다. [1이라면 무조건 홀수가 될 것이다.]
그래서 상위 bit에 있는 수를 imm [12] 자리에 쓰는 것이다.
2000이면 13번째 bit에는 0이 저장되어 있기에 imm [12]에는 0이 오는 것이다.
이렇게 되면 우리는 13bit를 사용할 수 있다.
📗The unconditional jump-and-link(jal)은 UJ-type 형식을 사용하는 인스트럭션이다.
- 20-bit address immediate -> branch 보다 범위가 크다.
📗Example
[opcode : 1101111 for jal]
UJ-type도 SB-type가 동일하게 주소가 무조건 짝수이기에 0번 비트를 검사하지 않는 대신 상위 bit를 하나 더 검사해서,
총 21bit를 사용할 수 있다.
📕PC-relative Addressing
📗만약 프로그램의 주소가 20비트 필드에 맞아야 한다면, 그것은 어떤 프로그램도 2^20보다 클 수 없다는 것을 의미할 것이다.
- 이러한 주소의 제한은 현대에서는 터무니없이 작다.
- 이러한 상황의 대안은 branch instruction이 다음을 수행할 수 있도록 브랜치 오프셋에 항상 추가되는 레지스터를 특정하는 것이다.
💡Program counter = Register + Branch offset
📗대부분의 branch 대상은 branch 근처에 있다. [앞이든 뒤든]
📗PC(프로그램 카운터)는 branch의 기본 레지스터를 위한 이상적인 선택입니다.
- PC는 -2^10 ~ + 2^10 범위를 branch 하게 하고, -2^18 ~ +2^18 범위를 jump 하게 한다.
- 이러한 형태의 branch addressing은 PC-relative addressing이라고 불린다.
📗RISC - V는 32-bit address 같은 매우 넓은 범위의 jump를 허락한다.
- lui는 주소의 12~31비트를 임시 레지스터에 쓴다.
- jalr는 주소의 하위 12비트를 레지스터에 추가하고 합으로 점프한다.
📕RISC - V Addressing Mode Summary
📗Multiple forms of addressing are generally called addressing mode
- immediate addressing : Operand가 constant이면서 인스트럭션 내부에 존재함.
- Register addressing : Operand가 register임.
- Base or displacement addressing : Operand는 memory 안에 있고, 그 주소는 register와 인스트럭션 안에 있는 constant의 합이다.
- PC-relative addressing : branch address가 PC와 인스트럭션 내부에 있는 constant의 합
📕Decoding Machine Language
위 두 사진을 이용해서
Instruction 0x00578833을 어셈블리어로 바꿔보자~!
1) 2진수로 전환
0000 0000 0101 0111 1000 1000 0011 0011
2) Opcode는 7bit를 차지하는데 Opcode를 추적해보면 R-type이라는 것을 알 수 있다.
0000 0000 0101 0111 1000 1000 0011 0011
3) R-type format에 맞게 인스트럭션을 구성한다.
funct7 | rs2 | rs1 | funct3 | rd | opcode |
0000000 | 00101 | 01111 | 000 | 10000 | 0110011 |
💻opcode rd , rd1, rs2 ==> add x16, x15, x5
📕C code가 컴퓨터에 실행되게 만드는 과정은 4가지다.
각 과정에서의 자세한 내용은 밑에서 설명할 예정
📕Compiler and Assembler
📗컴파일러는 C program을 어셈블리 언어 프로그램으로 바꾼다.
-> 이 어셈블리 언어의 형태는 기계가 이해할 수 있는 형태이다. [기존 C code는 이해하지 못함.]
📗어셈블러는 어셈블리 프로그램을 오브젝트 파일로 바꿉니다.
- 오브젝트 파일은 기계어 명령어, 데이터, 그리고 명령어를 메모리에 적절하게 배치하는데 필요한 정보의 조합이다.
- 오브젝트 파일의 구성
- Object header : 오브젝트 파일의 다른 조각의 크기와 위치
- Text segment : 기계 언어 코드
- Data segment : 정적, 동적 데이터
- Relocation information : 프로그램이 메모리에 로드될 때 절대 주소에 의존하는 명령어와 데이터 단어 식별한다.
- Symbol table : list of labels
- Debugging information
- 오브젝트 파일의 구성
📗어셈블러는 의사 명령이라고 불리는 기계 언어 명령의 일반적인 변형을 처리할 수 있다.
- mv x10, x11 // 의사 명령으로는 데이터를 이동하는 명령 -> x10 = x11
- 이것은 기계적 인스트럭션으로 바꾸면 addi x10, x11, 0이다.
📕Linker and Loader
📗링커는 여러 object를 결합해서 실행 가능하게 만든다.
- 코드 및 데이터 모듈을 메모리에 배치
- 데이터의 주소와 인스트럭션의 라벨을 결정한다.
- 내부 및 외부 참조를 Patch
📗컴파일러와 링커의 분리는 C 코드의 한 줄을 변경하더라도 전체 프로그램을 컴파일하고 조립하는 것을 피할 수 있게 해 준다.
- Standard library routines에 도움이 된다. (e.g., stdio)
📗로더는 디스크의 실행파일을 읽고 메모리로 읽고 시작합니다.
- 실행 파일 헤더를 읽고 텍스트와 데이터의 부분의 크기를 결정한다.
- 텍스트와 데이터가 들어가기에 충분한 주소 공간을 만든다.
- 메모리에 있는 실행 파일로부터 데이터와 인스트럭션을 복사한다.
- 매개변수를 스택에 있는 메인 프로그램에 복사한다.
- 프로세서 레지스터를 초기화하고 스택 포인터를 첫 번째 빈 위치로 설정합니다.
- 파라미터를 argument 레지스터에 복사하고 프로그램의 main 루틴을 호출하는 start-up rountine에 branch 합니다.
- main 루틴이 리턴될 때, start-up routine은 프로그램을 종료한다.
📕Array Vs Pointers
📗어레이 인덱싱에는 요소 크기별로 인덱스를 곱하고 어레이 기본 주소에 추가하는 작업이 포함됩니다.
📗포인터는 메모리 주소에 직접 대응하므로 인덱싱 복잡성을 줄일 수 있습니다.
📗사실, 현대의 최적화 컴파일러는 포인터 버전만큼 좋은 배열 버전을 위한 코드를 생성할 수 있다.
'Computer Science > Computer Architecture' 카테고리의 다른 글
[Computer Architecutre] - floating point (0) | 2022.10.20 |
---|---|
[computer archiecture] - arithmetic_for_computers(1) (0) | 2022.10.19 |
[computer architecture]-instructions_language_of_computer(2) (0) | 2022.10.09 |
[computer architecture] - instructions- language_of_computer(1) (1) | 2022.10.08 |
[computer architecture] -Computer Abstractions and Technology (2) (0) | 2022.09.29 |