kubo39's blog

ただの雑記です。

binutilsでどういうオプション使うの?というはなし

manみて適切なの使えば?というはなしなんだろうけど、まあとっかかりとかあるし。

objdump

だいたい -Cd(S) もしくは -Cdr(S)をつかってる気がする。Dだと -C dlang とかつける。でもわりとその場その場で変えてるような気もする。

nm

-f はポータブルじゃないので、–format=posix とかにしてる。

hexdump

-C くらい?使い込んでないだけかも。

size

意外と知らない人もいるけど、なかなか便利なやつ。(マイコンとかだとサイズ気になる)

単にサイズ知りたいときは10進数にしてる。

$ arm-none-eabi-size -d examples/itm/itm.bin
   text    data     bss     dec     hex filename
   8716     436      20    9172    23d4 examples/itm/itm.bin

セクション毎でみたいときは、だいたい -Ax にして16進数にしてる。

$ arm-none-eabi-size -Ax examples/itm/itm.bin
examples/itm/itm.bin  :
section             size         addr
.isr_vector        0x188    0x8000000
.text              0x174    0x8000188
.rodata           0x2098    0x80002fc
.bss                0x14   0x20000000
.data                0x8    0x8002394
.got                0x18    0x800239c
.got.plt             0xc    0x80023b4
.debug_str         0x62f          0x0
.debug_loc         0x226          0x0
.debug_abbrev      0x372          0x0
.debug_info        0x9ba          0x0
.debug_ranges       0xc0          0x0
.debug_macinfo       0x3          0x0
.debug_pubnames    0x273          0x0
.debug_pubtypes    0x1b1          0x0
.comment            0x12          0x0
.ARM.attributes     0x45          0x0
.debug_frame       0x1a0          0x0
.debug_line        0x2c4          0x0
Total             0x42f7

readelf

これこそいろいろ使いわけるような。でも -l と -S と -h は使用率高い気する。

あとCortex-Mだと -A オプションとか。

$ arm-none-eabi-readelf -A examples/led/led.bin
Attribute Section: aeabi
File Attributes
  Tag_conformance: "2.09"
  Tag_CPU_name: "cortex-m4"
  Tag_CPU_arch: v7E-M
  Tag_CPU_arch_profile: Microcontroller
  Tag_THUMB_ISA_use: Thumb-2
  Tag_FP_arch: VFPv4-D16
  Tag_ABI_PCS_RW_data: PC-relative
  Tag_ABI_PCS_RO_data: PC-relative
  Tag_ABI_PCS_GOT_use: GOT-indirect
  Tag_ABI_FP_denormal: Needed
  Tag_ABI_FP_exceptions: Needed
  Tag_ABI_FP_number_model: IEEE 754
  Tag_ABI_align_needed: 8-byte
  Tag_ABI_align_preserved: 8-byte, except leaf SP
  Tag_ABI_HardFP_use: SP only
  Tag_ABI_VFP_args: VFP registers
  Tag_ABI_optimization_goals: Prefer Speed
  Tag_CPU_unaligned_access: v6
  Tag_FP_HP_extension: Allowed
  Tag_ABI_FP_16bit_format: IEEE 754

gdb

だいたい -q -iex “set auto-load safe-path ./” で、もろもろは.gdbinitに書いてる。

リンカのメモリコマンド

大半のリンカスクリプトでは、リンク先のようにメモリコマンドに (rwx) のように記述されている。(stm32のやつとか)

リンカのメモリコマンドの仕様は以下のようになっている。

https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/4/html/Using_ld_the_GNU_Linker/memory.html

The attr string must consist only of the following characters:

R Read-only section

W Read/write section

X Executable section

おそらく、多くの人が r=read, w=write という風に誤解しているのだろう。

LDCでのvolatileのはなし

LDCはpragmaを利用してLLVMのコード生成時にvolatileLoad/volatileStoreのハンドリングをしてる。

ここではvolatileLoadの呼び出し/コード生成までみていく。

core.bitopモジュールは、LDCコンパイラを使うときは特に指定しなくても pragma(LDC_intrinsic, “ldc.bitop.vld”) を指定して volatileLoad の宣言をしている。

