プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

Emacsで Mozilla rr を使えるようにしてみた

 Windows野郎で未だにWSL1の情弱である私も、昨日の記事で遂に Mozilla rr が使えるようになりました。

nodamushi.hatenablog.com

 しかし、Emacsで使いにくい。Emacs用のパッケージも特に無さそう?というわけで、設定追加したので晒してみる。 -exec-reverse-continue見たいなコマンドはないっぽいけど、取りあえず、何か何とか動いてます。

 基本的には gdb を呼び出してるだけです。

  • M-x → rr : rr replayを起動
  • M-x → rr-record: rr recordを起動
  • F8: continue
  • Ctrl + F8: reverse-continue
  • F9: step
  • Ctrl + F9: reverse-step
  • F10: next
  • Ctrl + F10: reverse-next
  • F11: finish
  • Ctrl + F11: reverse-finish
(use-package gdb-mi
  :init
  (defcustom gud-rr-command-name "rr replay -i=mi"
    "Default command to execute an executable under the Mozilla rr debugger."
    :type 'string
    :group 'gdb)
  (defcustom gud-rr-record-command-name "rr record"
    "Default command to execute an executable under the Mozilla rr recode."
    :type 'string
    :group 'gdb)

  (defun rr (command-line)
    "rr replay"
    (interactive (list (gud-query-cmdline 'rr "")))
    (gdb command-line)
    (gud-def gud-reverse-next "reverse-next %p"
             "\C-r"
             "Step one line (skip functions).")
    (gud-def gud-reverse-step "reverse-step %p"
             "\C-e"
             "Step one line (skip functions).")
    (gud-def gud-reverse-continue "reverse-continue"
             "\C-w"
             "Step one line (skip functions).")
    )
  
  (defun rr-record (command-line)
    "rr record"
    (interactive (list (gud-query-cmdline 'rr-record)))
    (compile command-line))

  :config

  ;; global-display-line-numbersを有効にしてるので、変数バッファなどで無効化
  (defun my-disable-display-line-numbers (orig-func &rest args)
    (let ((r (apply orig-func args)))
      (remove-hook 'pre-command-hook #'display-line-numbers-update-width t)
      (setq display-line-numbers nil)
      r))
  (advice-add 'gud-common-init :around #'my-disable-display-line-numbers)
  (advice-add 'gdb-get-buffer-create :around #'my-disable-display-line-numbers)

  (global-set-key (kbd "<f8>") 'gud-cont)
  (global-set-key (kbd "C-<f8>") 'gud-reverse-continue)
  (global-set-key (kbd "<f9>") 'gud-step)
  (global-set-key (kbd "C-<f9>") 'gud-reverse-step)
  (global-set-key (kbd "<f11>") 'gud-finish)
  (global-set-key (kbd "C-<f11>") 'gud-reverse-finish))

f:id:nodamushi:20200702223541p:plain

Hyper-VにUbuntu20.04を入れてから、 Mozilla rr を動かすまで

 Mozilla rr という、実行のイベントを保存して、プロセスの実行を再現するデバッガを今更最近知りました。 ステップ実行を逆方向に行えるので、とても便利ですね。

 仕組み的にはWSL2なら使えそうな気がしますが、WSL1の私は使えません。(ていうか、Windows 2004は一体いつなんだ…。)

 仕方がないので、Hyper-V上のUbuntuで動かそうとしましたが、はい、動きません。チキショウ。

 調べてみると、Wiki曰く 「Hyper-V does not seem to support PMU virtualization.」とのことで、 Hyper-V上では動かないのだ。

 マジかよー、と一度は諦めたのですが、もう一度よく調べたら何かできたのでここにメモしておきます。

 ちなみに、初めてUbuntu 20.04 も入れたので、そこからだぜ。

Ubuntu 20.04 のインストール

 実は初めてのUbuntu 20.04。サクッと Hyper-V クイック作成から突っ込みます。

f:id:nodamushi:20200702004506p:plain:w320

 プロセッサやメモリや統合サービス等の設定を適当にして、起動。

f:id:nodamushi:20200702004721p:plain:w320

 サクッとインストールして、起動したのがこちら。

 ………リージョンを日本語にしていますが、全然翻訳されてない。

f:id:nodamushi:20200702005210p:plain:w320

 翻訳はされていませんが、ディレクトリは「デスクトップ」とかいう日本語になってるので、「Desktop」に変更。

 最初からEnglishを選んだ方が楽だと思う。

LANG=C xdg-user-dirs-gtk-update

f:id:nodamushi:20200702005521p:plain:w320

拡張セッション

 このままじゃクリップボードの共有が出来なくて辛タンなので、Microsoft linux-vm-toolsでxrdpを入れて、拡張セッションを使えるようにします。

sudo apt install git
mkdir ws
cd ws
git clone https://github.com/microsoft/linux-vm-tools.git
cd linux-vm-tools/ubuntu

 20.04が…………ない?あれー?

f:id:nodamushi:20200702010041p:plain

 2020年7月現在だと、Ubuntu 20.04に対応していませんでした。

 20.04対応のプリリクを出して下さっている方が居るので、そっちで試してみます。

(※未来の方で20.04があるなら、それを利用して下さい。)

f:id:nodamushi:20200702010412p:plain:w320

git clone -b ubuntu20-04 https://github.com/Hinara/linux-vm-tools.git
cd linux-vm-tools/ubuntu/20.04
chmod +x install.sh

 で、 install.shを実行してreboot

sudo ./install.sh
sudo reboot

 rebootしたらもう一回 install.shを実行し、シャットダウンします。

cd ~/ws/linux-vm-tools/ubuntu/20.04
sudo ./install.sh
sudo shutdown now

 Windows上で管理者権限のPowerShellを起動し、以下を実行。 Ubuntu 20.04の所は自分の仮想マシン名に合わせてください。

Set-VM -VMName "Ubuntu 20.04" -EnhancedSessionTransportType HvSocket

 これで、一応拡張セッションは使えるようになったんですが、何かやたらと反応するかしないかが不安定です。4回に一回ぐらいしか、拡張セッションが有効にならない。よくわからん。なーんか、「ゲスト サービス」を有効にしてるとダメな感じがする…(不明)

Mozilla rr を使えるようにする

 Windows Server 2019 および Windows 10 バージョン1809以降のバージョンのみ可能です。IntelのCPU以外は知らない。Ryzen欲しい。

 先ずは Windows上で PowerShellを管理者権限で起動して、以下を実行。Ubuntu 20.04の部分は仮想マシン名に合わせてください。

 なお、たぶん、 PMUだけで十分だとは思われる。

Set-VMProcessor "Ubuntu 20.04" -Perfmon @("pmu", "lbr", "pebs")

t.co

 そして、Ubuntu20.04を起動して、rrをインストール

sudo apt install rr

 で、以下の様な適当なCコードを作成して、デバッグビルドしてrrで記録を取ってみる。

 初回は必ず /proc/sys/kernel/perf_event_paranoid が3だと怒られるので、1に変更しておきます。

 図のメッセージのように、 /etc/sysctl.conf にkernel.perf_event_paranoid = 1 を書くかどうかはご自由に。(私は面倒くさいので設定しましたが、どういう影響が出るのか良く分からないので…。まぁ、どうせ仮想マシンだし)

f:id:nodamushi:20200702023835p:plain

gcc -g3 foo.c
echo 1 | sudo tee /proc/sys/kernel/perf_event_paranoid
rr record ./a.out
rr replay

 これで上の図のようにちゃんとHyper-V上のUbuntu20.04でMozilla rrが動きました!やったね!

ZenでC++ライクなTupleを作る

 はい、なんかブログを書けという圧が強い気がしたので、何か書きます。

 というわけで、いつでもどこでもメタい事を考えていると、どうしても欲しくなりますよね、Zen言語でコンパイル時動的型。 ですが、Zenといえど流石に構造体を自在に作ることは出来ません。(出来ないよね?出来たら凄いね。)

 

 しかし、C++のtupleのような使い方をする構造体なら作ることが可能です。

 これで型の配列さえ作れば、後は構造体に出来るので、若干フィールドへのアクセスが面倒くさい点を除けば、何でも出来ますね!

const std = @import("std");

pub fn main() anyerror! void {
  const Name = [:0]const u8;
  const Age = u32;
  const Height = u32;

  const t = tuple( &[_]type {Name, Age, Height} )
            .init(.{ "nodamushi", 19, 182 });

  std.debug.warn("name={}, age={}, height={}\n",
                 .{ t.get(0),  t.get(1),  t.get(2) });
}


/// C++のtupleのような型を生成します
pub fn tuple(comptime types: []const type) type{
  return _tuple(types, 0);
}

fn _tuple(comptime types: []const type, comptime index:usize) type{
  if(types.len == index){
    return void;
  } else {
    const Car = types[index];
    const Cdr = _tuple(types, index+1);

    return struct {
      const Self = @This();

      pub const len = types.len - index;
      car: Car,
      cdr: Cdr,

      pub inline fn init(value: var) Self {
        return Self{
          .car = value[index],
          .cdr = if(void == Cdr) {} else Cdr.init(value),
        };
      }

      pub inline fn get(self: Self, comptime idx:usize) types[idx] {
        if(index == idx) {
          return self.car;
        } else {
          return self.cdr.get(idx);
        }
      }

      pub inline fn ptr(self: *Self, comptime idx:usize) *types[idx] {
        if(index == idx){
          return &self.car;
        } else {
          return self.cdr.ptr(idx);
        }
      }
    };
  }
}

 

JavaのOptionalは現状失敗作

 チマチマとは書いていたんですが、1年ぶりぐらいに真面目にJava書いています。

 前からずっと思っていて、実際何度か口にしたことがあるんですが、やっぱ言いたい。

 JavaのOptionalは失敗作

 

 

JavaのOptionalの何が悪いの?

 言語機能じゃないから。完全にここに集約されると私は思っています。

 Java8で登場したOptionalですが、この時にコンパイラを拡張しなかったのがそもそもの問題だと思っています。

 

AutoBoxing

 例えばKotlinのnullableのような機能の導入、というのは慎重になるべきでしょうし、すぐできるとは思えません。

 しかし、Optionalへのオートボクシングぐらいは実現できたのではないでしょうか?int→Integerというオートボクシングは既に存在しているわけで、最初から導入すべきだったと思います。

public OptionalInt  intMethod(){
     if(何か条件) return 1;  // -> OptionalInt.of(1)に
     if(何か条件) return OptionalInt.of(1);  // そのまま
     return null;// -> OptionalInt.empty()に
}

public Optional<Object> objMethod(){
    Object obj = null;
    if(何か条件)   return new Object(); //-> Optional.of(new Object())に
    if(何か条件)   return obj; // -> Optional.ofNullable(obj)に
    return null; // -> Optional.empty()に
}

 

 実に鬱陶しいofやらemptyやらofNullableが省略できる上、Optionalの癖にnullが返せるという謎の状況も生まれにくいでしょう。(※上の例だと、OptionalIntをそのまま返すとしてるけど、ここでnull挿入できる)

 むろん、色々な議論があったのでしょうが、導入が許されるのは、最初だけなのです。オートボクシングはもはや、実現不可能です。

 なぜなら、現在、nullが返せるという謎の状況が合法だからです。(どれほどおかしいとしても!)

public Optional<Object> objMethod(){
    return null;
}

 オートボクシングを認めてしまうと、後方互換を破壊してしまいます。

 今から可能なのは、Kotlinの様に言語機能を拡張するか、後方互換の破壊か、nilとかnullに変わる単語を導入する(ミスる自信があるぞ!)か、アノテーションで処理変えるか、Optionalに暗黙に変換されるOptional2見たいな型でも用意するかぐらいでしょうか。

 

Auto-Unboxing

 Optionalのオートボクシングを認めるなら、当然、アンボクシングもあるべきです。Optional<T>→Tへのアンボクシング………の前にOptional→booleanへのアンボクシングです。アンボクシングと言うよりは、暗黙の変換ですね。

 例えば、以下の様な状況を考えると、二つ目のif文の方が読みやすいと思います。isPresentって長いし面倒くさいよ。

if(intMethod().isPresent()){
  //戻り値は特に使わない処理
}else{
}

if( intMethod() ){
  //戻り値は特に使わない処理
}else{
}

 

 ifPresentOrElseがあるだろ、この馬鹿たれ!というお叱りの声が聞こえますが、本気でそれが読みやすいと思っています?

 ifPresentOrElse(()->{},()->{})より、単純にif文の方が、私には読みやすいです。

 そして、条件式がOptionalだったら、trueの場合は_にアンボクシングした結果が格納される、みたいな構文でアンボクシングされると良いかなーとか思います。

int value = intMethod() ? _  :  0 ;
if( intMethod() ) {
  int x = _ + value;
}

 なるべくキーワードを増やさない様に_ってかいたけど、$1とか$2でもいいかもね。

 ifPresentOrElseなんかよりずっと読みやすいし、ラムダ式の変数のキャプチャとかも発生しない。

 更に推し進めて、存在演算子が導入されても良いと思います。

int value =  intMethod()? 0;

 

まとめ

 JavaもプリミティブのラッパーやString、列挙体、Cloneable、AutoCloseableなど、幾つかのクラスでは言語的に特殊処理しているのだから、Optionalは何が何でも絶対駄目という理由はないと思います。むしろ言語として特殊扱いすべきだったと思います。

 C++は難しすぎる、Javaの方が簡単と言われますが、OptionalはC++の方が簡単ですね。オートボクシングやbooleanへの変換は、C++では暗黙の型変換で普通に使えます。

 最近のJavaは言語機能の拡張に積極的ですから、早いところ、言語機能に組み込んで欲しいな。

組み込みでラムダは使って良いけど、std::functionは駄目っぽい

 例えば、組み込みでウォッチドックタイマー(WDT)を使う場面を想定しましょう。

void main()
{
  クロック設定とか;
  WDT->Prescaler = プリスケーラの設定;
  WDT->Clear     = クリア;
  WDT->Control = イネーブル;

  for(;;){
     WDT->Clear = クリア;

     何か処理;
  }
}

 無茶苦茶長い処理をする場合は処理中にクリアを挟むことはあるだろうけど、基本形はこれでいいでしょう。

 さて、どうだろう?なんとも思わない?よし、君は素晴らしい人格者だ。幸せに生きて欲しい。

 さぁ、無駄な時間を過ごす前に、ブラウザバックするなり、タブを今すぐ閉じるんだ。

 モヤモヤした何かを感じるかい?奇遇だ、私もだ。さぁ、私と一緒に地獄に堕ちようよ。

 

こう書きたい

 さて、上記の様なコードを見ると、私としては、例えばこう書きたくなります。

void main()
{
  クロック設定とか;
  WDT.period( 1ms, 100_MHz ) // WDTの基準クロックと、オーバーフロー周期
     .loop( [](){
    何か処理;
   });
}

 

 クリアだなのなんだのは隠蔽して、勝手にやって欲しい。一回のループでするべき処理だけ定義して、後は自動でやって欲しいのです。

 つまり、処理の「意味」だけに注力したいのです。レジスタとか後から読んで読めないゴミクズを、一々触りたくないのです。

(なお、1msはstd::chrono_literalsに定義してあります。 100_MHz は、ユーザー定義リテラルで自作できます。)

 

std::functionで関数を渡す

 このように、何か勝手にやって欲しい処理の間に、ユーザー定義の処理を挟むには大凡以下の方法があります。

  1. 関数を定義して、関数ポインタを渡す
  2. 関数オブジェクトを渡す
  3. ラムダ式を渡す

 先に書いた例ではラムダ式を使っていますね。なお、実際にはラムダ式は2の糖衣構文なので、基本的には二つですね。

 「C++は関数ポインタよりも、関数オブジェクトの方が最適化が効きやすい」という話もありますが、最近のコンパイラは賢いので、関数ポインタでも最適化がいけてる気がします。実際、手元にあるIARのコンパイラでリリースビルドすると、関数ポインタの最適化ができています。

 どれを使うにせよ、受け取る側(loopメソッド)はC++11以上では、std::functionで簡単に同じように受け取ることができます。

#include <functional>

void loop(std::function< void () > func);

 

 これをビルドするとどうなるのでしょうか?

 以降はWDTの例を引きずるとコードが長くなるので、以下の様な単純なコードで考えてみましょう。

#include <functional>

volatile int a=0,b=1;//レジスタを想定

void foo(std::function<void()> f)
{
  while(1)
  {
    a++;//ループの最適化をさせない
    f();
  }
}


void main()
{
  foo([](){
    int bb = b;
    b = bb * (bb + 1); // volatileなので最適化はされない
  });
}

 

 IARのコンパイラで上記のコードをデバッグビルドすると、マップファイルは以下の様になります。

Entry                       Address  Size  Type      Object
-----                       -------  ----  ----      ------
.iar.init_table$$Base         0x478         --   Gb  - Linker created -
.iar.init_table$$Limit        0x488         --   Gb  - Linker created -
?main                         0x489        Code  Gb  cmain.o [4]

(略)
std::allocator<int>::allocator(const std::allocator<int>&) [subobject]
                              0x3a1   0x8  Code  Gb  main.o [1]
(略)
operator delete (void *)
                              0x41d   0xa  Code  Gb  delop_0.o [3]
operator new (unsigned int, void *)
                              0x339   0x4  Code  Gb  main.o [1]

(略)

 newとかdeleteとかallocatorとか、組み込みの敵なメソッドがずらり。

 最適化してないからしょうが無いのかと思ったら、リリースビルドにしても、deleteや仮想テーブルが残る結果になりました。

 これでは、組み込み的にはちょっと使えません。

 ラムダ式がインライン化されて、もっとがっつり消えてくれるのかと思ってたんですけど、少なくともIAR Embedded Workbench for ARM8.40のコンパイラでは消えませんでした。

 

 そこで、もうちょっと最適化させやすくしてみます。

inline void foo(std::function<void()> f)

 fooにinlineを付けてみました。これでリリースビルドをすると、デリータ関連が消えました。fooも消えたので、fooがinline展開されて、デリータがいらなくなったのでしょう。(というか、C++において、inlineは基本的にヘッダーに関数をかける以上の意味は無いと思っていたのですが、消えてびっくり)

 しかし、仮想テーブルは残りました。std::functionという情報を消せてないようですね。(無論コンパイラによっても違うでしょうが)

 ということは、どうも組み込み的にはstd::functionが敵のようです。そうか、君もstd::vectorとかと同類の禁止薬物だったか。

 

sdt::functionを使わず、templateにしてみる

 functionがだめならば、使わなければいいのだろう?と単純にtemplate化してみました。

template<typename F>  void foo(F f)

   これを、デバッグビルドしたときのマップファイルが以下です。

Entry                       Address  Size  Type      Object
-----                       -------  ----  ----      ------
.iar.init_table$$Base          0xa4         --   Gb  - Linker created -
.iar.init_table$$Limit         0xb4         --   Gb  - Linker created -
?main                          0xb5        Code  Gb  cmain.o [3]
CSTACK$$Base            0x2000'0008         --   Gb  - Linker created -
CSTACK$$Limit           0x2000'0408         --   Gb  - Linker created -
Region$$Table$$Base            0xa4         --   Gb  - Linker created -
Region$$Table$$Limit           0xb4         --   Gb  - Linker created -
__cmain                        0xb5        Code  Gb  cmain.o [3]
__exit                        0x119  0x14  Code  Gb  exit.o [4]
__iar_data_init3               0x7d  0x28  Code  Gb  data_init.o [3]
__iar_debug_exceptions         0xd6   0x1  Data  Gb  unwind_debug.o [4]
__iar_program_start           0x12d        Code  Gb  cstartup_M.o [3]
__iar_zero_init3               0x41  0x3a  Code  Gb  zero_init3.o [3]
__low_level_init               0xd3   0x4  Code  Gb  low_level_init.o [2]
__vector_table                  0x0        Data  Gb  vector_table_M.o [3]
_call_main                     0xc1        Code  Gb  cmain.o [3]
_exit                         0x10d        Code  Gb  cexit.o [3]
_main                          0xcf        Code  Gb  cmain.o [3]
a                       0x2000'0000   0x4  Data  Gb  main.o [1]
b                       0x2000'0004   0x4  Data  Gb  main.o [1]
exit                           0xf1   0x4  Code  Gb  exit.o [2]
main                           0xe9   0x8  Code  Gb  main.o [1]
main::[lambda() (instance 1)]::operator ()() const
                               0xd9   0xc  Code  Lc  main.o [1]
void foo<main::[lambda() (instance 1)]>(main::[lambda() (instance 1)])
                               0xf5  0x18  Code  Gb  main.o [1]

 おぉ、単にmain::[lambda()(instance 1)]という型の関数オブジェクトになったお陰で、デバッグビルドだというのに、newだとかallocとかdeleteとか仮想テーブルが一切なくなっています。

 ちなみに、最適化すると、fooもラムダ式もインライン展開されて、全部消えてました。

 これなら、組み込みでもニッコリですね。

 

関数ポインタ

 テンプレートでもニッコリですが、関数ポインタにもしてみましょう。

 例の様に、キャプチャしていないラムダ式は関数ポインタに暗黙に変換できます。

volatile int a=0,b=1;//単に最適化させない為だけ

void foo(void (*f) ())
{
  while(1)
  {
    a++;// レジスタを想定して、ループの最適化をさせない
    f();
  }
}

int main()
{
  foo([](){
    int bb = b;
    b = bb * (bb + 1); // volatileなので変に最適化はされない
  });
  return 0;
}

 これを同様にIARでデバッグビルドするとこんなんができてて、微妙に8byteほどサイズが大きい。

main::[lambda() (instance 1)]::_FUN()
                               0x71   0xa  Code  Lc  main.o [1]
main::[lambda() (instance 1)]::operator ()() const
                               0x41   0xc  Code  Lc  main.o [1]

 この_FUNってなんじゃらほい?というわけで、逆アセンブル。

   0x70: 0xb580         PUSH    {R7, LR}
   0x72: 0x2000         MOVS    R0, #0
   0x74: 0xf7ff 0xffe4  BL      _ZZ4mainENKUlvE_clEv   ; 0x40
                                main::[lambda() (instance 1)]::operator ()() const
   0x78: 0xbd01         POP     {R0, PC}

 単にインスタンスを置いてラムダ式をコールしてreturnしてるだけですね。暗黙変換されるとき、こいつが渡されるようです。こういう処理が入る分、8byteほどデバッグビルドのサイズが大きくなった様子。

 なお、最適するとどっちも同じ結果になりました。

   

まとめ

 組み込みにおいては、肥大化しても良いならテンプレートを、そうでないなら関数ポインタを使いましょう。近年のコンパイラは賢いので、関数ポインタでもちゃんと最適化してくれます。

 

 無論、これは2019年現在の話であって、コンパイラが賢くなれば、std::functionでも良くなるかも知れませんが。

static_assertで比較した値を表示したい

「C++のconstexprの値がおかしいんだけど、static_assertで間違った値表示できないの?」と聞かれました。

  できねぇんだな、これが

 C++11からstatic_assertはあるというのに、何故static_assert_eqやstatic_assert_ltとかがないのだろう。謎で仕方がないです。

#include <cassert>

constexpr int x = 10;
static_assert(x == 1, "x?");

 これをg++でコンパイルすると、以下の様なエラーになります。

 xが何だかは全然分からないですね。

static assertion failed: x?
    4 | static_assert(x == 1, "x?");
      |               ~~^~~~~

 

 

 static_assertのメッセージには、ストリングリテラルしか入れられないので、動的に生成することもできないです。 C++20でconstexprにおけるメモリ確保までできる様になるんだから、そこら辺も緩和して欲しいところです。

 

組み込みの定数計算ぐらいならなんとかなるかも

 

 表示できないと言いましたが、整数値なら、裏技的に値を表示する方法があります。

constexpr int x = 10;

template<int NUMBER>
struct test
{
  static constexpr bool value=true;
  static_assert(NUMBER == 1,"test<NUMBER>");
};

static_assert(test<x>::value,"");

 

 これのg++のエラーは以下の様になります。

foo.cpp: In instantiation of 'struct test<10>':
foo.cpp:15:22:   required from here
foo.cpp:12:24: error: static assertion failed: test<NUMBER>
   12 |   static_assert(NUMBER == 1,"test<NUMBER>");
      |                 ~~~~~~~^~~~~

 そう、一度テンプレートパラメータに入れてしまうと、「foo.cpp: In instantiation of 'struct test<10>':」ここ(赤字)に値が出るんです。

 ちなみに、clang++だと、NUMBER==1じゃなくて、10==1と表示してくれます。凄いね。

 

 複雑なクラスなど使わない組み込みで、整数を計算するだけのconstexprなら、これで十分な場合も多々あると思います。

 

というわけで、まとめておいた

 一々書くのも面倒くさいと言うことで、まとめておきました。

github.com

 こんな感じで使えます。

#include "sassert.h"

// @return x * x
constexpr int pow(int x){return x  +  x;}

SASSERT_EQ(pow(10),100); // 10*10 == 100
SASSERT_NE(pow(10),20);  // 10*10 != 20
SASSERT_LT(pow(1),2);    // 1*1 < 2
SASSERT_GT(pow(10),50);  // 10*10 > 50
SASSERT_LTE(pow(10),100);// 10*10 <= 100
SASSERT_GTE(pow(10),100);// 10*10 >= 100

 エラー時の値は「nds::assert_○<型, 値, 期待値, ファイル行 >」という形で表示されるので、その言葉を探してください。IAR Embedded Workbench等で使う場合は、エラーログを右クリックし、ログのフィルタリングを無し(全て)にして表示してください。

 g++だとこんな感じ。

include/sassert.h: In instantiation of 'struct nds::assert_eq<int, 20, 100, nds::line<6> >':
foo.cpp:6:1:   required from here
include/sassert.h:54:17: error: static assertion failed: Value == Expected
   54 |   static_assert(value,"Value == Expected");
      |                 ^~~~~
include/sassert.h:31:45: error: static assertion failed: pow(10) == 100
   30 |   static_assert(::nds::assert_##op <::nds::assert_type_t<decltype(x)>,\
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   31 |                 x,y,::nds::line<__LINE__>>::value,#x message #y)
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
include/sassert.h:22:27: note: in expansion of macro '__NDS__ASSERT_BASE'
   22 | #  define SASSERT_EQ(x,y) __NDS__ASSERT_BASE(eq," == ",x,y)
      |                           ^~~~~~~~~~~~~~~~~~
foo.cpp:6:1: note: in expansion of macro 'SASSERT_EQ'
    6 | SASSERT_EQ(pow(10),100); // 10*10 == 100
      | ^~~~~~~~~~

 「include/sassert.h: In instantiation of 'struct nds::assert_eq<int, 20, 100, nds::line<6> >':」の所に、pow(10)の間違った結果が出ています。

 ライセンスはCC0なので(Unlicenseとどっちがいいの?)、適当に改変して結構です。 

 ………static_assert_eqとかC++で対応してくれないかなぁ。

C++の相互参照ってどう作るのが良いの?

スマポと相互参照

 GCを持っていない以上、根本的にC++のスマポと循環参照は相性が悪いけど、私が問題にしたいのはソコではないです。

 例えば、前回公開したSVDライブラリだと以下の様に、木構造の情報を構成します。

  1. デバイスはペリフェラルを複数保持する
  2. ペリフェラルはレジスターを複数保持する
  3. レジスタ―はフィールドを複数保持する

 ただ保持しているだけなら、vector<レジスタ>か、vector<unique_ptr<レジスタ>>でいいでしょう。 ですが、SVDのデータ構造は、フィールドの情報を確定するにはレジスターの情報が必要である、という様に、子→親への参照も必須なのです。

 (むろん、木構造の構築時に確定してしまっても良いけど。実際、最初に作ったときは、どうせ編集するつもりがないので確定して作った。この場合、子が親を必要としないので、単にvectorで困ることはないないばーですね。)

   で、今更生ポインタ(ナマポ?)で管理ってのもあれなので、スマポ使いましょう。相互参照するので、shared_ptrとweak_ptrを使えばいいのでしょうか?

 しかし、これだと問題が発生します。それは親ポインタ(オヤポ?)をいつ確定するのってことです。

struct device
{
  vector< share_ptr< peripheral> >  peripheral;

  device():list{}
  {
    peripheral . emplace_back(  new peripheral(this)  );
  }
}

struct peripheral
{
   week_ptr< device > parent;  // オヤポ
   vector< share_ptr <register>  >  registers;

  peripheral(  week_ptr< device>  オヤポ? ) 
    parent( オヤポ? ),
    registers{}
  {
    registers . emplace_back(  new field(this) );
  }
};

struct register
{
   week_ptr< peripheral > parent;  // オヤポ
   vector< share_ptr <field>  >  fields;
};

 

 オヤポ?とか書いてるところがネックです。

 ナマポだったら、コンストラクタにthisを渡せば済むことが、スマポだと、コンストラクタで完結できないなさそうです。次の様な関数を定義するなりして、後で呼び出すしかない。

struct device
{
  void set_parent (  std::share_ptr< device > ptr)
  {
    for(auto p: peripherals)  p-> set_parent( ptr, p);
  }
};

struct peripheral
{
  void set_parent (  std::share_ptr< device > ptr, std::shared_ptr<peripheral> me)
  {
    parent = ptr;
    for(auto p: registers)  p-> set_parent( me, p);
  }
};

 

 

 もしくは、諦めて、親への参照はナマポにするか。

 今回の場合なんかは、そもそも、親への参照が壊れたら正常に動かないので、ソコを気にしない(ユーザー責任)というのは一つの答えとして悪くない気がします。

struct device
{
  vector< unique_ptr< peripheral> >  peripheral;

  device():list{}
  {
    peripheral . emplace_back(  new periphera( *this ) );
  }
}

struct peripheral
{
private:
   device& parent;  // オヤポ

public:
   vector< unique_ptr <register>  >  registers;

  peripheral(  device&  d ) 
    parent( d ),
    registers{}
  {
    registers . emplace_back(  new register(*this) );
  }
};

struct register
{
private:
   peripheral& parent;  // オヤポ
public:
   vector< unique_ptr <field>  >  fields;
};

 うーむ………。Java何かだと一切悩まない様なところで、変に悩むのがC++ですな。

 一般にはどうするのが良い手段なんだろうね。