Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

誤記等報告 #36

Open
hirooih opened this issue Apr 13, 2023 · 0 comments
Open

誤記等報告 #36

hirooih opened this issue Apr 13, 2023 · 0 comments

Comments

@hirooih
Copy link

hirooih commented Apr 13, 2023

以前読みながら気がついたことをメモしたものです。そのまま放置していましたが、整理して共有させていただきます。

誤りとは言えない修正不要な些細なものもありますが、参考なればと思い、それらも含めて報告させていただきます。

グローバル変数に関しては、別の issue ticket を立てます。


P.34 big-endian

RISC-Vの使用でも現在のところリトルエンディアンに従います。

(本書が出版された2022年7月より約2年半前の) 2019/12/13 の版の RISC-V spec から big-endianもsupport されている。

Preface

...

  • Defined big-endian variant.

P.48 で 2019/06/08 の版を参照していると明記されているので、誤りではないが、本書を読んで誤解する方を減らしたいという意図。


P.90 LLVM IR

ここで LLVM IR が出てくるが、生成方法が説明されるのは P.100。PCで確認しながら読んでいて、躓いた。

clang -emit-llvm -c add.c

P.96 LLVMのビルド

メモリ16GB の Ubuntu PCでもmemory不足で fail しました。

Issue #15 で回答されている

  • DCMAKE_BUILD_TYPE="Debug" としているものを -DCMAKE_BUILD_TYPE="Release" に変更する
  • ninja -j1 を指定する

を本サポートページの 「LLVMビルド時の推奨オプション」 に記載すると、読者の方々の助けになると思います。

参考: 最初から ninja-j1 を指定すると効率が悪いので、まず -k 0 option をつけて、並列 build できるものは build した後に、-j1 を指定すると大幅に時短できます。

また、同ページの 「macOSでのビルド方法補足」 の Command Line Tools for XCode の install に加えて以下が必要でした。

brew install make
brew install ninja

P.110 図5

図の右側: %a %b %c -> %add %a %b


P.141: MYRISCVXCallingConv.td

- def CSR_LP32 : CalleeSavedRegs<(add RA, SP, FP, S1, (sequence "S%u", 2, 11))>;
+ def CSR_LP32 : CalleeSavedRegs<(add RA, SP, FP, (sequence "S%u", 1, 11))>;

P.152 にも同様な記述

- def CSR_LP32 : CalleeSavedRegs<(add RA, SP, FP, S1, (sequence "S%u", 2, 11))>;
+ def CSR_LP32 : CalleeSavedRegs<(add RA, SP, FP, (sequence "S%u", 1, 11))>;

P.142

... なお、... llvm/lib/Target/MYRISCVZX/ 以下のパスを示しています。

ここで build/ 以下のファイルは LLVM が生成するものであることも説明が欲しかった。
(LLVM経験者には自明のことかと思われるが、知らないと、ファイルを探しても見つからず苦労しました。)


P.152 MYRISCVXRegisterInfo.cpp : getCallPreservedMask() コメント

CSR_LP32のリストを... → CSR_LP32のビットマスク (CSR_LP32_RegMask) を...


P.152 getReservedRegs()

Reserved register に ra が含まれています。一方、

https://github.com/llvm/llvm-project/blob/main/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp#L84

では ra が含まれていません。

他の sp などは「システムが使用し」ているのに対して、 ra はcompilerの制御下にあるので、使用可能だからではと考えますが、いかがでしょうか?


P.175 total_64bit()

この例は 「 c + d の計算結果を (桁上がりを捨てて) int型にした後に、符号拡張」する例。(読者が正しく理解していれば、これで問題ない。しかし関数名に惑わされて、誤って理解することを懸念。)

c + d の結果を long long (64bit) で得る場合は以下となる。

long long total_64bit(int a, int b, int c, int d) {
    return (long long)c + (long long)d;
}
total_64bit(int, int, int, int):                    # @total_64bit(int, int, int, int)
        srai    a1, a2, 31
        srai    a4, a3, 31
        add     a0, a3, a2
        // `sltu` (Set Less Than Unsigned) で桁上がりがあった時 `a2` に 1 を set。
        sltu    a2, a0, a3
        add     a1, a1, a4
        add     a1, a1, a2
        ret

