为 sw MIPS 汇编指令指定一个参数?

Specifying one argument for sw MIPS assembly instruction?

我正在为计算机体系结构课程学习 MIPS 汇编。对于本课程,class 使用的是 MARS。在将十个数字放入数组中进行分配时,我决定测试一些东西。我想看看我是否可以创建一个循环,自动将用户输入的整数压入堆栈。到目前为止,这是我的代码:

 # For loop initialization
 li $s0, 0  # Set $s0 equal to zero.
 li $sp, -40    # Reserve space for 10 integers on the stack.
 li $t0, 0  # temp var to increment stack offset.
 # Stores ten user inputted numbers onto the stack.
stackLoop:
  beq $s0, 10, doneStack
  li $v0, 51
  la $a0, prompt
  syscall
  sw $t0($sp)
       # Print out stack values.
       li $v0, 1
       lw [=10=]($sp)
       syscall      
  addi $t0, $t0, 4
  addi $s0, $s0, 1
  j stackLoop
doneStack:

当我到达 sw 指令时遇到问题,当我指定两个参数时抛出错误,因为 $t0($sp) 无效。但是,如上所示,仅指定一个参数似乎有效。在开始这个工作之前,我问过我的教授,他说这是不可能的。为什么只使用一个参数有效?我的结论是 sw 必须默认存储 $v0。但是,这并不能成为语法的借口。例如,键入 sw $v0, $t0($sp) 会在编译时引发错误。

同理,为什么用$0($sp)时lw会起作用?我猜 lw 默认加载到 $a0,这可以解释为什么 li $v0, 1 有效。但是,如果是这样,为什么 lw $a0, 0($sp) 会产生 4 的增量,即一个字中的字节数?它不会引用 0($sp) 处的数据,该数据在每次迭代时从堆栈中弹出吗?

我查看了一些文档,但所有文档都使用 sw 和 lw 的两个参数。我的 class 的教科书甚至没有提到我在上面所做的事情是可能的。为中篇小说风格道歉post,但我的好奇心被激起了。

MARS 使用非常 简单的解析器和更简单的分词器。
在分发的JAR中,源文件是可用的,看Javaclassmars.mips.instructions.InstructionSet看哭了
由于 MARS 的本质,这是完全可以接受的:一个 教育 模拟器。

标记器将文本分解为空格(但是,输入首先被分解为行)、逗号和括号。
因此这些语法都是等价的:

sw $t0($sp)
sw $t0 ($sp)
sw $t0, ($sp)

在 MIPS ISA 中 没有 默认寄存器 swlw (或任何其他指令)。

这对每条指令都是正确的,但是,尊重 RISC 的思想,只有 lwsw(以及兄弟)可以有括号(因为它们表示寻址模式)从而减轻问题(像 or $t0 $t0 $t1 这样的事情仍然是可能的)。

最后,sw $v0, $t0($sp) 在 MIPS 中不可编码。
sw它是一个I-type instruction,因此它有一个源寄存器(t),一个基址寄存器(s)和一个16位立即数位移(i):

101011 ttttt sssss iiiiiiiiiiiiiiii

TL;DR: sw $t0($sp) 只是一个 MARS 神器。

汇编语言不像 C、Java 等编程语言... - 您可以在其中使用一些自由语法来编写单个表达式。

汇编更像是机器指令的 "mnemonics"(名称),它们在 CPU 中硬连线,顺便说一下 CPU 的创建者如何设计晶体管布局芯片,以及它如何连接到计算机的其他部分。所以如果 CPU 被设计成有指令 ori , , 0x25,那么你可以在源代码中写它,汇编器会把它翻译成机器码,对于 MIPS CPU 就是字 0x34630025。当 MIPS 在内存中的地址 pc(程序计数器)处遇到这个特定字时,它将执行 ori , , 0x25 而不会执行其他任何内容。

你不能编码 ori , , 0x25 + 0x33(在将常量组装成简单的 0x58 之前不预先计算常量),没有允许将 0x25 + 0x33 编码为两个值的机器操作码应该在运行时添加。一个聪明的汇编器会让你写这个,并将其编译为 ori , , 0x58(IIRC MARS 不是那么聪明)。

