なんだこれは

はてなダイアリーから移転しました。

nimとzigはじめました

nim はじめました

教訓

LLM は マイナー言語や最新バージョンなどは把握していないため、LLMに聞くと高確率でハルシネーションしたり、実装してエラー→旧バージョンではこうだった的な嘘を教えてくれます。

環境は intel macOS 15.6.1です。

nim で gzip されたxmlファイルを解凍してパースする

xml を行う方法は 二種類あるらしいが簡単な方を選んだ。

import os, strutils, strformat, zippy, xmlparser, xmltree, options

when isMainModule:
  if paramCount() < 1:
    quit("Usage: nim2 <gzipFile> [csvOutputFile]")

  let gzipFile = paramStr(1)
  let csvFile = if paramCount() >= 2: some(paramStr(2)) else: none(string)

  # GZIPを解凍
  let compressed = readFile(gzipFile)
  let decompressed = uncompress(compressed)

  # XMLを解析
  let xmlDoc = parseXml(decompressed)

  # CSVのヘッダ
  var rows: seq[string] = @["xmlFile,fileName,contentId"]

  # 必要な要素を探索(例: <file-name> 要素から 値 と contentId を取る)
  for node in xmlDoc.findAll("file-name"):
    let fileName = node.innerText().strip()
    let contentId = node.attr("contentId")
    rows.add(&"{gzipFile},{fileName},{contentId}")

  # 出力処理
  if csvFile.isSome:
    writeFile(csvFile.get(), rows.join("\n"))
  else:
    echo rows.join("\n")

これでコンパイルも実行もできた。やったね

nim c nim2.nim # nim という実行ファイルの生成
nim c -r nim2.nim /path/to/NOTICE.xml.gz # 実行ファイルを生成して、引数を与えて実行

ロスコンパイルできる?

nimコンパイラC言語のソースを生成しているのでC言語のソースから linux用のバイナリを生成するクロスコンパイルすれば、nim もクロスコンパイルできる。 ということは二つの方法がある。

  1. nim コマンドで C言語を生成してクロスコンパイルする
  2. nim コマンドで C言語を生成まで。その後クロスコンパイルする。

ここで zig cc を使うとクロスコンパイルが簡単にできるという情報があったので試してみた。 もちろん、 glibc のバージョンが違って動かないという問題を回避するために musl libc を使おう。

一気にクロスコンパイルで失敗

いろいろ調べた結果これでいけそうなんだがうまくいかない。

CC="zig cc" nim c --cc:clang --os:linux --cpu:amd64 --passC:"-target x86_64-linux-musl" --passL:"-target x86_64-linux-musl -static" nim2.nim

これを実行すると、 musl-linux環境の stddef.h を探しにいくのだけれでども、intel MacOS の方を見てしまう。この問題の解決にこうすればいいとLLMはいうのだが、

  --passC:"-target x86_64-linux-musl --sysroot $(zig libc-path)" \

zig libc-path なんてオプションには対応していなくてエラーがでた。 というところで撤退

じゃあ分割してコンパイル

つぎのコマンドで nimcache: で指定したディレクトリにC 言語に書き出すまで行える。

nim c --cc:clang --os:linux --cpu:amd64 --compileOnly \
  --nimcache:build/c nim2.nim

それからこれでクロスコンパイルします、としたいのですが。

zig cc -target x86_64-linux-musl -static \
  -o nim2 \
  build/c/nim2.nim.c \
  $(find build/c -name '*.c' ! -name 'nim2.nim.c')

nim が提供する nimbase.h のパスを教える必要があります。

nim dump

すると、${HOME}/.choosenim/toolchains/nim-${NIMVERSION}/lib のようなディレクトリが表示されますこのディレクトリに nimbase.h があります。 このディレクトリを I オプションで指定します。

zig cc -target x86_64-linux-musl \
  -I${HOME}/.choosenim/toolchains/nim-2.2.4/lib \
  build/c/nim2.nim.c \
  -o nim2

SIMD命令SSSE3 対策

これで成功すると思ったのですが、次のようなエラーがでました。

'_mm_maddubs_epi16' requires target feature 'ssse3', but would be inlined into function 'adler32_ssse3__pkgZzippyZadler3295simd_u84' that is compiled without support for 'ssse3'ect 219 | nimln_(92); mad1_1 = _mm_maddubs_epi16(bytes1_1, tap1_1); |

これは ssse3 というSIMD 命令 (SSSE3) が必要なのに、ツールチェーン(llvm or zig)がデフォルトでサポートしていないようです。これは、zippy ライブラリ(Nim の gzip/zip ライブラリ)が要求しています。

解決方法としては、二つあります。一つは Zig 側に SSSE3 を有効化する です。もう一つは SIMD サポートを無効化したC言語ソースファイルを nim で生成する の二つです。今回は後者を選びました。

Zig 側に SSSE3 を有効化する

最近のx86_64のCPUでは ssse3SIMD命令はサポートされています。そのため、実際には有効にしても問題ないはずです。その場合、-mssse3 オプションで ssse3 を有効にすることもできますし、アーキテクチャを指定してあげる方法もあるらしいです。

zig cc -target x86_64-linux-musl \
  -I${HOME}/.choosenim/toolchains/nim-2.2.4/lib \
  -o nim2l \
  -march=x86_64 -mssse3 \
  build/c/nim2.nim.c $(find build/c -name '*.c' ! -name 'nim2.nim.c')

SIMD サポートを無効化したC言語ソースファイルを nim で生成する

SIMD サポートを無効化したC言語ソースファイルを nim で生成するには、-d:noSimd というオプションで SIMD サポートを無効にできることが多いのです。ですが、zippy は -d:zippyNoSimd というオプションで無効にするようです。

nim c --cc:clang --os:linux --cpu:amd64 --compileOnly \
    -d:zippyNoSimd --nimcache:build/c nim2.nim
zig cc -target x86_64-linux-musl \
  -I${HOME}/.choosenim/toolchains/nim-2.2.4/lib \
  -o nim2 \
  build/c/nim2.nim.c $(find build/c -name '*.c' ! -name 'nim2.nim.c') 

キャッシュチェックエラー対策

これで SIMD サポートを無効化できたのですが次に以下のようなエラーがでました。

build/c/nim2.nim.c:1:1: error: CacheCheckFailed

これは、Zig の クロスコンパイル時キャッシュ検証エラーだそうです。

# nim の不要なソースコードを先に削除
rm -rf build/c
# nim で zippy で SIMD命令を無効にしてCソースコードを生成
nim c --cc:clang --os:linux --cpu:amd64 --compileOnly \
    -d:zippyNoSimd --nimcache:build/c nim2.nim

# zig のキャッシュを削除
rm -rf ~/.cache/zig

# zig でキャッシュを無効化して各ファイルを分割コンパイル
for f in $(find build/c -name '*.c'); do
  zig cc -fno-cache -target x86_64-linux-musl -static \
    -I${HOME}/.choosenim/toolchains/nim-2.2.4/lib -c $f -o ${f%.c}.o
done

# zig で全部をリンク
zig cc -target x86_64-linux-musl -static \
  -o nim2l $(find build/c -name '*.o')

これでなんとかnimとzigでクロスコンパイルできました。