1

この C++ 関数を MIPS アセンブリに変換する必要があります。

int set(int a[], int n, int v)
{
    int i;
    i = 0;
    do {
      a[i++] = v;
    } while ( i < n);
    return i;
}

ここで、配列のアドレスは in $a0、n は in $a1、v は in$a2です。これは MIPS で関数を記述しようとした私の試みですが、「命令が 0x00400054 で未定義のシンボルを参照しています」というメッセージが表示されます。メイン(私の教授によって提供されます)にはjal set、関数を呼び出すはずの呼び出しがありset、私のエラーがそれと関係があると確信しています。関数を正常に変換したかどうかもわかりません。これは私の試みです:

.text
set:
    li      $t0, 0
    addi    $t0, $t0, 0
    sll     $t1, $t0, 2
    add     $t1, $t1, $a0
L1: lw      $t1, 4($t1)
    sw      $a2, 0($t1)
    blt     $t0, $a1, L1

私は QTSPIM を使用しています。何かお役に立てれば幸いです。また、MIPS プログラミングに関するアドバイスがあれば、それも素晴らしいことです。

アップデート:

ファイルは現在リンクされていますが、「PC=0x004000f0 で例外が発生しました」と「データ/スタック読み取りのアドレスが正しくありません: 0x00000000」の無限ループが発生しています。これは私の更新されたファイルです:

.text
.globl set
set:    addi    $t0, $t0, 0     #i = 0;
         sll    $t1, $t0, 2     #offsets 4 * i
         add    $t1, $t1, $a0       #pointer to a[i]
L1:       lw    $t1, 4($t1)     #a[i++]
          sw    $a2, 0($t1)     #a[i++] = v
         blt    $t0, $a1, L1        #continue the loop as long as i < n

私のコードが にある必要があるのはなぜ.globlですか? そして、の目的は.text何ですか?

4

1 に答える 1

2

さて、いくつか問題がありました。

修正したコードは一番下にあります。C コードからの変換がどの程度リテラルである必要があるかに基づいて、実際には 2 つの方法があります。概念上の問題の一部は、2 つの方法のパーツを組み合わせようとしていたことかもしれません。

[重複として] コメント フィードバックに基づいて、元のコードの最初の 2 つの命令を 1 つにまとめました (例: lithen addi)。ただし、1 つだけを使用する場合は、レジスタをそれ自体に追加するliため正しいaddiですが、初期値がゼロであることに依存することはできません。

値がゼロのsllレジスタをシフトしているため、inst は何もしていません。

でロードt1するには、 [または]a0を使用しますadd $t1,$a0,0add $t1,$a0,$zero

役に立たlwなかった [C コードがロードされないaのに、なぜ asm を使用する必要があるのか​​?]。

しかし、ループがまだ正しく機能していなかったので、これを少し変更しました。

あなたの後に戻ることはなかったbltので、ループが機能したとしても、それは「世界の端から落ちる」でしょう. 呼び出される各asm ルーチン [ のように呼び出されるものjal set] には、明示的なreturn ステートメントが必要です。jr $ra

注: MIPS asm では、a*[引数レジスタ]はcalleeによって変更される可能性があるため、代わりにループします(つまり、callerはそれらが破棄されることを期待しています)。a0t1


とにかく、修正されたコードは次のとおりです [不要なスタイルのクリーンアップをご容赦ください]:

    .text

    .globl  set
set:
    li      $t0,0                   # i = 0
L1:
    sw      $a2,0($a0)              # a[i] = v
    add     $a0,$a0,4               # advance pointer
    add     $t0,$t0,1               # ++i
    blt     $t0,$a1,L1              # continue the loop as long as i < n
    jr      $ra                     # return

元の C 関数が次のようなものだったとします。

int
set(int *a, int n, int v)
{
    int *end;

    end = a + n;
    for (;  a < end;  ++a)
        *a = v;

    return n;
}

次に、これはより文字通りの翻訳になります。

    .text

    .globl  set
set:
    sll     $a1,$a1,2               # convert int count to byte length
    add     $a1,$a0,$a1             # end = a + length

L1:
    sw      $a2,0($a0)              # *a = v
    add     $a0,$a0,4               # ++a
    blt     $a0,$a1,L1              # continue the loop as long as a < end

    jr      $ra                     # return

IMO、どちらの方法も元の C 関数の許容可能な実装です。最初のものは、インデックス変数 [の概念] を保持するという点で、より文字通りですi。ただし、2 番目にない追加の命令があります。

オプティマイザーは、変換する C 関数に関係なく、おそらく同じコード (つまり、2 番目の asm) を生成します (MIPS には、x86asm のような強力なインデックス アドレッシング モードがありません)。

したがって、どちらが「正しい」かは、あなたの教授がどれだけ執着するかによって異なります。


補足: 2 つの例の間でスタイルが変更されていることに注意してください。つまり、コードの変更はさておき、明確にするためにいくつかの空白行を追加します。


完全を期すために、mainテスト時に作成した関数を次に示します。

    .data
arr:    .space  400                 # allocate more space than count

    .text

    .globl  main
main:
    la      $a0,arr                 # get array pointer
    li      $a1,10                  # get count
    li      $a2,3257                # value to store

    jal     set

    li      $v0,1                   # exit syscall number
    syscall