所以你不能学习某种语法并构建指令,你必须按原样学习指令,由 CPU 供应商定义,并记住什么是可能的,什么是不是。汇编是一种从可读助记符到二进制机器代码的 1:1 翻译(尽管 MARS 汇编程序有许多 "pseudo instructions",它们不会转换为单个本机操作码,而是本机操作码链(通常为 2-3 个),模拟伪指令行为,因此它是一种编译器,尽管非常原始并且记录了所有可能的伪指令)。

这就是为什么 可能的 指令是有限的,您直接使用芯片上的 HW 晶体管,并且您只有 CPU设计师。如果你想创建一些新的指令,那么固定 CPUs 就很不走运了(尽管你可以使用其中一个 FPGA 芯片来创建你自己的内部逻辑,但那是完全不同的话题)。


关于 lw [=18=]($sp) - 这是无效的语法,它会编译,因为 MARS 汇编器不是世界上最好的软件,所以在我希望它更聪明一点的情况下它会失败(比如 li $t1,123+34 不起作用),并且在停止并报告错误会更好的情况下,它实际上会产生一些东西。

你的lw [=18=]($sp)汇编为lw [=21=], 0($sp),即它会猜测缺少彗差,缺少位移,然后整个指令只是space填充符,因为你可以存储到[=22=] 又名 $zero 任何你想要的东西(就像 lw 那样),但你将始终读回零。


运行 MARS,打开帮助 F1 并检查选项卡 "Basic Instructions" 和 "Extended (pseudo) Instructions",这些都是你可用的。不幸的是,用于描述它们的语法是示例式的,而不是数学式的,所以它有时看起来像是可用的,直到你发现它不是,很难。

现在关于 lw...帮助说 lw $t1,-100($t2)。如果您是经验丰富的 asm 开发人员,知道其他几个 CPU 的汇编,以及几个不同汇编器的语法,那么这是完全显而易见的。如果您是装配新手,那么我可以看出这是相当难以理解的。冗长的描述也无济于事。

但部分技巧是使用 "Operand Key for Example Instructions" 检查选项卡上方的绿色区域,让我们尝试利用此 lw...

$t1, $t2, $t3 any integer register
-100 signed 16-bit integer (-32768 to 32767)

Load & Store addressing mode -100($t2) sign-extended 16-bit integer added to contents of $t2

如您所见,没有lw $t1,$t3($t2)(这意味着您可以在位移位置上使用寄存器)。

那么如何解释那个帮助:lw是"load word",这是一个基本指令,它有两个操作数。

左操作数是目标寄存器(可以是32位GPR(通用寄存器)中的任何一个,即[=22=]</code>,或者它们的别名如<code>$at$t0, etc... - 这是将从内存中获取的单词值存储的地方。

右操作数的形式为"displacement_constant($GPR)",作为该GPR的内容与displacement_constant相加,用于寻址内存计算。 IE。 -100($sp) 会取寄存器 $sp 中的值并从中减去 100,这将用作内存地址来联系内存芯片,并从那里获取字值。

这意味着在 MIPS 上,您只能通过单个寄存器间接地使用 lw 寻址内存,不允许像 $t0 + $t2 这样的数学表达式。为此,您必须先进行计算,例如:

    add $at, $t0, $t2    # don't use $at unless you know what you are doing
    lw  $a0,0($at)       # as 99% of pseudo ins. will use $at for their temporaries

实际上在你编译你的代码之后,你可以在 MARS 中看到反汇编的机器代码(这就是我如何弄清楚 ori 示例使用了什么操作码,以及 MARS 产生了什么样的憎恶来自无效 lw [=18=]($sp)) - 在 "Execute" 选项卡中(我只是不确定我是否必须对它进行一些配置以显示所有内容,包括如何将伪指令转换为基本指令,但不能在设置中找到任何关于它的信息,所以我们希望视图是默认的)。