一方ここではRV32で64bitの値を返す例を示している。その目的では、以下でも十分。

long long return_64bit(int a) {
    return (long long)a;
}
return_64bit(int):                      # @return_64bit(int)
        srai    a1, a0, 31
        ret

P.176

...スタックの動作に似ている...

疑問: ここでの「スタック」はどのような物を指しているのか?
(私には (ここで説明している) softwareのスタックしか思い当たらない。)


P.202 図17

図中で本文で説明している「0x800を加算した上で12bit右シフトを行っ」ていることを明確に表現することが望ましい。

0x01235 -> 0x01234 + 1


P.203

... RISC-Vアーキテクチャでも同様に定義されており ...

厳密には 「...RISC-V ABI で...」。

(C拡張を除いて) RISC-V ISA では x0 以外に特別な意味を持たせていない。 (RISC-V ISAの美しい特徴の一つ)


P.276 図7

上の箱から順に

誤: 1. tmp = 0x12345678abcを生成
正: 1. tmp = (0x123456789abc + 1)を生成

誤: 1. tmp = 0x123456789を生成
正: 1. tmp = (0x123456789 + 1)を生成

誤: 1. tmp = 0x0123を生成
     2. (tmp << 12) と0x456を結合
正: 1. tmp = 0x0123456を生成
     2. (tmp << 12) と0x789を結合

誤: 1. tmp = 0x0123を生成
正: 1. tmp = 0x0123を生成
     2. (tmp << 12) と0x456を結合

参考: LLVM 14以降で64bit immediateの生成方法が変わっている。

Compiler Explorer で P.279の long_value() 相当を以下を試した結果 (option: -O)

long long long_value() {
    return 0x0123456789abcdefLL;
}

RISC-V rv64gc clang 13.0.1 以前 : 4*8 = 32byte (本書で説明されている例と同じ結果)

long_value():                        # @long_value()
        lui     a0, 146
        addiw   a0, a0, -1493
        slli    a0, a0, 12
        addi    a0, a0, 965
        slli    a0, a0, 13
        addi    a0, a0, -1347
        slli    a0, a0, 12
        addi    a0, a0, -529
        ret

RISC-V rv64gc clang 14.0.0 以降, もしくは gcc 8.2.0 (このサイトでの最も古いversion) 以降: 8+4*2 = 16byte

.LCPI0_0:
        .quad   81985529216486895               # 0x123456789abcdef
long_value():                        # @long_value()
        lui     a0, %hi(.LCPI0_0)
        ld      a0, %lo(.LCPI0_0)(a0)
        ret

P.285

unsigned の比較のテストがない


P.304 __global_pointer$

本書に global pointer の説明がない。

cf. RISC-V ELF Specification, Global-pointer Relaxation 参照


図22と図23、それぞれの位置付けが分かりづらい。

修正案:

図23: PICポリシを使った外部関数のアドレス解決とジャンプ

に合わせて、図22の見出しを

図22: PICポリシを使った外部変数のアドレス解決とアクセス

などとする。


P.317 typo

PLT: Proceduce Lookup Table -> Procedure Linkage Table

cf. RISC-V ELF Specification, Procedure Linkage Table


P.319 Disassembly of section .got:

# これから8バイトの値が...

6バイト分しか示されていない。


P.319 Disassembly of section .plt:

このコードが最初理解できなかった。

RISC-V ELF Specification, Procedure Linkage Table に同じのコードがコメント付きで記載されており、本書の解説と合わせてようやく理解できた。

以下はさらに本署の例の値をコメントに加えたもの。

.got.plt: PLT 0x10420
    # GOT
    # input:
    #   t1 = update_global@.plt + 12   = 0x10450+12
    #   t3 = *(update_global@.got.plt) = 0x10420