アップデート:

ループ内にある場合、?の前に行a[i++] = vを配置しますか?add $t0, $t0, 1sw $a2, 0($a0)

いいえ、C コードがa[++i] = v. おそらく、これを確認する最善の方法は、最初に C コードを単純化することです。

a[i++] = v実際には:

a[i] = v;
i += 1;

そして、a[++i] = v実際には:

i += 1;
a[i] = v;

現在、C コードの行と asm 命令の間には 1 対 1 の対応があります。

いつ使用しsllますか?私は例を読んでいsll $t1, $t0, 2ましたが、カウンターを使用して配列を通過するときに、人々が通常そうしていることに気付きました。

はい。私の2 番目の実装をよく見るとsll、そのように使用されています。また、元の C コードが与えられた場合でも、ループをコーディングする方法になります。

lwCコードが次のようなことを言った場合、私はを使用しint x = a[0]ますか?

はい、正確に。


asm のプロトタイプを作成するもう 1 つの方法は、C コードを「非常に馬鹿げた C」に変換することです。

つまりif、最も単純な形式のみです: if (x >= y) goto label. 立ち入りif (x < y) j = 10禁止です。

関数スコープ変数または関数引数変数はありません。レジスタ名であるグローバル変数のみです。

複雑な表現はありません。x = yx += y、またはのような単純なもののみx = y + z。したがって、a = b + c + d複雑すぎます。

レジスタ変数は、整数値とバイトポインターの両方として機能します。したがって、ポインターとして使用されているレジスターに追加するときは、バイト ポインターに追加するのと同じように、int配列をインクリメントするには、 を追加する必要があります4

バイト ポインターとポインターの実際の違いintは、ロード/ストア操作を行う場合にのみ問題になります: lw/swforintlb/sbfor byte。

したがって、「ダム」として再コーディングされた 2 番目の C 関数は次のとおりです。

// RETURNS: number of elements changed
int
set(void)
// a0 -- "a" (pointer to int array)
// a1 -- "n" (number of elements in "a")
// a2 -- "v" (value to set into "a")
{

    v0 = a1;                            // set return value

    a1 <<= 2;                           // convert int count to byte length
    a1 += a0;                           // point to one past end of array

L1:
    *(int *)a0 = a2;                    // store v at current array location
    a0 += 4;                            // point to next array element
    if (a0 < a1) goto L1;               // loop back until done

    return;
}

更新#2:

最初の実装では、2 番目の実装でのadd $a0, $a0, 4使用と同等sllですか?

そうではありません。覚えておくべき重要なことはCです。ポインターに1を追加する[またはインデックスを付ける]と、コンパイラーは、ポインターが定義されている型の を追加iするインクリメント/追加命令を生成します。sizeof

つまり、 forint *iptrを指定するとis 4iptr += 1が生成さadd $a0,$a0,4れます。 , があった場合、コンパイラはis 8を生成します。sizeof(int)double *dptrdptr += 1add $a0,$a0,8sizeof(double)

これは、C コンパイラが提供する強力な「便利さ」です。なぜなら、配列、ポインタ、インデックスを交換可能に使用できるからです。

asm では、C コンパイラが自動的に行うことを手動で行う必要があります。

次のことを考慮してください。配列内の要素数のカウントである値があり、それを と呼びますcount。ここで、配列が占めるバイト数を知りたいと思います。これを と呼びますlen。さまざまな型についてそれを決定するための C コードを次に示します。

char *arr;
len = count * sizeof(char);
len = count * 1;
len = count << 0;
//  sll $a1,$a1,0

short *arr;
len = count * sizeof(short);
len = count * 2;
len = count << 1;
//  sll $a1,$a1,1

int *arr;
len = count * sizeof(int);
len = count * 4;
len = count << 2;
//  sll $a1,$a1,2

double *arr;
len = count * sizeof(double);
len = count * 8;
len = count << 3;
//  sll $a1,$a1,3

私が理解していることから、sll セット i を int のカウンターとして使用すると、i がインクリメントされ、配列も反復されます

No.は単に MIPS の「論理左シフト」命令であり、C の演算子sllに相当するものが必要な場合に使用します。<<

あなたが考えているのは、その効果を達成するためにどのようsll に使用できるかということです。

の配列を反復処理するにはintインデックスを 1 増やしますが、配列ポインターも 4 増やします。これが、最初の asm の例で行ったことです。終了条件はindex >= count.

2 番目の asm の例では、要素数を ( を使用して) バイト長に変換sslし、配列アドレスを追加することで、個別のインデックス変数を削除しました。配列$a1の最後の要素のアドレス + 1 になり、終了条件はcurrent_address >= last_element_plus_1. current_address ( ) は 4 ( )$a0ずつインクリメントする必要があることに注意してください。add $a0,$a0,4

覚えておくべき重要なことの 1 つは、asm 命令 [特に MIPS] は単純である (つまり、dumb ) ということです。彼らは一度に 1 つのことしか行いません。ステートメントが十分に複雑な場合、1 つの C 代入ステートメントで約 20 の命令を生成できます。より複雑な結果を生成するのは、asm 命令がどのように組み合わされるかです。

于 2016-02-15T02:03:03.050 に答える