loongarch 基础指令
loongarch基础指令格式

R表示寄存器,I表示立即数
寄存器使用约定
- 整数寄存器
| Name | Alias | Meaning | Preserved across calls |
|---|---|---|---|
| $r0 | $zero | Zero register | Constant Zero |
| $r1 | $ra | Return Address | No |
| $r2 | $tp | Thread Local Cache | Non-allocatable |
| $r3 | $sp | Stack Pointer | Yes |
| $r4-$r11 | $a0-$a7 | Argument registers | No |
| $r4-$r5 | $v0-$v1 | Return value | No |
| $r12-$r20 | $t0-t8 | Temp Registers | No |
| $r21 | Reserved | Non-allocatable | |
| $r22 | $fp/$s9 | Frame pointer/Static Register | Yes |
| $r23-$31 | $s0-$s8 | Static Registers | Yes |
- 浮点寄存器
| Name | Alias | Meaning | Preserved across calls |
|---|---|---|---|
| $f0-$f7 | $fa0-$fa7 | Argument registers | No |
| $f0-$f1 | $fa0-$fa1 | Return Value registers | No |
| $f8-$f23 | $ft0-$ft15 | Temporary registers | No |
| $f24-$f31 | $fs0-$fs7 | Static registers | Yes |
caller-save和callee-save
caller-save和calle-save是相对于编译器abi而言的, 而caller-save是相对于callee-save而言的,所以得先搞懂什么是callee-save函数
callee也就是当前函数,所谓的callee-save寄存器指的是,如果callee在执行过程中修改了该寄存器的值,那么需要将该寄存器的值恢复到原来的值。那么为什么要恢复到原来的值呢?是因为原来的值虽然对于当前的函数是无用的,但是对于整个执行流(可以认为是一个协程,协程不存在的情况下是一个线程,就是函数栈得连续)是有用的,这里修改了别的函数咋办。典型的callee-save就是sp,tp寄存器,剩下的s1-s9都是编译器规定的,没有特别作用。
反过来而言还有一部分寄存器,callee可以随便修改它的值,也就是除过callee-save以及r21, $tp以外剩下的都是caller-save。然后就是了解这些有什么用?
再次强调caller-save和callee-save是编译器决定的,规定这些有什么用:
- 编译器在从c语言生成汇编的时候
a系列的寄存器用完就只能使用栈空间了,这是编译其生成汇编进行寄存器分配的准则之一。 - 用来指导手写汇编,我们在手写汇编
.S文件的时候,我们编写的函数要符合寄存器的使用规范,举个例子:你在自己写的汇编里面修改了sp指针的内容但是没有复原,就会导致实际执行的时候caller的sp指针乱掉
boost context
// https://github.com/boostorg/context/blob/develop/src/asm
/*******************************************************
* *
* ------------------------------------------------- *
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
* ------------------------------------------------- *
* | 0 | 8 | 16 | 24 | *
* ------------------------------------------------- *
* | FS0 | FS1 | FS2 | FS3 | *
* ------------------------------------------------- *
* ------------------------------------------------- *
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
* ------------------------------------------------- *
* | 32 | 40 | 48 | 56 | *
* ------------------------------------------------- *
* | FS4 | FS5 | FS6 | FS7 | *
* ------------------------------------------------- *
* ------------------------------------------------- *
* | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | *
* ------------------------------------------------- *
* | 64 | 72 | 80 | 88 | *
* ------------------------------------------------- *
* | S0 | S1 | S2 | S3 | *
* ------------------------------------------------- *
* ------------------------------------------------- *
* | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | *
* ------------------------------------------------- *
* | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | *
* ------------------------------------------------- *
* | S4 | S5 | S6 | S7 | *
* ------------------------------------------------- *
* ------------------------------------------------- *
* | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | *
* ------------------------------------------------- *
* | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | *
* ------------------------------------------------- *
* | S8 | FP | RA | PC | *
* ------------------------------------------------- *
* *
* *****************************************************/
.file "jump_loongarch64_sysv_elf_gas.S"
.text
.globl jump_fcontext
.align 2
.type jump_fcontext,@function
jump_fcontext:
# reserve space on stack
addi.d $sp, $sp, -160
# save fs0 - fs7
fst.d $fs0, $sp, 0
fst.d $fs1, $sp, 8
fst.d $fs2, $sp, 16
fst.d $fs3, $sp, 24
fst.d $fs4, $sp, 32
fst.d $fs5, $sp, 40
fst.d $fs6, $sp, 48
fst.d $fs7, $sp, 56
# save s0 - s8, fp, ra
st.d $s0, $sp, 64
st.d $s1, $sp, 72
st.d $s2, $sp, 80
st.d $s3, $sp, 88
st.d $s4, $sp, 96
st.d $s5, $sp, 104
st.d $s6, $sp, 112
st.d $s7, $sp, 120
st.d $s8, $sp, 128
st.d $fp, $sp, 136
st.d $ra, $sp, 144
# save RA as PC
st.d $ra, $sp, 152
# store SP (pointing to context-data) in A2
move $a2, $sp
# restore SP (pointing to context-data) from A0
move $sp, $a0
# load fs0 - fs7
fld.d $fs0, $sp, 0
fld.d $fs1, $sp, 8
fld.d $fs2, $sp, 16
fld.d $fs3, $sp, 24
fld.d $fs4, $sp, 32
fld.d $fs5, $sp, 40
fld.d $fs6, $sp, 48
fld.d $fs7, $sp, 56
#load s0 - s7
ld.d $s0, $sp, 64
ld.d $s1, $sp, 72
ld.d $s2, $sp, 80
ld.d $s3, $sp, 88
ld.d $s4, $sp, 96
ld.d $s5, $sp, 104
ld.d $s6, $sp, 112
ld.d $s7, $sp, 120
ld.d $s8, $sp, 128
ld.d $fp, $sp, 136
ld.d $ra, $sp, 144
# return transfer_t from jump
# pass transfer_t as first arg in context function
# a0 == FCTX, a1 == DATA
move $a0, $a2
# load PC
ld.d $a2, $sp, 152
# restore stack
addi.d $sp, $sp, 160
# jump to context
jr $a2
.size jump_fcontext, .-jump_fcontext
/* Mark that we don't need executable stack. */
.section .note.GNU-stack,"",%progbits
- 这段代码是boost-context中进行上下问切换的代码,上面的图是寄存器保存在栈当中的内存视图。
- 这里主要保存的就是
callee-saved寄存器,也就是static registers和ra
普通访存类指令
ld 指令
ld 指令的格式如下:
ld.{b/h/w/d}{u} rd, rj, si12
这里为64位系统,这里b/h/w/d分别表示1/2/4/8个字节,si12表示 ld.w指令执行的伪代码如下
# grlen = 64
# word_len = 32
vaddr = rj + signed_extend(si12, grlen)
# 内存对其检查
mem_compliance_check(vaddr)
paddr = addr_translate(vaddr)
word = mem_load(paddr, word_len)
rd = signed_extend(word, grlen)
ld.wu指令执行的伪代码如下:
vaddr = rj + sign_extend(si12, grlen)
mem_compliance_check(vaddr)
paddr = addr_translate(vaddr)
word = mem_load(paddr, word_len)
rd = zero_extend(word, grlen)
st 指令
ld 指令的格式如下:
st.{b/h/w/d} rd, rj, si12
st.w指令执行的伪代码如下
vaddr = rj + sign_extend(si12, grlen)
mem_compliance_check(vaddr)
paddr = addr_translate(vaddr)
// 0到31位
mem_store(rd[31:0], paddr, word_len)
ldptr
ldptr的指令格式如下
ldptr.{w/d} rd, rj, si14
// 这里是算术左移动
vaddr = rj + sign_extend(si14<<2, grlen)
mem_compliance_check(vaddr)
paddr = addr_translate(vaddr)
word = mem_load(paddr, word_len)
rd = sign_extend(word, grlen)
stptr
stptr.{w/d} rd, rj, si14
vaddr = rj + sign_extend(si14<<2, grlen)
mem_compliance_check(vaddr)
paddr = addr_translate(vaddr)
// 0到31位
mem_store(rd[31:0], paddr, word_len)
跳转指令
B, BEQ,BNE,BLT[U],BGE[U],BEQZ,BNEZ
| Name | Format | Meaning |
|---|---|---|
| b | b offs26 | pc += sign_extend(offs26<<2) |
| beq | beq rj, rd, offs16 | if rj==rd:<br> pc += sign_extend(offs16<<2) |
| bneq | bneq rj, rd, offs16 | if rj!=rd: pc += sign_extend(offs16<<2) |
| blt | blt rj, rd, offs16 | if rj<rd: pc += sign_extend(offs16<<2) |
| bge | bge rj, rd, offs16 | if rj>=rd: pc += sign_extend(offs16<<2) |
| bltu | bltu rj, rd, offs16 | if unsign(rj)<unsign(rd): pc += sign_extend(offs16<<2) |
| bgeu | bgeu rj, rd, offs16 | if unsign(rj)>=unsign(rd): pc += sign_extend(offs16<<2) |
| beqz | beqz rj, offs21 | if rj==0: pc += sign_extend(offs21<<2) |
| bnez | bne rj, offs21 | if rj!=0: pc += sign_extend(offs21<<2) |
BL, JIRL
| Name | Format | Meaning |
|---|---|---|
| bl | bl offs26 | ra = pc+4; pc += sign_extend(offs26<<2) |
| jirl | jirl rd, rj, offs16 | rd = pc+4; pc = rj +sign_extend(offs26<<2) |
杂项指令
nop指令
nop是一条伪指令,它的实现如下:
andi $r0,$r0,0x0
rdtime
rdtime.d rd, rj
龙芯指令系统定义了一个恒定频率的计时器,其主体是一个64位的计数器,称为Stable Counter。Stable Counter复位后置为0, 随后每个计数时钟周期自增1,当计数至全部为1的时候,自动绕回到0,继续自增。同时每个计时器有一个全局可配置的唯一编号Counter ID。
对于rdtime.d而言Stable Counter的值写入rd,Counter ID的值写入rj。
使用如下:
unsigned long result;
asm volatile ("rdtime.d %0,$r0" : "=r" (result));
return result;
syscall
syscall code
目前这个code是没有什么意义的,一般默认为0即可
// https://github.com/loongson-community/musl/blob/master/arch/loongarch64/syscall_arch.h#L99
static inline long __syscall6(long n, long a, long b, long c, long d, long e, long f)
{
register long a0 __asm__("$a0") = a;
register long a1 __asm__("$a1") = b;
register long a2 __asm__("$a2") = c;
register long a3 __asm__("$a3") = d;
register long a4 __asm__("$a4") = e;
register long a5 __asm__("$a5") = f;
register long a7 __asm__("$a7") = n;
__asm__ __volatile__ (
"syscall 0"
: "+&r"(a0)
: "r"(a7), "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a5)
: SYSCALL_CLOBBERLIST);
return a0;
}

浙公网安备 33010602011771号