// druntime - src/core/bitop.d
version (LDC)
{
    pragma(LDC_intrinsic, "ldc.bitop.vld")
        ubyte volatileLoad(ubyte* ptr);
    pragma(LDC_intrinsic, "ldc.bitop.vld")
        ushort volatileLoad(ushort* ptr);
    pragma(LDC_intrinsic, "ldc.bitop.vld")
        uint volatileLoad(uint* ptr);
    pragma(LDC_intrinsic, "ldc.bitop.vld")
        ulong volatileLoad(ulong* ptr);

    pragma(LDC_intrinsic, "ldc.bitop.vst")
        void volatileStore(ubyte* ptr, ubyte value);
    pragma(LDC_intrinsic, "ldc.bitop.vst")
        void volatileStore(ushort* ptr, ushort value);
    pragma(LDC_intrinsic, "ldc.bitop.vst")
        void volatileStore(uint* ptr, uint value);
    pragma(LDC_intrinsic, "ldc.bitop.vst")
        void volatileStore(ulong* ptr, ulong value);
}
...

宣言の一覧はこれ。

// ldc - gen/dpragma.d
extern (C++) enum LDCPragma : int {
  LLVMnone = 0,   // Not an LDC pragma.
  LLVMignore, // Pragma has already been processed in DtoGetPragma, ignore.
  LLVMintrinsic,
  LLVMglobal_crt_ctor,
  LLVMglobal_crt_dtor,
  LLVMno_typeinfo,
  LLVMalloca,
  LLVMva_start,
  LLVMva_copy,
  LLVMva_end,
  LLVMva_arg,
  LLVMinline_asm,
  LLVMinline_ir,
  LLVMfence,
  LLVMatomic_store,
  LLVMatomic_load,
  LLVMatomic_cmp_xchg,
  LLVMatomic_rmw,
  LLVMbitop_bt,
  LLVMbitop_btc,
  LLVMbitop_btr,
  LLVMbitop_bts,
  LLVMbitop_vld,
  LLVMbitop_vst,
  LLVMextern_weak
};

Dコードから命令に落とすところはこうなっている。素朴。

bool DtoLowerMagicIntrinsic(IRState *p, FuncDeclaration *fndecl, CallExp *e,
                            DValue *&result) {
...
  if (fndecl->llvmInternal == LLVMbitop_vld) {
    if (e->arguments->dim != 1) {
      e->error("bitop.vld intrinsic expects 1 argument");
      fatal();
    }
    // TODO: Check types

    Expression *exp1 = (*e->arguments)[0];
    LLValue *ptr = DtoRVal(exp1);
    result = new DImValue(e->type, DtoVolatileLoad(ptr));
    return true;
  }
...

ここでLLVMの中間コード生成するところに渡している。あとはごにょごにょやってLLVM IRを生成してるだけ。(だけ、とは)

LLValue *DtoVolatileLoad(LLValue *src, const char *name) {
  llvm::LoadInst *ld = gIR->ir->CreateLoad(src, name);
  ld->setVolatile(true);
  return ld;
}

たとえば、ARM Cortex-Mとかでベアメタル環境で volatileLoad/volatileStore がほしければ core.bitop の定義をそのままもってくるとよい。

D言語をプロダクションに使うには、ということを考える

D言語のコードを社のレポジトリにpushした。(まだmasterにmergeされていないので、先走り気味かもしれない)

D言語を使った理由は使いたかったというのもあるが、今回のケースでは

  • 使う箇所が部分的(言語はわりとなんでもいいところ)
  • 標準でjsonを扱うためのライブラリがついている(まあ今時の言語だとだいたいありそうな,Rustは標準でなかったので外れた)
  • バイナリを置けばいいだけのほうが楽そう(共有ライブラリのバージョンや置き場所の問題があるので、distroが同じという前提はあった)
  • 静的型チェックがある(これはチームの文化として)
  • それなりに安定して使える(NimとかCrystalとかPonyとかはこのへんで省かれる)

このへんを満たしているという点で、D言語を使うことが合理的な判断だったというところがある。この場合golangでよかった気がするけれど。

ユースケースを満たせばいいってものでもなく、D言語がチームで受け入れられるか、という点も大事になる。

チーム内での評価は

  • Pros
    • (C++がチームの標準言語なので)似たような構文なので読みやすい
    • 構文はわりとよい(型推論があるとか)
    • unittestを関数の近くに書けるところ
  • Cons
    • パターンマッチがない
    • 直和型がない(std.variantは、まあ…)

といった感じ。OCamlとかHaskellが好まれるチーム文化があるけれど、C++も書くチームなので受け入れられやすい。気がする。

D言語投入ありきで考えて、プロダクション投入するには、

  1. 小さく独立した部分ではじめる(動く状態を作ることが大事。時間かけるとけっきょくだれかにPythonとかで書かれてしまう)
  2. チームの文化に合致するか見定める(けっきょく他の人がレビューしないって言ったら別の言語で書くしかない)

この2点に集約される気がする。

Rustのconst_fnがそれほど使い勝手がよくなさそうだという話

現状ではnightlyでしか使えないが、Rustは部分的にコンパイル時に関数評価を定数畳み込みする const_fn という機能がある。

https://doc.rust-lang.org/beta/unstable-book/language-features/const-fn.html

そこでコンパイル時にバイト列を比較するコードを書いてみたが、これはコンパイルエラーになる。

#![feature(const_fn)]

const fn my_strcmp(s1: &[u8], s2: &[u8]) -> i32 {
    let len = if s1.len() <= s2.len() {
        s1.len()
    } else {
        s2.len()
    };

    for i in 0..len {
        if s1[i] != s2[i] {
            if s1[i] > s2[i] {
                return 1;
            } else {
                return -1;
            }
        }
    };

    if s1.len() < s2.len() {
        -1
    } else if s1.len() > s2.len() {
        1
    } else {
        0
    }
}

const X: i32 = my_strcmp(b"kubo39", b"kubo39");

fn main() {
    println!("{}", X);
    // println!("{}", my_strcmp(b"kubo39", b"kubo39")); // 実行時評価、これは動く
}

なるほど、const_fn は推移的でないとだめそうだ。

$ rustc --version
rustc 1.19.0-nightly (04145943a 2017-06-19)
$ rustc -O strcmp.rs
error[E0015]: calls in constant functions are limited to constant functions, struct and enum constructors
 --> strcmp.rs:4:18
  |
4 |     let len = if s1.len() <= s2.len() {
  |                  ^^^^^^^^

error[E0015]: calls in constant functions are limited to constant functions, struct and enum constructors
 --> strcmp.rs:4:30
  |
4 |     let len = if s1.len() <= s2.len() {
  |                              ^^^^^^^^

error[E0019]: constant function contains unimplemented expression type
 --> strcmp.rs:4:15
  |
4 |       let len = if s1.len() <= s2.len() {
  |  _______________^
5 | |         s1.len()
6 | |     } else {
7 | |         s2.len()
8 | |     };
  | |_____^

error: aborting due to previous error(s)

こういうコードはどうだろうか。

#![feature(const_fn)]

const fn lower_len(s1_len: usize, s2_len: usize) -> usize {
    if s1_len <= s2_len {
        s1_len
    } else {
        s2_len
    }
}

const fn my_strcmp(s1: &'static [u8], s1_len: usize, s2: &'static [u8], s2_len: usize) -> i32 {
    const len: usize = lower_len(s1_len, s2_len);

    for i in 0..len {
        if s1[i] != s2[i] {
            if s1[i] > s2[i] {
                return 1;
            } else {
                return -1;
            }
        }
    };

    if s1_len < s2_len {
        -1
    } else if s1_len > s2_len {
        1
    } else {
        0
    }
}

const S1: &'static [u8; 6] = b"kubo39";
const S1_LEN: usize = 6;
const S2: &'static [u8; 6] = b"kubo39";
const S2_LEN: usize = 6;

const X: i32 = my_strcmp(S1, S1_LEN, S2, S2_LEN);

fn main() {
    println!("{}", X);
    // println!("{}", my_strcmp(b"kubo39", b"kubo39"));
}

だめだ、 error[E0434]: can't capture dynamic environment in a fn item; use the || { ... } closure form instead のように怒られてしまっている。

$ rustc -O strcmp.rs
error[E0434]: can't capture dynamic environment in a fn item; use the || { ... } closure form instead
  --> strcmp.rs:12:34
   |
12 |     const len: usize = lower_len(s1_len, s2_len);
   |                                  ^^^^^^

error[E0434]: can't capture dynamic environment in a fn item; use the || { ... } closure form instead
  --> strcmp.rs:12:42
   |
12 |     const len: usize = lower_len(s1_len, s2_len);
   |                                          ^^^^^^

error: aborting due to previous error(s)

他にもいろいろ試したが、どうも制約が強すぎてそれほど利用できる箇所はなさそうなので現状あまり期待しないほうがよさそうだ。

Faster command line tool in Crystal?

こんな感じで巷ではやっているので、crystalはどうだろうと思い試してみた。

# coding: utf-8
# $ crystal build --release fastcmdline.cr
# crystal build --release fastcmdline.cr  7.94s user 0.09s system 101% cpu 7.937 total
# $ time ./fastcmdline googlebooks-eng-all-1gram-20120701-0 1 2
# max_key: 2006 sum: 22569013
# ./fastcmdline googlebooks-eng-all-1gram-20120701-0 1 2  8.68s user 0.69s system 125% cpu 7.465 total

if ARGV.size < 3
   puts "synopsis: #{__FILE__} filename keyfield valuefield"
   exit 1
 end

filename = ARGV[0]
key_field_index = ARGV[1].to_i
value_field_index = ARGV[2].to_i
max_field_index = [key_field_index, value_field_index].max
delim = "\t"

# ここで初期値いじってもあんま効果ない・・ 11, 1024 ~ 8192あたりでとったけど11が一番ましくらい
sum_by_key = Hash(String, Int64).new(initial_capacity: 11)

File.each_line(filename) do |line|
  # たぶんここでメモリ確保走る分遅い
  fields = line.split(delim).first(max_field_index + 1)
  if max_field_index < fields.size
    key = fields[key_field_index]
    field_value = fields[value_field_index].to_i64
    if sum_by_key[key]?.nil?
      sum_by_key[key] = field_value
    else
      sum_by_key[key] += field_value
    end
  end
end

if sum_by_key.empty?
  puts "No entries"
else
  max_key = ""
  max_value = 0.to_i64
  sum_by_key.each do |key, value|
    if value > max_value
      max_key = key
      max_value = value
    end
  end
  puts "max_key: #{max_key} sum: #{max_value}"
end

同じ環境でdmdやrustとやったところdmdが6.6secs、rustが1.1secsほどなのでcrystalは少し遅い。

アルゴリズム的なところ以外でいうと、

  1. Hashの初期メモリ確保が小さくて割当なおすので遅い
  2. splitが毎回メモリ確保するので遅い
  3. メモリ割り当て時にGCスキャンが走るので遅い

というあたりがありそうなかんじだった。

1.は初期値の11から1024,2048,4096,8192あたりに変えて試したけどとくに効果なし。 2.はちょっとめんどくさいのでやらなかったけど、ここは効果ありそう。 3.はGC.stopが生えていなかったのでパス。

といった形で、まあ結局大して最適化してない。それでもCPythonに比べるとだいぶ速いのですごいなあ。

Rustのlockとスコープのはなし

こういうコードを書くと当然deadlockする。

use std::sync::{Mutex, Arc};
use std::thread;

fn main() {
    let mut lock = Arc::new(Mutex::new(false));
    let mut cloned_lock = lock.clone();

    let th = thread::spawn(move|| {
        loop {
            let mut quit = cloned_lock.lock().unwrap();
            if *quit {
                break;
            }
        }
    });

    let mut quit = lock.lock().unwrap();
    *quit = true;
    th.join();
}

ロックの開放タイミングをいいかんじにするとちゃんと終了する。

use std::sync::{Mutex, Arc};
use std::thread;

fn main() {
    let mut lock = Arc::new(Mutex::new(false));
    let mut cloned_lock = lock.clone();

    let th = thread::spawn(move|| {
        loop {
            let mut quit = cloned_lock.lock().unwrap();
            if *quit {
                break;
            }
        }
    });

    {
        let mut quit = lock.lock().unwrap();
        *quit = true;
    }
    th.join();
}

さらに以下のように書いても正常に終了する。 おそらく変数スコープのところで獲得・開放してくれているのだろう。

use std::sync::{Mutex, Arc};
use std::thread;

fn main() {
    let mut lock = Arc::new(Mutex::new(false));
    let mut cloned_lock = lock.clone();

    let th = thread::spawn(move|| {
        loop {
            let mut quit = cloned_lock.lock().unwrap();
            if *quit {
                break;
            }
        }
    });

    *lock.lock().unwrap() = true;
    th.join();
}

Rustいいなあ。