컴퓨터 구성 및 설계 Patterson & Hennessy Notes (1) MIPS 명령어 세트

컴퓨터 언어: 조립 설명서

그것이 명령어 세트입니다. 이 책은 주로 MIPS 명령 집합을 소개합니다.

조립 설명서

산술 연산:

add a,b,c	# a=b+c
sub a,b,c	# a=b-c

MIPS 어셈블리에 대한 설명은 # 기호입니다.

MIPS의 레지스터 크기는 기본 액세스 단위인 32비트이므로 워드 워드라고도 합니다. MIPS 어셈블리에는 32개의 레지스터가 있습니다. 레지스터의 수는 무어의 법칙이 많을수록 좋다는 말은 아니지만 명령어 세트의 실행 가능성과 관련이 있습니다.

다음으로 위의 추상 문자를 레지스터 표현으로 바꿉니다.

# f=(g+h)-(i+j)
# f,g,h,i,j in s0,s1,s2,s3,s4

add $t0,$s1,$s2
add $t1,$s3,$s4
sub $s0,$t0,$t1

MIPS 어셈블리의 레지스터는 $+ 두 문자로 표시됩니다.

그런 다음 메모리에서 데이터를 가져오는 명령이 있습니다.

# g=h+A[8]
# g,h in s1,s2
# A address in s3
lw $t0,8($s3)	# 用这种方式拿出来
add $s1,$s2,$t0

MIPS는 big-endian 주소 지정을 사용합니다. 즉, 상위 바이트가 하위 주소에 저장됩니다.

최하위 비트: MIPS[31:0]의 비트 0인 가장 오른쪽 비트.

최상위 비트: 31비트.

바이트로 주소 지정하는 경우 올바른 오프셋은 offset*4입니다.

예: A[12]=h+A[8]을 계산하면 h는 s2에 있고 A는 s3에 있습니다.

lw $t0,32($s3)
add $t0,$s2,$t0
sw $t0,48($s3)	# 写回

상수 연산: 예를 들어 상수 4는 s1+AddrConstant4에 저장되며 lw $t0,AddConstant4($s1)상수를 꺼낼 수 있습니다.

또는 즉치 값을 직접 사용하십시오.

addi $s3,$s3,4	#加立即数需要用 addi

즉각적인 작업 속도가 빠르고 에너지 소비가 적습니다.

$zero 레지스터는 상수 0을 저장합니다. 예를 들어 데이터 전송 명령은 데이터 전송 명령과 추가 명령의 조합을 단순화하는 0 추가 명령으로 간주될 수 있기 때문입니다.

사용 빈도에 따라 일부 상수를 정의하는 것은 확률이 높은 이벤트를 가속화하는 방법 중 하나입니다.

디지털 저장장치는 무부호와 보수부호로 표현되는 부호로 구분되며 구체적인 알고리즘은 자세히 설명하지 않겠습니다~

명령 구성

t0-t7은 레지스터 8-15이고 s0-s7은 레지스터 16-23입니다.

예를 들어, 명령어 add $t0,$s1,$s2기계어 표현(10진수)은 다음과 같습니다.

이미지-20230623165653673

시작 부분의 0 필드와 끝 부분의 32 필드는 추가 명령을 나타냅니다.

17 18은 두 개의 소스 피연산자 s1 s2입니다.

8은 대상 피연산자 t0입니다.

다섯 번째 필드는 사용되지 않으며 0으로 설정됩니다.

물론 기본 표현은 32비트 이진수입니다. MIPS 명령어는 모두 32비트입니다.

이미지-20230623170143372

op: 연산 코드.

rs rt: 두 개의 소스 피연산자 레지스터.

rd: 대상 피연산자 레지스터.

샴트: 변위.

funct: 함수 코드, addi와 같은 op의 변형.

그러나 이 명령어 형식의 결함은 길이가 충분하지 않은 경우가 있다는 것입니다.예를 들어 처리하려는 주소 또는 즉치 값을 5비트로 표현할 수 없습니다(5비트는 최종 분석에서 32개의 숫자만 표현할 수 있음).

따라서 유형 I 명령어의 도입(즉시 값에 사용되는 명령어. 위 명령어는 레지스터에 사용되는 R 유형 명령어임)

이미지-20230623170757377

마지막 큰 필드는 주소 오프셋 또는 즉시 값을 나타내는 데 사용됩니다.

둘 다 32비트 명령어이므로 복잡성이 크게 증가하지 않습니다. 그러나 컴퓨터는 어떤 것이 R 명령이고 어떤 것이 I 명령인지 어떻게 판단합니까? 특정 명령 형식은 다른 작업에 의해 판단됩니다.

1687511468734

예를 들어 lw는 rs와 rt가 레지스터이고 다음 비트는 모두 addr 값임을 의미합니다.

1687516329025

위의 예는 서로 다른 두 명령어에 대한 기계 코드를 보여줍니다. rd가 없으면 두 번째 소스 피연산자 레지스터 rt가 대신 사용됩니다.

왼쪽으로 이동:

sll $t2,$s0,4	# t2=s0<<4
and $t0,$t1,$t2	#t0=t1 & t2
or $t0,$t1,$t2	#t0=t1 | t2
nor $t0,$t1,$t2	#t0=~(t1 | t2), 其中一个是0的话相当于 not

이미지-20230623192547413

조건부 점프:

beq $s1,$s2,L1	#两者相等则跳转到L1处
bne $s1,$s2,L1

예를 들어:if(i==j)f=g+h;else f=g-h;

bne $s3,$s4,Else
add $s0,$s1,$s2
j Exit
Else:sub $s0,$s1,$s2
Exit:

루프 점프: 유사한 점프 비교 방법입니다.

Loop:	#循环体
bne $s0,$s1,Exit	#如果两者不等,跳出循环
j Loop
Exit:

미만 지시어:

slt $t0,$s3,$s4	#t0=1 if s3<s4
sltu $t0,$s3,$s4	#无符号数
slti $t0,$s3,10	#带立即数的比较

단순성 원칙으로 인해 "미만인 경우 점프" 명령이 없습니다. 물론 먼저 slt를 사용한 다음 beq를 사용하여 t0의 값을 판단할 수 있으므로 두 개의 간단한 명령이 더 효율적입니다.

프로세스(기능)

함수와 유사한 프로그램 실행 프로세스의 일부인 추상적인 개념입니다. 프로세스는 호출자에 대한 모든 정보를 알 필요는 없지만 프로세스를 완료하는 데 필요한 부분만 알면 됩니다.

여기에는 매개변수 전달, 프로세스에 대한 제어 전달, 프로세스가 반환된 후 프로세스의 반환 값을 얻기 위해 지정된 저장 영역 획득이 포함됩니다.

a0~a3은 들어오는 매개변수, v0과 v1은 함수의 반환 값, ra는 반환 시작점의 반환 주소 레지스터입니다.

jal: 점프 및 링크, 점프 및 반환 값을 ra에 저장합니다. 그런 다음 jr 명령을 사용하여 뒤로 이동합니다. jr은 후속 레지스터에 저장된 주소로의 무조건 점프입니다.

jr ra

함수를 호출하는 부분이 호출자입니다. 호출되는 함수는 피호출자입니다.

jal은 실제로 pc+4의 값을 ra에 저장합니다.

스택

예를 들어 함수에서 이 5개가 아닌 더 많은 레지스터를 사용해야 합니다.

먼저 원래 레지스터의 값을 스택에 푸시한 다음 해당 레지스터를 스택에 제공할 수 있습니다.

스택 sp는 여전히 높은 주소에서 낮은 주소로 증가합니다.

예를 들어 함수는 f=(g+h)-(i+j)의 합을 계산합니다. 전달된 4개의 매개변수는 a0-a3이고 f는 s0에 저장되며 프로세스의 두 추가 작업은 두 개의 임시 변수 t0 및 t1을 사용해야 합니다. 따라서 이 세 레지스터의 값을 스택에 저장해야 합니다.

푸시 코드:

addi $sp,$sp,-12
sw t0,8($sp)
sw t1,4($sp)
sw s0,0($sp)

연산 후 s0 레지스터의 값을 반환 값 레지스터로 넘겨야 함

add $v0,$s0,$zero

스택 코드:

lw t0,8($sp)
lw t1,4($sp)
lw s0,0($sp)
addi $sp,$sp,+12

그러나 실제로 MIPS는 s0-s7 계열 레지스터를 저장해야 하고 호출 수신자를 저장하고 복원해야 한다고 규정합니다. t0-t9 시리즈는 사용하지 않습니다.

중첩 프로시저

다른 프로세스를 호출하지 않는 프로세스를 리프 프로세스라고 합니다. 그러나 우리는 리프 프로시저로만 구성된 프로그램이 거의 없다는 것을 알고 있습니다.

리프가 아닌 프로세스는 예약해야 하는 모든 레지스터를 푸시해야 하고, 호출자는 a0-a3 매개변수 레지스터와 t0-t9 임시 레지스터를 저장하고 호출 수신자는 s0-s7 저장 레지스터와 ra 반환 주소를 저장합니다. 호출 수신자가 저장한 레지스터는 호출과 반환 시 동일한 값을 보장할 수 있지만 임시 레지스터와 파라미터 레지스터의 값은 반환 시 가변적입니다. 따라서 호출자가 유용하게 사용하기 위해 t-시리즈를 저장하는 경우 직접 저장해야 하며 호출 수신자가 저장하기를 기대하지 말고 유용하지 않으면 저장할 필요가 없습니다.

예를 들어 다음과 같이 C 언어로 표현되는 재귀 코드는 다음과 같습니다.

int fact(int n)
{
    
    
    if(n<1)return 1;
    else return n*fact(n-1);
}

MIPS 어셈블리 코드:

먼저 리프가 아닌 프로세스에서 사용하는 레지스터 목록을 가져옵니다.

  • 계산 단계는 비교적 간단하며 임시 레지스터를 저장할 필요가 없습니다.

  • n은 저장해야 하는 a0 매개변수입니다.

  • ra를 구해야 합니다.

  • s0-s7은 사용되지 않습니다.

즉, a0 ra만 저장하면 됩니다. 그런 다음 어디에 저장할지 판단합니까? 발신자 또는 수신자?

팩트 함수가 호출될 때마다 팩트 함수는 호출 수신자이므로 자체적으로 ra를 저장합니다. 사실이 다음 자기를 재귀적으로 호출하면 자신이 호출자가 되어 a0을 저장해야 합니다. 값이 변경되지 않았기 때문에)

fact:
	addi $sp,$sp,-8
	sw   $ra,4($sp)
	sw   $a0,0($sp)
	
	slti $t0,$a0,1		# 判断是否 <1
	beq  $t0,$zero,L1	# >=1 则准备进入下一层循环
	
	addi $v0,$zero,1
	addi $sp,$sp,8
	jr   $ra			# ra a0 值没变,这里把栈指针恢复一下,返回值赋值一下就结束函数了
	
L1:
	addi $a0,$a0,-1
	jal  fact			# 递归调用非叶过程
	
	lw   $ra,4($sp)
	lw   $a0,0($sp)
	addi $sp,$sp,8		# 恢复寄存器原值
	mul  $v0,$a0,$v0    # 乘上本轮递归的 n
	jal  $ra

보충: 전역 및 정적 변수는 정적입니다. 정적 변수는 정적 데이터 영역에 저장되며 프로세스 시작 및 종료 중에도 존재하지만 동적 변수는 프로세스 시작 및 종료 중에만 존재합니다. MIPS에는 정적 데이터 영역을 가리키는 전역 포인터 $gp가 있습니다.

재귀 프로세스의 오버헤드는 여전히 상대적으로 크므로 대신 반복을 사용하는 것이 더 좋을까요?

프로세스 프레임

일부 프로세스에서는 레지스터의 일부도 스택에 푸시되는데 이 레지스터와 로컬 변수 조각을 프로세스 프레임 또는 활성 레코드라고 합니다.

일부 MIPS 소프트웨어는 $fp 프레임 포인터와 $sp를 사용하여 어떤 부분이 프로세스 프레임인지 식별하고 일부 소프트웨어는 레지스터를 사용하여 프로세스 프레임의 포인터를 저장합니다.

프레임 포인터는 4개 이상의 매개변수를 저장할 수도 있으며 초과 부분은 프레임 포인터에 따라 메모리에서 해당 위치를 찾을 수 있습니다.

물론 매우 중요한 원칙은 프로세스가 반환되기 전에 이 부분을 빈 상태로 복원해야 한다는 것입니다.

1692197891107

코드 조각

힙에서 정적 변수 및 동적 데이터를 위한 공간을 제공합니다.

1692197983293

본문: 코드 스니펫.

정적 데이터: 전역 및 정적 변수.

동적: 동적 변수.

C 언어는 명시적인 malloc 및 free 함수를 통해 힙 공간을 할당하고 해제합니다. 단점은 수동으로 해제하는 것을 잊어버리면 쉽게 메모리 누수로 이어질 수 있으며, 해제가 일찍 이루어지면 댕글링 포인터가 생성되어 프로그램이 가리키고 싶지 않은 위치를 가리킬 것입니다. 그리고 Java는 자동으로 메모리를 할당하고 쓸모 없는 단위를 회수합니다 XD

이것은 MIPS에 저장된 레지스터 규칙입니다. 이는 대부분의 경우 8개의 레지스터와 10개의 스크래치 패드를 저장하는 것으로 충분하다는 것을 통계적으로 증명하기 때문에 가속화된 확률이 높은 이벤트로 간주됩니다.

이미지-20230816230725911

인간 컴퓨터 상호 작용

오늘날 대부분의 컴퓨터는 8비트 바이트를 사용하여 ASCII 코드라고도 하는 문자를 나타냅니다.

lb sb: 대상 레지스터의 맨 오른쪽 8비트에 1바이트만 읽고 씁니다.

문자는 일반적으로 문자열로 결합됩니다. 문자열의 길이를 표시하는 방법에는 세 가지가 있습니다: 1. 문자열의 첫 번째 문자는 길이입니다. 2. 별도의 변수를 사용하여 문자열의 길이를 저장합니다. 문자열.c 언어는 \0 플래그를 사용하는 체계 3을 사용합니다.

예를 들어 문자열 복사 구현의 경우 c 언어의 논리는 다음과 같습니다. \0이 나타날 때까지 약간의 복사 문자를 반복합니다.

타겟 및 소스 어레이가 $a0 $a1에 기반하고 i가 $s0에 저장되어 있다고 가정합니다.

strcpy:
	addi $sp,$sp,-4
	sw   $s0,0($sp)
	
	add  $s0,$zero,$zero	# i置0
    L1:
        add  $t1,$a1,$s0		# t1存放源数组的当前指针
        lbu  $t2,0($t1)			# 无符号字节读取
        add  $t3,$a0,$s0		# t1存放目标数组的当前指针
        sbu  $t2,0($t3)

        beq  $t2,$zero,L2		# 跳出结束复制

        addi $s0,$s0,1			# 这里和之前以字为单位做处理不同,我们是以字节为单位做处理,因此i++而不是i+4

    L2:
        lw   $s0,0($sp)
        addi $sp,$sp,4
        jr   $ra

Java는 보다 일반적인 유니코드(오늘날 대부분의 웹 페이지에서 사용되는 스키마)를 사용하여 문자를 저장하며 단위는 16비트입니다. MIPS는 lh sh lhu shu를 직접 사용하여 정확히 한 문자 길이의 반단어 길이를 읽고 쓸 수 있습니다. 따라서 자바 문자열은 c보다 두 배 많은 메모리를 차지하지만 문자열 작업이 더 빠릅니다.

Java는 단어를 사용하여 문자열의 총 길이를 저장합니다.

MIPS의 스택 주소는 워드 단위로 정렬해야 하므로 c의 한 문자는 8비트이므로 5문자가 있더라도 2워드 정렬에 8문자의 길이가 할당됩니다. Java의 하프워드도 유사하게 정렬 메커니즘이 필요합니다.

32비트 즉시

일반적인 즉치 숫자는 16비트이지만 때로는 더 긴 32비트가 필요합니다.

lui load upper 즉시 명령은 16비트 즉치 값을 레지스터의 상위 16비트에 복사할 수 있습니다.

이미지-20230816234205831

이런 식으로 예를 들어 32자리 숫자의 경우 먼저 처음 16비트를 레지스터에 루이한 다음 하위 16비트 ori를 삽입할 수 있습니다.

32비트 즉치 값을 임시로 저장하기 위해 MIPS에 특별한 $at 레지스터가 있습니다.

단, 32비트 즉시와 16비트 즉시 사용에 주의할 필요가 있는데, 예를 들어 가산의 상위 16비트와 논리 연산이 관여(16-비트의 상위 16비트 논리 연산은 즉시 비트는 모두 0으로 간주됩니다).

주소 지정

J 계열 점프 명령어는 6비트 opcode + 26비트 점프 주소이며 점프 범위는 2^26입니다.

조건 분기 b 계열 명령어도 비교할 레지스터를 저장하는 비트가 필요하기 때문에 6비트 opcode + 5비트 레지스터 1 + 5비트 레지스터 2 + 16비트 주소로 구성됩니다.

이 16비트 주소가 대상 주소를 나타내는 경우 점프할 수 있는 주소 범위는 2^16워드로 제한되며 프로그램의 전체 길이는 이 범위를 초과할 수 없으므로 너무 지루합니다.

이를 위해 채택한 솔루션은 16비트 주소를 오프셋 주소로 하고 점프 방식을 현재 기준 주소 + 16비트 오프셋 주소(1 부호 비트, 즉 ±2^15)로 한다. 대부분의 루프 명령 및 조건부 명령이 2^15 미만이므로(이는 속도 향상을 위한 높은 확률 이벤트이기도 함) 이 접근 방식으로 충분합니다. 이 방법을 PC 상대 주소 지정이라고 합니다.

또한 MIPS 주소는 워드로 정렬되어 있어 바이트 주소에 비해 주소 지정 범위가 4배 확장되어 예를 들어 j 시리즈의 주소 지정 범위는 2^28바이트 주소입니다.

그런데 PC주소는 32비트 아닌가요? 실제로 하위 28비트만 점프 명령으로 수정할 수 있습니다. 프로그램 크기가 2^28을 초과하면 레지스터 점프로 점프해야 합니다.

1692203076366

addi $s3,$s3,1b 시리즈는 상대 주소 지정이며 다음 명령어, 즉 80016 에서 80032의 종료로 점프하는 것, 즉 8+80016에 상대적입니다 .

j 시리즈는 직접 주소 지정, 20000*4=80000 점프입니다.

1692203253588

이것도 재미있는 생각인데, 생각해보면 조건부 분해는 일반 분해와 다르다는 것을 항상 느낍니다.

MIPS의 주소 지정 모드에는 일반적으로 다음과 같은 유형이 있습니다.

1692203396325

기본 주소 지정: 지정된 레지스터의 주소 + 오프셋 주소.

의사 직접 주소 지정: PC 상위 순서 및 26비트 형식 주소가 연결됩니다.

이 책의 MIPS는 32비트 주소 지정이지만 거의 모든 마이크로프로세서는 상위 호환이 가능한 64비트 주소 지정으로 확장할 수 있습니다.

병렬 및 동기 명령어

동기화 메커니즘은 데이터 경쟁을 방지하기 위해 작업을 병렬로 실행할 때 더 중요합니다.

여기서 운영 체제를 배우는 것과 매우 유사한 느낌입니다. 뮤텍스를 통해 데이터 집합에 대해 원자적 읽기 및 쓰기 작업을 수행합니다.

달성하기 위해 명령 쌍: 링크 액세스 + 조건부 저장소 ll+sc를 사용합니다.

again:	addi $t0,$zero,1		; 尝试上锁=1
	ll	$t1,0($s1)				; 获取 s1 初始值
	sc	$t0,0($s1)				; 保存 s1 值。如果发现 ll 获取值和 sc 保存值不同,t0 置零
	beq	$t0,$zero,again			; 如果 t0 又变成0了,执行失败,重新执行
	add $s4,$zero,$t1			; 做操作

이후 챕터는 더 확장됩니다.

번역 임원

1692205180908

초기에는 하드웨어의 저장 용량이 작았고 컴파일러의 효율성이 높지 않았으며 모두 어셈블리로 작성되었습니다.

어셈블러는 이동 명령과 같은 기계어의 일부 변형을 지원합니다. 사실 add $t0,$zero,$t0어셈블러도 번역할 수 있지만 이동 명령은 없습니다. 이러한 유형의 명령을 의사 명령이라고 합니다.

어셈블러는 기호 테이블을 호출합니다.

어셈블리 파일에 의해 생성된 개체 파일에는 다음이 포함됩니다.

  • 대상 파일 헤더: 대상 파일 구성, 크기, 위치 및 기타 정보를 설명합니다.
  • 코드 조각
  • 정적 데이터 세그먼트
  • 재배치 정보: 절대 주소에 의존하는 일부 지침 및 데이터.
  • 기호 테이블: 외부 참조와 같은 정의되지 않은 나머지 태그(예: 브랜치의 레이블 및 데이터 전송 지침은 참조할 테이블에 배치되고 테이블의 데이터는 쌍으로 레이블과 주소로 구성됨).
  • 디버그 정보.

링커는 개별 기계 언어 개체 파일을 실행 파일로 결합합니다. 관련된 주요 단계는 다음과 같습니다.

  • 파일의 재배치 정보와 심볼 테이블에 따라 각 파일의 이전 주소가 결합되어 새 주소가 됩니다. 각 파일을 별도의 오브젝트 파일로 컴파일한 다음 다시 수정하는 대신 실행 파일을 생성하고 처음부터 새 주소를 설정하는 것이 어떻습니까? 이 수정이 더 효율적이기 때문입니다.
  • 외부 링크를 구문 분석한 후 링커는 메모리에 있는 모든 모듈의 위치를 ​​결정하고 재배치된 절대 주소로 모듈을 나타냅니다. 절대 주소가 먼저 처리된 다음 나머지 상대 주소가 배치됩니다.

일반적으로 실행 파일과 개체 파일은 동일한 형식이지만 해결되지 않은 참조를 포함하지 않습니다(라이브러리 기능에 대한 링크와 같은 일부 외부 링크 제외).

예: 다음은 AB의 두 대상 파일 링크 및 업데이트된 주소 제공입니다.

1692270268963

  1. 외부 참조를 처리합니다. A는 XB를 참조하고 B는 YA를 참조합니다.
  2. 코드 세그먼트는 0x400000에서 시작하고 데이터 세그먼트는 0x10000000에서 시작하므로 A의 코드 세그먼트는 0x400000-0x400100(프로세스 A의 파일 헤더는 텍스트의 크기를 식별하며 0x400100은 사용되지 않음)이고 데이터 세그먼트는 0x10000000-0x10000020, B 다음에 코드 세그먼트는 0x400100-0x400300이고 데이터 세그먼트는 0x10000020-0x10000050입니다.
  3. 둘의 첫 번째 점프 지시는 상대방의 첫 번째 지시 위치로 점프하는 것이다. jal: Pseudo-direct addressing, jal 점프 주소는 상대방의 첫 번째 명령어의 주소, a는 400100으로 점프, b는 400000으로 점프. 또한 jal 점프 규칙은 가장 왼쪽 두 개를 버리는 것이다. 숫자(실제 점프는 기본 주소 + 4* jal), 두 개의 실제 점프 주소는 100040 및 100000입니다. 명령어는 점진적으로 증가합니다.
  4. gp의 초기 주소는 0x10008000이고, 액세스 데이터는 기본 주소 레지스터에 따라 달라집니다. 실제 가져오기 주소가 0x10000000을 가져오려면 오프셋은 0x8000이어야 합니다. 빅엔디안 데이터는 점점 줄어들고 있습니다 .

여기에 이미지 설명 삽입

실행 파일이 생성되면 로더가 와서 데이터 명령을 메모리에 넣습니다.

  1. 코드 세그먼트와 데이터 크기를 알기 위해 파일 헤더를 읽으십시오.
  2. 충분히 큰 본문과 데이터 공간을 만듭니다.
  3. 데이터 복사 명령;
  4. 기본 함수 매개변수는 스택 맨 위에 복사되고 스택 포인터는 NULL을 가리킵니다.
  5. 시작 루틴으로 이동하여 매개변수를 복사하고 프로그램의 주 함수를 호출합니다. 주 함수가 반환되면 종료를 호출하여 프로그램을 종료합니다.

위에서 언급한 연동 방식은 정적 연동 방식인데 단점은 라이브러리 기능을 업데이트해도 이전에 연동했던 라이브러리 기능이 바뀌지 않는다는 점이다. 또한 라이브러리의 모든 콘텐츠가 사용되지 않더라도 라이브러리가 완전히 로드되고 프로그램이 매우 커집니다.

동적 링크 DLL은 실행 중일 때 라이브러리를 링크하기 위한 것입니다. 처음에 동적 링크도 모든 라이브러리에 내용을 추가하고 후반 프로세스에서 링크된 DLL은 호출된 루틴만 링크합니다.

1692293369223

두 번째는 간접 점프를 피합니다.

자바 프로그램의 실행은 두 단계를 거칩니다.

  1. javac는 자바 언어를 클래스 바이너리 바이트 파일로 컴파일합니다.
  2. jvm은 바이트코드 파일을 항목별로 해석하고 번역합니다.

jvm의 효율성은 너무 낮고 JIT 인스턴트 컴파일러 지원은 나중에 자주 실행되는 코드 블록을 "핫 코드"로 식별하고 로컬 플랫폼과 관련된 기계 코드로 컴파일하고 최적화하여 효율성을 향상시킵니다.

프로젝트 예시: 정렬

이미지-20230818014711247

sll $t0,$a1,2
add $t1,$a0,$t0
lw  $t2,0($t1)
lw  $t0,4($t1)
sw  $t0,4($t1)
sw  $t2,0($t1)
jr ra

이미지-20230818014932306

루프 1:

move $s0,$zero
for1tst:
	slt  $t0,$s0,$a1
	beq  $t0,$zero,$exit1
	...
	for loop 2
	...
	addi $s0,$s0,1
	j    for1tst
exit1:

주기 2: 주기 1에서 s 레지스터를 건드리지 마십시오. t 레지스터를 마음대로 변경할 수 있습니다.

addi $s1,$s0,-1
for2snd:
	slti $t0,$s1,0
	bne  $t0,$zero,$exit2
	sll  $t1,$s1,2
	add  $t2,$t1,$a0
    lw   $t3,0($t2)
    lw   $t4,4($t2)
    slt  $t0,$t4,$t3
    beq  $t0,$zero,$exit2
    ...
    swap
    ...
    addi $s1,$s1,-1
    j for2snd
exit2:

교환 호출: 원래 매개변수 레지스터를 먼저 저장한 다음 매개변수 레지스터를 수정합니다.

# 传参
move $s2,$a0
move $s3,$a1
move $a0,$s2
move $a1,$s1

jal swap

마지막으로 병합하기 전에 처음과 끝에 소스 레지스터를 저장하고 변경하는 작업도 추가해야 합니다. 우리는 s0-s3을 사용했으므로 s0-s3과 ra를 저장합니다.

sort:
	addi $sp,$sp,-20
	sw   $ra,16($sp)
	sw   $s3,12($sp)
	sw   $s2,8($sp)
	sw   $s1,4($sp)
	sw   $s0,0($sp)
	
	move $s2,$a0
	move $s3,$a1 # 不管是否要调用,先存一下参数
	
	move $s0,$zero
    for1tst:
        slt  $t0,$s0,$a1
        beq  $t0,$zero,$exit1
        
        addi $s1,$s0,-1
        for2snd:
            slti $t0,$s1,0
            bne  $t0,$zero,$exit2
            sll  $t1,$s1,2
            add  $t2,$t1,$a0
            lw   $t3,0($t2)
            lw   $t4,4($t2)
            slt  $t0,$t4,$t3
            beq  $t0,$zero,$exit2
            
            move $a0,$s2
            move $a1,$s1
            jal swap
            
            addi $s1,$s1,-1
            j for2snd
        exit2:
            addi $s0,$s0,1
            j    for1tst
    exit1:
    
	lw   $ra,16($sp)
	lw   $s3,12($sp)
	lw   $s2,8($sp)
	lw   $s1,4($sp)
	lw   $s0,0($sp)
	addi $sp,$sp,20
	
	jr   $ra

스왑 함수를 호출하는 부분은 인라인으로 최적화할 수 있습니다. 즉, 점프 오버헤드를 줄이기 위해 함수를 호출하는 대신 스왑 연산을 직접 확장합니다. 그러나 코드의 양이 증가하기 때문에 캐시 미스율이 증가하면 손실이 이득보다 큽니다.

또한 실제로 $sp는 항상 4개의 매개변수 레지스터 -16을 보유합니다. c는 포인터 매개변수를 허용하는 가변 매개변수 vararg 옵션을 갖기 때문입니다.

배열과 포인터

배열은 배열의 기본 주소에 *4를 더한 아래 첨자이고 포인터는 배열 +=4의 기본 주소입니다.

다음 프로그램 예제는 배열 지우기의 두 가지 구현입니다.

1692341881231(1)

다른 명령어 세트와 비교

  1. ARM 아키텍처:
    • ARM(Advanced RISC Machines)은 모바일 장치, 임베디드 시스템, 임베디드 칩 및 마이크로컨트롤러에 널리 사용되는 RISC(축소 명령 세트 컴퓨터) 아키텍처입니다.
    • ARM 설계는 에너지 효율과 저전력 소비에 중점을 두므로 모바일 장치에서 탁월합니다.
    • ARM 아키텍처에는 ARMv7, ARMv8 등 여러 버전과 변형이 있으며 ARMv8은 64비트 실행 모드(AArch64)를 도입했습니다.
  2. x86 아키텍처:
    • x86 아키텍처는 주로 개인용 컴퓨터, 서버 및 데스크탑 시스템에 사용되는 CISC(Complex Instruction Set Computer) 아키텍처입니다.
    • x86 아키텍처의 대표적인 프로세서로는 Intel의 Pentium, Core i 시리즈, AMD의 Ryzen 시리즈가 있습니다.
    • x86 아키텍처는 광범위한 컴퓨팅 작업을 위한 성능 및 기능의 높은 유연성을 제공합니다.
  3. MIPS 아키텍처:
    • MIPS(Microprocessor without Interlocked Pipeline Stages)는 초창기에 워크스테이션과 임베디드 시스템에 널리 사용되었던 RISC(축소 명령 세트 컴퓨터) 아키텍처입니다.
    • MIPS 설계는 실행 효율성을 개선하기 위해 명령어 집합을 단순화하는 데 중점을 둡니다.
    • 일부 영역에서 사용이 점차 감소했지만 MIPS 아키텍처는 여전히 일부 임베디드, 네트워킹 장비 및 임베디드 컨트롤러에서 사용됩니다.

gpt로

예를 들어 arm의 비교 및 ​​조건 분기: MIPS는 비교 결과를 레지스터에 저장하고 ARM은 비교 결과를 음수, 0, 캐리, 오버플로를 포함하는 조건 코드로 설정합니다. 예를 들어, CMP 비교는 두 개의 레지스터 값을 빼고 결과는 조건 코드를 설정합니다.

이미지-20230818195828205

이런 식으로 arm은 두 배의 레지스터 수(16)를 갖지만 여전히 작업을 완료할 수 있으며 경우에 따라 조건 코드에 따라 실행할 필요가 없는 명령을 직접 건너뛰어 코드 공간을 절약하고 실행합니다 . 시간.

arm의 즉각적인 가치: 4+8의 확장된 형태.

이미지-20230818200350122

또한 두 번째 레지스터 매개변수에 대한 시프트 연산을 지원하며 동일한 수의 비트가 더 많은 데이터를 나타낼 수 있습니다.

arm은 또한 레지스터 그룹의 작업을 지원하고 16비트 마스크를 통해 로드/복사할 16비트 레지스터의 레지스터를 결정합니다.

x86의 단점은 주로 주소 지정 범위가 제한되고 csc가 효율성을 떨어뜨린다는 것입니다.

일반적으로 MIPS 명령어는 길이가 일정하고 균일하며, 속도 요구 사항을 보장하기 위해 32개의 레지스터만 사용되며, 확률이 높은 이벤트 등을 가속하여 명령어 구성을 최적화합니다.

추천

출처blog.csdn.net/jtwqwq/article/details/132369334