1:  auipc  t2, %pcrel_hi(.got.plt)
    sub    t1, t1, t3               # shifted .got.plt offset + hdr size + 12
                                    # t1 = t1 - t3 = 0x10450+12 - 0x10420 = 0x30 + 12
    l[w|d] t3, %pcrel_lo(1b)(t2)    # _dl_runtime_resolve (in the first entry of GOT)
                                    # t3 = *(0x12000) (_dl_runtime_resolve)
    addi   t1, t1, -(hdr size + 12) # shifted .got.plt offset
                                    # t1 = 0x30+12 -(hdr size + 12) = 0x10
    addi   t0, t2, %pcrel_lo(1b)    # &.got.plt
                                    # t0 = 0x12000
    srli   t1, t1, log2(16/PTRSIZE) # .got.plt offset
                                    # t1 = 0x10 >> log2(16/8) = 0x8
    l[w|d] t0, PTRSIZE(t0)          # link map
                                    # t0 = *(0x12000 + 8) (link map)
    jr     t3                       # jump to _dl_runtime_resolve(*(0x12000))
                                    #   t0: link map (*(0x12008)), t1: GOT offset (0x8)

__libc_start_main@.plt: 0x10440

update_global@.plt: 0x10450
1:  auipc   t3, %pcrel_hi(update_global@.got.plt)
    l[w|d]  t3, %pcrel_lo(1b)(t3)   # t3 = 0x10420 on the first call
    jalr    t1, t3
    nop                             # <- t1

...

.got.plt : GOT
0x12000: _dl_runtime_resolve
0x12008: link map
0x12010: __libc_start_main@.got.plt
0x12018: update_global@.got.plt

gdb で値を確認しようと試みたのですが、これらの code で step 実行することができませんでした。
やり方をご存知の方は教えてください。


P.321 medlow コードモデル

生成できる最大アドレス: ... 0x8000_0000

生成できる最小アドレス: ... 0x8000_0000 - 1

  • 単に「最小アドレス」と書くのであれば 0x0、「最大アドレス」は 0xFFFF_FFFF (RV32), 0xFFFF_FFFF_FFFF_FFFF (RV64)
  • (「最小の負のアドレス」、「最大の正のアドレス」だとしても) RV64 の場合の範囲が誤っている。Medium low code model 参照

アドレス範囲: 下記のようにRV32とRV64と分けてシンプルに書く方が分かりやすい。

  • RV32: 0x0 ~ 0xFFFFFFFF (全て)
  • RV64: 0x0 ~ 0x000000007FFFF7FF, 0xFFFFFFFF7FFFF800 ~ 0xFFFFFFFFFFFFFFFF

P.328

...medany コードモデルの場合、PICポリシの場合には...

...medany コードモデルの場合、もしくはPICポリシの場合には...


P.325 表5 リロケーション情報

疑問: なぜ (GNU toolchain の ld を流用する前提で、その ld も使っているであろう) R_RISCV_* という名前をそのまま使わずに R_MYRISCVX_* を定義しているのか?


P.331 MYRISCVXISelLowering.cpp

前ページのコード例と重複している。


P.333 MYRISCVXISelLowering.cpp

P.330 のコード例と重複している。
再掲するのであれば、ここでは最後の3行のみを示す方が明確。


P.353 上のコード例

# この地点で ... a か b の

... 1, 2, もしくは 3の


P.352-360

疑問: ここで示されているアセンブラのコードは、llvmの出力ではなく、gcc の出力を例として示しているのか?


P.419, P.467 asm volatile

asm が常に volatile と共に使用されるように説明されている。

しかし GCC Manual, Extended Asm, "6.47.2.1 Volatile" では、 volatile を使用すべきところ、使用すべきではないところ、について解説している。


P.432 図6

readif

readelf


P.446 MYRISCVXMCCodeEmitter.cpp

同じcode が2回引用されている。


P.456 脚注

TAIL 擬似命令は末尾再帰呼び出しに対応した...

"tail call" 自体の意味は、関数の最後の subroutine callに過ぎず、「末尾再帰呼び出し」とは別のもの。

付録1. 関数呼び出しのバリエーションと高度な機能 より:

末尾関数呼び出しとは、ある関数 f1() が関数 f2() の最後の処理として呼び出されるケースを言います。

この後「末尾再帰呼び出し」の解説が続く。


P.476

while-loop: while-loopの前と中で、同じ処理があり冗長。

以上

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant