読者です 読者をやめる 読者になる 読者になる

llvm: ビットコードのデコード

llvm common lisp

LLVM Bitcode File Format — LLVM 3.4 documentationを読んでllvmのビットコードをデコード(パース)してみたので、そのメモ(あくまでも個人用メモ。用語とかは結構自分の好き勝手に書いているので、ちゃんと知りたい人はオリジナルのドキュメントを参照のこと)

ビットコードフォーマット

ビットコードのフォーマット自体は、用途をLLVMに限定されず「階層的なタグ付きデータをビットストリームとして表現する」ために汎用的に使えるものとなっているみたい。
そして、LLVMではそれをIR(Intermediate Representation)の表現用に利用している、という位置づけ。

What is commonly known as the LLVM bitcode file format (also, sometimes anachronistically known as bytecode) is actually two things: a bitstream container format and an encoding of LLVM IR into the container format.


The bitstream format is an abstract encoding of structured data, very similar to XML in some ways. Like XML, bitstream files contain tags, and nested structures, and you can parse the file without having to understand the tags. Unlike XML, the bitstream format is a binary encoding, and unlike XML it provides a mechanism for the file to self-describe "abbreviations", which are effectively size optimizations for the content.

http://llvm.org/docs/BitCodeFormat.html#overview

データ構造の定義*1およびそれに基づくデータのエンコードの方法自体は汎用的で、それらをどう解釈するかがアプリケーションごとで異なる。
今回実装するのは、その汎用的な「bitstream container」のパース処理で、LLVM IRに特化した部分は扱わない*2

デコード関数

以降は、パーサのソースコード*3とメモコメント。


まずはビットストリーム読み込み用の構造体と関数を定義。

;;;; ファイル名: bit-stream.lisp
;;;;
;;;; - ビットストリームの読み込み方法には三種類ある
;;;;  1] ビット幅が固定の整数値の読み込み: read-fixed-width-int関数
;;;;  2] ビット幅が可変の整数値の読み込み: read-variable-width-int関数
;;;;     Nビットずつ読み込み、最上位ビットが1か0かで終端を判断する。0なら終端。
;;;;  3] 6bit文字の読み込み: read-6bit-char関数
;;;;
;;;; - ビットは下位のものから順番に読み込んでいく

;;;;;;;;;;
;;; 型定義
(deftype octet () '(unsigned-byte 8))                  ; 一バイト
(deftype octet-bit-position () '(integer 0 8))         ; 一バイトを表現するのに必要なビット数+1
(deftype bit-position () `(mod ,most-positive-fixnum)) ; ビットの位置   XXX: 上限はテキトウ

;;;;;;;;;;;;;;;;;;;;
;;; ビットストリーム
(defstruct (bit-stream (:constructor make-bit-stream (octet-stream)))
  (octet-stream nil :type stream)              ; 読み込み元となるバイトストリーム
  (octet          0 :type octet)               ; バッファ (1bitずつ読み込むことは出来ないので、まず一バイト取得し、そこから1bitずつ取り出していく)
  (pos            8 :type octet-bit-position)  ; octet内での現在位置
  (acc-pos        0 :type bit-position))       ; 読み込んだビット数 (ストリームの始点を基準とした現在位置)

(defmacro with-input-bit-stream ((in file) &body body)
  `(with-open-file (,in ,file :element-type 'octet)
     (let ((,in (make-bit-stream ,in)))
       ,@body)))

;; 1bit読み込む
(defun read-bit (in)
  (with-slots (octet-stream octet pos acc-pos) (the bit-stream in)
    (when (= pos 8)
      (setf octet (read-byte octet-stream nil 0)  ; ストリームの終端に達した場合は0を返す
            pos   0))
    (prog1 
        (ldb (byte 1 pos) octet)
      (incf acc-pos)
      (incf pos))))

;; ビット幅が固定の整数値を読み込む
(defun read-fixed-width-int (in width &key (align 1))
  (with-slots (acc-pos) (the bit-stream in)
    ;; 読み込み開始位置をalignビットの境界に合わせる   TODO: alignmentは別関数に分けた方が良い
    (loop WHILE (not (zerop (mod acc-pos align))) 
          DO (read-bit in))
    
    ;; 整数値読み込み
    (loop FOR i FROM 0 BELOW width
          SUM (ash (read-bit in) i))))

;; ビット幅が可変(不定)の整数値の読み込み
(defun read-variable-width-int (in width &aux (data-width (1- width)))
  (loop WITH int = 0
        FOR i FROM 0 
        FOR n = (read-fixed-width-int in data-width)  ; 下位の(1- width)ビットが整数値用のデータ
        FOR last? = (zerop (read-bit in))             ; 上位1bitはフラグビット
    DO
      (incf int (ash n (* data-width i)))
      (when last?
        (return int))))

;; 6bit文字の読み込み
;; - 英数字と'.'及び'_'が表現可能
(defun read-6bit-char (in)
  (let ((code (read-fixed-width-int in 6)))
    (cond ((<= 00 code 25) (char+ #\a code))
          ((<= 16 code 51) (char+ #\A code))
          ((<= 52 code 61) (char+ #\0 code))
          (t (case code
               (62 #\.)
               (63 #\_))))))

(defun char+ (base-char delta)
  (code-char (+ (char-code base-char) delta)))

ビットコードのデコード。

;;;; ファイル名: decode.lisp
;;;; 
;;;; - LLVMのビットコードをデコード(パース)する: read-bitcode関数

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; bit-stream.lispをロード
(load "bit-stream")

;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; デバッグ用変数 と 関数
(defvar *depth* 0)          ; 階層のネスト数
(defvar *show-progress* nil)  ; 進捗(デバッグ表示)を出力するかどうか

;; 進捗表示
(defun progress (fmt &rest args)
  (when *show-progress*
    (format *error-output* "~V@T; ~?~%" (* *depth* 2) fmt args)))

;;;;;;;;;;;;;;;;
;;; デコード処理

;; ビットコードを読み込む
;;   # 8byte x 4のマジックナンバー: read-magic-numbers関数
;;   # ビットストリーム本体:        read-bit-stream関数
(defun read-bitcode (file &key show-progress)
  (let ((*depth* 0)
        (*show-progress* show-progress))
    (with-input-bit-stream (in file)
      `((:magic-numbers ,(read-magic-numbers in))
        ,@(read-bit-stream in 2 '() (make-hash-table))))))

;; ストリームの先頭のマジックナンバーを読み込む
;;   # 最初の二バイトは0x42('B')と0x43('C')に固定。
;;   # 次の二バイトはアプリケーション定義の値。LLVM-IRなら0xC0と0xDE。
(defun read-magic-numbers (in)
  (let ((nums (loop REPEAT 4 COLLECT (read-fixed-width-int in 8))))
    (progress "magic numbers: ~S" nums)
    nums))

;; ビットストリームを読み込む
;; - ビットストリームは大まかには'ブロック'と'レコード定義'、'レコードデータ'から成る(この三つのシーケンス)
;;
;; 読み込み手順:
;;  1] 次のデータの種類を判定するためのID(abbreviation-ID)を読み込む
;;  2-a] ID値が0〜3の場合は、特別な処理を行う
;;     - END_BLOCK(0): 現在のブロックを抜ける。
;;     - ENTER_SUBBLOCK(1): サブブロックに入る。
;;     - DEFINE_ABBREV(2): abbreviationを定義する。
;;                         個人的にはレコードの型定義と解釈。フィールド数と各フィールドの読み込み方法を指定する。
;;                         定義されたブロック内でのみ使用可能(BLOCK_INFOで定義されたものは別)
;;     - UNABBREV_RECORD(3): 未定義(非省略)のレコードを読み込む。
;;                           定義済みのものとは異なり、レコード識別子とフィールドの数を有し、各フィールドのデータは可変長整数としてエンコードされている
;;                           ※ 定義済みのものは、フィールドのデータのみが、その型に即した形式でエンコードされている
;;  2-b] ID値が4以上の場合は、そのIDに該当する定義済みのレコードを読み込む
;;       ※ レコード定義のIDは、定義された順に自動で、4から昇順の値が振られていく
(defun read-bit-stream (in abbr-width abbrs global-abbrs)
  (flet ((read-next (&optional new-abbr)
           (if (null new-abbr)
               (read-bit-stream in abbr-width abbrs global-abbrs)
             (read-bit-stream in abbr-width (cons new-abbr abbrs) global-abbrs))))
    (let ((abbr-id (read-fixed-width-int in abbr-width)))
      (case abbr-id
        (0 ; BUILT-IN: END_BLOCK
         (read-end-block in))
        (1 ; BUILT-IN: ENTER_SUBBLOCK
         `(,(read-enter-subblock in global-abbrs) ,@(read-next)))
        (2 ; BUILT-IN: DEFIN_ABBREV
         (let ((new-abbr (read-define-abbrev in)))
           `((:define :abbrev ,new-abbr) ,@(read-next new-abbr))))
        (3 ; BUILT-IN: UNABBREV_RECORD
         `((:record :unabbrev ,(read-unabbrev-record in)) ,@(read-next)))
        (otherwise ; Normal abbreviation ID (It is defined by the stream itself)
         `((:record :abbrev ,(read-abbrev-record in abbr-id abbrs)) ,@(read-next)))))))

;; END_BLOCK
;; ブロック終端: 32bit境界に揃える
(defun read-end-block (in)
  (progress "end-block")
  (read-fixed-width-int in 0 :align 32)
  '())

;; UNABBREV_RECORD
;; 未定義(非省略)レコードの読み込み
(defun read-unabbrev-record (in)
  (progress "unabbrev-record:")
  (let* ((code  (read-variable-width-int in 6))  ; レコードの種類を判別するための(?)識別子(コード値)
         (numop (read-variable-width-int in 6))  ; オペランドの数
         (operands (loop REPEAT numop COLLECT (read-variable-width-int in 6))))
    (progress "  CODE=~S, OPERANDS={~{~S~^ ~}}" code operands)
    `(:code ,code :operands ,operands)))

;; 補助関数:
;; BLOCKINFOブロックにて、あらかじめ定義されたblock-idブロック用のレコード定義群を取得する
;; ※ マクロにしているのは、単に汎変数(setf ... )の定義を省くため
(defmacro get-block-abbrevs (block-id global-abbrs)
  `(gethash ,block-id ,global-abbrs))

;; ENTER_SUBBLOCK
;; サブブロックに入る。
;;
;; 最初にサブブロックのIDを取得するが、
;; それが0〜7の場合はあらかじめ予約されたstandard blockとなり、
;; 特別に処理される(?)。
;; ※ 現在はID値が0のBLOCKINFOブロックのみが定義されている
;; 
;; 8以上はアプリケーション定義のブロックとなる。
(defun read-enter-subblock (in global-abbrs)
  (let ((block-id   (read-variable-width-int in 8))           ; ブロックID
        (abbr-width (read-variable-width-int in 4))           ; read-bit-stream関数でabbr-idを取得する際に用いるビット幅
        (block-size (read-fixed-width-int in 32 :align 32)))  ; ブロックのサイズ
    (progress "enter-subblock: ID=~S, SIZE=~S" block-id block-size)
    (let ((*depth* (1+ *depth*)))
      `(:block 
        (:id ,block-id :abbr-width ,abbr-width :block-size ,block-size)
        ,@(case block-id
            (0               ; standard block# BLOCKINFO block
             (progress "BLOCKINFO:")
             (read-blockinfo-block in abbr-width global-abbrs))
            ((1 2 3 4 5 6 7) ; standard blocks# reserved for future use
             (error "BLOCK-ID#~S is being reserved for standard blocks" block-id))
            (otherwise       ; application defined blocks#
             (read-bit-stream in abbr-width 
                                 (get-block-abbrevs block-id global-abbrs)
                                 global-abbrs)))))))

;; BLOCKINFOブロック
;; 
;; ストリームの読み込み方自体は、read-bit-stream関数と同様。
;; ただし、このブロックで読み込んだ情報は、他のブロックからも参照(利用)可能にする必要があるので、
;; その処理を行うために、専用の関数を用意している。
(defun read-blockinfo-block (in abbr-width global-abbrs &optional focused-block)
  (let ((abbr-id (read-fixed-width-int in abbr-width)))
    (ecase abbr-id
      (0 ; BUILD-IN: END_BLOCK
       (read-end-block in))
      (2 ; BUIlD-IN: DEFINE_ABBREV
       (assert (numberp focused-block))
       (let ((new-abbr (read-define-abbrev in)))
         ;; 他のブロックから参照可能なように、定義済みレコードをglobal-abbrsに保存する
         (push new-abbr (get-block-abbrevs focused-block global-abbrs))
         `((:define :abbrev ,new-abbr) 
           ,@(read-blockinfo-block in abbr-width global-abbrs focused-block))))
      (3 ; BUILD-IN: UNABBREV_RECORD
       (let ((rc (read-unabbrev-record in))) ; レコードを読み込む。BLOCKINFOではレコードはただのデータではなく特別な意味を持つ。
         (ecase (getf rc :code)
           (1 ; SETBID: 
              ;; 後続のシーケンスで対象となるブロックを設定する (どのブロック用のレコードを定義しているのか、などを知るため)
              ;; BLOCKINFOブロックでは、まず初めにこのレコードを読み込む必要がある
              ;; 一つのBLOCKINFOブロックに複数回出現しても良い
            (setf focused-block (first (getf rc :operands)))
            (progress "SWITCH FOCUS: BLOCK#~S" focused-block))
           (2 #| BLOCKNAME |#)       ; ブロックの名前を設定する。オプショナル。自分が試したビットコードでは不使用だったので省略。
           (3 #| SETRECORDNAME |#))  ; レコードの名前を設定する。オプショナル。自分が試したビットコードでは不使用だったので省略。
         `((:record :unabbrev ,rc)
           ,@(read-blockinfo-block in abbr-width global-abbrs focused-block)))))))

;; 補助関数:
;; 進捗表示に含まれるオペランド(フィールド)の型情報を若干見やすくする。
(defun format-types (operand-types)
  (mapcar (lambda (type)
            (if (eq (first type) :literal)
                (second type)
              (case (first #1=(second type))
                ((:fixed :vbr) `(,(first #1#) ,(third #1#)))
                (otherwise     (first #1#)))))
          operand-types))

;; DEFINE_ABBREV
;; レコード(の省略)定義を読み込む
(defun read-define-abbrev (in)
  (progress "define-abbrev:")
  (let* ((numops (read-variable-width-int in 5))  ; オペランドの数
         (operand-types (loop REPEAT numops COLLECT (read-operand-type in))))
    (progress "  operand-types: {~{~S~^ ~}}" (format-types operand-types))
    `(:operand-types ,operand-types)))

;; レコードのオペランドの型情報を読み込む
(defun read-operand-type (in)
  (if (= 1 (read-bit in))
      `(:literal ,(read-variable-width-int in 8)) ; リテラル(定数値)
    (let ((code (read-fixed-width-int in 3)))
      `(:enc
        ,(ecase code
           (1 `(:fixed :width ,(read-variable-width-int in 5))) ; fixed-width int
           (2 `(:vbr   :width ,(read-variable-width-int in 5))) ; variable-width int
           (3 `(:array))     ; array. この次には配列の要素の型定義が続く (:arrayはその次の型定義と二つで一セット)
           (4 `(:char6))     ; 6bit-character
           (5 `(:blob))))))) ; blob.

;; 定義済みレコードを読み込む
(defun read-abbrev-record (in abbr-id abbrevs)
  (let ((abbr (get-abbr abbr-id abbrevs)))             ; レコード定義を取得する
    (progress "abbrev-record: ABBREV-ID=~S" abbr-id)
    (let ((operands (read-operands in (second abbr)))) ; 型情報に従ってデータを読み込む
      (progress "  operands: {~{~S~^ ~}}" operands)
      `(:abbr-id ,abbr-id :operands ,operands))))

;; IDに対応するレコード定義を取得する
;; - レコードのIDは4から昇順に、定義された順番に振られる
;; - abbrevsにはレコード定義が逆順に格納されているので、後ろから(- id 4)番目が対応する定義となる
(defun get-abbr (id abbrevs &aux (len (length abbrevs)))
  (nth (- len 1 (- id 4)) abbrevs))

;; 各フィールドのデータを読み込む
(defun read-operands (in types)
  (when types
    (destructuring-bind ((flag datum) . rest) types
      `(,(if (eq flag :literal)
             ; リテラル値: ストリームからの読み込みは不要
             datum     
           ; エンコードされているデータ         
           (ecase (first datum)
             (:fixed (read-fixed-width-int in (third datum)))
             (:vbr   (read-variable-width-int in (third datum)))
             (:array (let ((ary-len (read-variable-width-int in 6))
                           (elem-type (list (first rest))))  ; 配列の場合、次の型情報が配列の要素の型を表す
                       (prog1 (loop REPEAT ary-len 
                                    APPEND (read-operands in elem-type)) ; サイズ分だけデータを読み込む
                         (setf rest (cdr rest)))))
             (:char6 (read-6bit-char in))
             (:blob (let ((len (read-variable-width-int in 6)))
                      (prog2 ;; XXX: 実際のデータで試していないので、この処理であっているかは不明
                          (read-fixed-width-int in 0 :align 32)
                          (loop REPEAT len COLLECT (read-fixed-width-int in 8))
                          (read-fixed-width-int in 0 :align 32))))))
        ,@(read-operands in rest)))))

実行例

ずっと以前に作成したハノイの塔のプログラムで試してみる。

;;;; ファイル名: hanoi.ll
@.MSG = internal constant [10 x i8] c"%c -> %c\0A\00"

declare i32 @atoi(i8*)
declare i32 @printf(i8*, i8, i8)

;; ハノイの塔 : 末尾再帰(?)版
define void @hanoi(i8 %start, i8 %tmp, i8 %dist, i32 %level) {
entry:
  %cond = icmp eq i32 %level, 0
  br i1 %cond, label %end, label %recur
recur:
  %level1 = phi i32 [%level, %entry], [%level2, %recur]
  %start1 = phi i8  [%start, %entry], [%tmp1, %recur]
  %tmp1   = phi i8  [%tmp,   %entry], [%start1, %recur]

  %level2 = sub i32 %level1, 1
  %msg = getelementptr [10 x i8]* @.MSG, i64 0, i64 0

  call void @hanoi(i8 %start1, i8 %dist, i8 %tmp1, i32 %level2)
  call i32 @printf(i8* %msg, i8 %start1, i8 %dist)
  
  ; 再帰的にhanoi関数を呼び出さないで、直接%recurラベルに遷移してしまう(終了ケース以外の場合)
  %cond2 = icmp eq i32 %level2, 0
  br i1 %cond2, label %end, label %recur
  ret void
end:
  ret void
}

;; メイン関数 : コマンドの引数で塔の高さを指定可能なように変更
define i32 @main(i32 %argc, i8** %argv) {
  %argv1 = getelementptr i8** %argv, i32 1
  %num   = load i8** %argv1
  %level = call i32 @atoi(i8* %num)
  call void @hanoi(i8 65, i8 66, i8 67, i32 %level)
  ret i32 0
}

ビットコード生成。

$ llvm-as hanoi.ll -o hanoi.bc

まずは、llvm-bcanalyzerというコマンドのダンプ結果を見てみる。

$ llvm-bcanalyzer -dump hanoi.bc
<MODULE_BLOCK NumWords=144 BlockCodeSize=3>
  <BLOCKINFO_BLOCK/>
  <TYPE_BLOCK NumWords=17 BlockCodeSize=4>
    <NUMENTRY op0=18/>
    <INTEGER op0=8/>
    <INTEGER op0=32/>
    <LABEL/>
    <POINTER abbrevid=4 op0=0 op1=0/>
    <POINTER abbrevid=4 op0=17 op1=0/>
    <POINTER abbrevid=4 op0=3 op1=0/>
    <INTEGER op0=1/>
    <POINTER abbrevid=4 op0=15 op1=0/>
    <POINTER abbrevid=4 op0=12 op1=0/>
    <INTEGER op0=64/>
    <POINTER abbrevid=4 op0=13 op1=0/>
    <POINTER abbrevid=4 op0=14 op1=0/>
    <ARRAY abbrevid=7 op0=10 op1=0/>
    <FUNCTION abbrevid=5 op0=0 op1=0 op2=1 op3=3/>
    <FUNCTION abbrevid=5 op0=0 op1=0 op2=1 op3=3 op4=0 op5=0/>
    <FUNCTION abbrevid=5 op0=0 op1=0 op2=16 op3=0 op4=0 op5=0 op6=1/>
    <VOID/>
    <FUNCTION abbrevid=5 op0=0 op1=0 op2=1 op3=1 op4=5/>
  </TYPE_BLOCK>
  <GLOBALVAR abbrevid=4 op0=8 op1=1 op2=6 op3=3 op4=0 op5=0/>
  <FUNCTION op0=10 op1=0 op2=1 op3=0 op4=0 op5=0 op6=0 op7=0 op8=0/>
  <FUNCTION op0=11 op1=0 op2=1 op3=0 op4=0 op5=0 op6=0 op7=0 op8=0/>
  <FUNCTION op0=7 op1=0 op2=0 op3=0 op4=0 op5=0 op6=0 op7=0 op8=0/>
  <FUNCTION op0=4 op1=0 op2=0 op3=0 op4=0 op5=0 op6=0 op7=0 op8=0/>
  <CONSTANTS_BLOCK NumWords=7 BlockCodeSize=4>
    <SETTYPE abbrevid=4 op0=12/>
    <CSTRING abbrevid=10 op0=37 op1=99 op2=32 op3=45 op4=62 op5=32 op6=37 op7=99 op8=10/>
  </CONSTANTS_BLOCK>
  <FUNCTION_BLOCK NumWords=44 BlockCodeSize=4>
    <DECLAREBLOCKS op0=4/>
    <CONSTANTS_BLOCK NumWords=3 BlockCodeSize=4>
      <SETTYPE abbrevid=4 op0=1/>
      <NULL/>
      <INTEGER abbrevid=5 op0=2/>
      <SETTYPE abbrevid=4 op0=9/>
      <NULL/>
    </CONSTANTS_BLOCK>
    <INST_CMP2 op0=9 op1=10 op2=32/>
    <INST_BR op0=3 op1=1 op2=13/>
    <INST_PHI op0=1 op1=9 op2=0 op3=17 op4=1/>
    <INST_PHI op0=0 op1=6 op2=0 op3=16 op4=1/>
    <INST_PHI op0=0 op1=7 op2=0 op3=15 op4=1/>
    <INST_BINOP abbrevid=5 op0=14 op1=11 op2=1/>
    <INST_GEP op0=0 op1=12 op2=12/>
    <INST_CALL2 op0=0 op1=0 op2=3 op3=15 op4=8 op5=16 op6=17/>
    <INST_CALL2 op0=0 op1=0 op2=2 op3=18 op4=15 op5=8/>
    <INST_CMP2 op0=17 op1=10 op2=32/>
    <INST_BR op0=3 op1=1 op2=20/>
    <INST_RET abbrevid=8/>
    <INST_RET abbrevid=8/>
    <VALUE_SYMTAB NumWords=20 BlockCodeSize=4>
      <BBENTRY abbrevid=7 op0=1 op1=114 op2=101 op3=99 op4=117 op5=114/>
      <ENTRY abbrevid=6 op0=16 op1=116 op2=109 op3=112 op4=49/>
      <ENTRY abbrevid=6 op0=13 op1=99 op2=111 op3=110 op4=100/>
      <ENTRY abbrevid=6 op0=18 op1=109 op2=115 op3=103/>
      <ENTRY abbrevid=6 op0=14 op1=108 op2=101 op3=118 op4=101 op5=108 op6=49/>
      <ENTRY abbrevid=6 op0=17 op1=108 op2=101 op3=118 op4=101 op5=108 op6=50/>
      <ENTRY abbrevid=6 op0=6 op1=115 op2=116 op3=97 op4=114 op5=116/>
      <ENTRY abbrevid=6 op0=7 op1=116 op2=109 op3=112/>
      <BBENTRY abbrevid=7 op0=0 op1=101 op2=110 op3=116 op4=114 op5=121/>
      <ENTRY abbrevid=6 op0=8 op1=100 op2=105 op3=115 op4=116/>
      <ENTRY abbrevid=6 op0=20 op1=99 op2=111 op3=110 op4=100 op5=50/>
      <BBENTRY abbrevid=7 op0=3 op1=101 op2=110 op3=100/>
      <ENTRY abbrevid=6 op0=9 op1=108 op2=101 op3=118 op4=101 op5=108/>
      <ENTRY abbrevid=6 op0=15 op1=115 op2=116 op3=97 op4=114 op5=116 op6=49/>
    </VALUE_SYMTAB>
  </FUNCTION_BLOCK>
  <FUNCTION_BLOCK NumWords=22 BlockCodeSize=4>
    <DECLAREBLOCKS op0=1/>
    <CONSTANTS_BLOCK NumWords=4 BlockCodeSize=4>
      <SETTYPE abbrevid=4 op0=0/>
      <INTEGER abbrevid=5 op0=130/>
      <INTEGER abbrevid=5 op0=132/>
      <INTEGER abbrevid=5 op0=134/>
      <SETTYPE abbrevid=4 op0=1/>
      <INTEGER abbrevid=5 op0=2/>
      <NULL/>
    </CONSTANTS_BLOCK>
    <INST_GEP op0=7 op1=11/>
    <INST_LOAD abbrevid=4 op0=13 op1=0 op2=0/>
    <INST_CALL2 op0=0 op1=0 op2=1 op3=14/>
    <INST_CALL2 op0=0 op1=0 op2=3 op3=8 op4=9 op5=10 op6=15/>
    <INST_RET abbrevid=9 op0=12/>
    <VALUE_SYMTAB NumWords=7 BlockCodeSize=4>
      <ENTRY abbrevid=6 op0=7 op1=97 op2=114 op3=103 op4=118/>
      <ENTRY abbrevid=6 op0=13 op1=97 op2=114 op3=103 op4=118 op5=49/>
      <ENTRY abbrevid=6 op0=14 op1=110 op2=117 op3=109/>
      <ENTRY abbrevid=6 op0=15 op1=108 op2=101 op3=118 op4=101 op5=108/>
      <ENTRY abbrevid=6 op0=6 op1=97 op2=114 op3=103 op4=99/>
    </VALUE_SYMTAB>
  </FUNCTION_BLOCK>
  <METADATA_BLOCK NumWords=2 BlockCodeSize=3>
    <METADATA_KIND op0=0 op1=100 op2=98 op3=103/>
  </METADATA_BLOCK>
  <VALUE_SYMTAB NumWords=8 BlockCodeSize=4>
    <ENTRY abbrevid=6 op0=2 op1=112 op2=114 op3=105 op4=110 op5=116 op6=102/>
    <ENTRY abbrevid=6 op0=0 op1=46 op2=77 op3=83 op4=71/>
    <ENTRY abbrevid=6 op0=4 op1=109 op2=97 op3=105 op4=110/>
    <ENTRY abbrevid=6 op0=1 op1=97 op2=116 op3=111 op4=105/>
    <ENTRY abbrevid=6 op0=3 op1=104 op2=97 op3=110 op4=111 op5=105/>
  </VALUE_SYMTAB>
</MODULE_BLOCK>

Summary of hanoi.bc:
         Total size: 4704b/588.00B/147W
        Stream type: LLVM IR
  # Toplevel Blocks: 1

Per-block Summary:
  Block ID #0 (BLOCKINFO_BLOCK):
      Num Instances: 1
         Total Size: 637b/79.62B/19W
    Percent of file: 13.5417%
      Num SubBlocks: 0
        Num Abbrevs: 0
        Num Records: 0

  Block ID #8 (MODULE_BLOCK):
      Num Instances: 1
         Total Size: 480b/60.00B/15W
    Percent of file: 10.2041%
      Num SubBlocks: 7
        Num Abbrevs: 1
        Num Records: 5
    Percent Abbrevs: 20.0000%

	Record Histogram:
		  Count    # Bits   % Abv  Record Kind
		      4       276          FUNCTION
		      1        18  100.00  GLOBALVAR

  Block ID #10 (TYPE_BLOCK):
      Num Instances: 1
         Total Size: 605b/75.62B/18W
    Percent of file: 12.8614%
      Num SubBlocks: 0
        Num Abbrevs: 4
        Num Records: 19
    Percent Abbrevs: 63.1579%

	Record Histogram:
		  Count    # Bits   % Abv  Record Kind
		      7        63  100.00  POINTER
		      4       114  100.00  FUNCTION
		      4       100          INTEGER
		      1        17  100.00  ARRAY
		      1        16          LABEL
		      1        16          VOID
		      1        22          NUMENTRY

  Block ID #11 (CONSTANTS_BLOCK):
      Num Instances: 3
         Total Size: 636b/79.50B/19W
    Percent of file: 13.5204%
       Average Size: 212.00/26.50B/6W
  Tot/Avg SubBlocks: 0/0.000000e+00
    Tot/Avg Abbrevs: 4/1.333333e+00
    Tot/Avg Records: 14/4.666667e+00
    Percent Abbrevs: 78.5714%

	Record Histogram:
		  Count    # Bits   % Abv  Record Kind
		      5        84  100.00  INTEGER
		      5        45  100.00  SETTYPE
		      3        48          NULL
		      1        73  100.00  CSTRING

  Block ID #12 (FUNCTION_BLOCK):
      Num Instances: 2
         Total Size: 879b/109.88B/27W
    Percent of file: 18.6862%
       Average Size: 439.50/54.94B/13W
  Tot/Avg SubBlocks: 4/2.000000e+00
    Tot/Avg Abbrevs: 0/0.000000e+00
    Tot/Avg Records: 20/1.000000e+01
    Percent Abbrevs: 25.0000%

	Record Histogram:
		  Count    # Bits   % Abv  Record Kind
		      4       232          INST_CALL2
		      3       138          INST_PHI
		      3        18  100.00  INST_RET
		      2        80          INST_CMP2
		      2        68          INST_BR
		      2        62          INST_GEP
		      2        44          DECLAREBLOCKS
		      1        15  100.00  INST_LOAD
		      1        20  100.00  INST_BINOP

  Block ID #14 (VALUE_SYMTAB):
      Num Instances: 3
         Total Size: 1308b/163.50B/40W
    Percent of file: 27.8061%
       Average Size: 436.00/54.50B/13W
  Tot/Avg SubBlocks: 0/0.000000e+00
    Tot/Avg Abbrevs: 0/0.000000e+00
    Tot/Avg Records: 24/8.000000e+00
    Percent Abbrevs: 100.0000%

	Record Histogram:
		  Count    # Bits   % Abv  Record Kind
		     21       948  100.00  ENTRY
		      3       132  100.00  BBENTRY

  Block ID #15 (METADATA_BLOCK):
      Num Instances: 1
         Total Size: 125b/15.62B/3W
    Percent of file: 2.6573%
      Num SubBlocks: 0
        Num Abbrevs: 0
        Num Records: 1
    Percent Abbrevs: 0.0000%

	Record Histogram:
		  Count    # Bits   % Abv  Record Kind
		      1        57          METADATA_KIND

次に今回作成した関数の実行結果。

(read-bitcode "hanoi.bc" :show-progress t)
; magic numbers: (66 67 192 222)
; enter-subblock: ID=8, SIZE=144
  ; enter-subblock: ID=0, SIZE=18
    ; BLOCKINFO:
    ; unabbrev-record:
    ;   CODE=1, OPERANDS={14}
    ; SWITCH FOCUS: BLOCK#14
    ; define-abbrev:
    ;   operand-types: {(:FIXED 3) (:VBR 8) :ARRAY (:FIXED 8)}
    ; define-abbrev:
    ;   operand-types: {1 (:VBR 8) :ARRAY (:FIXED 7)}
    ; define-abbrev:
    ;   operand-types: {1 (:VBR 8) :ARRAY :CHAR6}
    ; define-abbrev:
    ;   operand-types: {2 (:VBR 8) :ARRAY :CHAR6}
    ; unabbrev-record:
    ;   CODE=1, OPERANDS={11}
    ; SWITCH FOCUS: BLOCK#11
    ; define-abbrev:
    ;   operand-types: {1 (:FIXED 5)}
    ; define-abbrev:
    ;   operand-types: {4 (:VBR 8)}
    ; define-abbrev:
    ;   operand-types: {11 (:FIXED 4) (:FIXED 5) (:VBR 8)}
    ; define-abbrev:
    ;   operand-types: {2}
    ; unabbrev-record:
    ;   CODE=1, OPERANDS={12}
    ; SWITCH FOCUS: BLOCK#12
    ; define-abbrev:
    ;   operand-types: {20 (:VBR 6) (:VBR 4) (:FIXED 1)}
    ; define-abbrev:
    ;   operand-types: {2 (:VBR 6) (:VBR 6) (:FIXED 4)}
    ; define-abbrev:
    ;   operand-types: {2 (:VBR 6) (:VBR 6) (:FIXED 4) (:FIXED 7)}
    ; define-abbrev:
    ;   operand-types: {3 (:VBR 6) (:FIXED 5) (:FIXED 4)}
    ; define-abbrev:
    ;   operand-types: {10}
    ; define-abbrev:
    ;   operand-types: {10 (:VBR 6)}
    ; define-abbrev:
    ;   operand-types: {15}
    ; end-block
  ; enter-subblock: ID=10, SIZE=17
    ; define-abbrev:
    ;   operand-types: {8 (:FIXED 5) 0}
    ; define-abbrev:
    ;   operand-types: {9 (:FIXED 1) 0 :ARRAY (:FIXED 5)}
    ; define-abbrev:
    ;   operand-types: {10 (:FIXED 1) :ARRAY (:FIXED 5)}
    ; define-abbrev:
    ;   operand-types: {11 (:VBR 8) (:FIXED 5)}
    ; unabbrev-record:
    ;   CODE=1, OPERANDS={18}
    ; unabbrev-record:
    ;   CODE=7, OPERANDS={8}
    ; unabbrev-record:
    ;   CODE=7, OPERANDS={32}
    ; unabbrev-record:
    ;   CODE=5, OPERANDS={}
    ; abbrev-record: ABBREV-ID=4
    ;   operands: {8 0 0}
    ; abbrev-record: ABBREV-ID=4
    ;   operands: {8 17 0}
    ; abbrev-record: ABBREV-ID=4
    ;   operands: {8 3 0}
    ; unabbrev-record:
    ;   CODE=7, OPERANDS={1}
    ; abbrev-record: ABBREV-ID=4
    ;   operands: {8 15 0}
    ; abbrev-record: ABBREV-ID=4
    ;   operands: {8 12 0}
    ; unabbrev-record:
    ;   CODE=7, OPERANDS={64}
    ; abbrev-record: ABBREV-ID=4
    ;   operands: {8 13 0}
    ; abbrev-record: ABBREV-ID=4
    ;   operands: {8 14 0}
    ; abbrev-record: ABBREV-ID=7
    ;   operands: {11 10 0}
    ; abbrev-record: ABBREV-ID=5
    ;   operands: {9 0 0 (1 3)}
    ; abbrev-record: ABBREV-ID=5
    ;   operands: {9 0 0 (1 3 0 0)}
    ; abbrev-record: ABBREV-ID=5
    ;   operands: {9 0 0 (16 0 0 0 1)}
    ; unabbrev-record:
    ;   CODE=2, OPERANDS={}
    ; abbrev-record: ABBREV-ID=5
    ;   operands: {9 0 0 (1 1 5)}
    ; end-block
  ; define-abbrev:
  ;   operand-types: {7 (:FIXED 4) (:FIXED 1) (:VBR 6) (:FIXED 4) 0 0}
  ; abbrev-record: ABBREV-ID=4
  ;   operands: {7 8 1 6 3 0 0}
  ; unabbrev-record:
  ;   CODE=8, OPERANDS={10 0 1 0 0 0 0 0 0}
  ; unabbrev-record:
  ;   CODE=8, OPERANDS={11 0 1 0 0 0 0 0 0}
  ; unabbrev-record:
  ;   CODE=8, OPERANDS={7 0 0 0 0 0 0 0 0}
  ; unabbrev-record:
  ;   CODE=8, OPERANDS={4 0 0 0 0 0 0 0 0}
  ; enter-subblock: ID=11, SIZE=7
    ; define-abbrev:
    ;   operand-types: {7 :ARRAY (:FIXED 3)}
    ; define-abbrev:
    ;   operand-types: {8 :ARRAY (:FIXED 8)}
    ; define-abbrev:
    ;   operand-types: {9 :ARRAY (:FIXED 7)}
    ; define-abbrev:
    ;   operand-types: {9 :ARRAY :CHAR6}
    ; abbrev-record: ABBREV-ID=4
    ;   operands: {1 12}
    ; abbrev-record: ABBREV-ID=10
    ;   operands: {9 (37 99 32 45 62 32 37 99 10)}
    ; end-block
  ; enter-subblock: ID=12, SIZE=44
    ; unabbrev-record:
    ;   CODE=1, OPERANDS={4}
    ; enter-subblock: ID=11, SIZE=3
      ; abbrev-record: ABBREV-ID=4
      ;   operands: {1 1}
      ; unabbrev-record:
      ;   CODE=2, OPERANDS={}
      ; abbrev-record: ABBREV-ID=5
      ;   operands: {4 2}
      ; abbrev-record: ABBREV-ID=4
      ;   operands: {1 9}
      ; unabbrev-record:
      ;   CODE=2, OPERANDS={}
      ; end-block
    ; unabbrev-record:
    ;   CODE=28, OPERANDS={9 10 32}
    ; unabbrev-record:
    ;   CODE=11, OPERANDS={3 1 13}
    ; unabbrev-record:
    ;   CODE=16, OPERANDS={1 9 0 17 1}
    ; unabbrev-record:
    ;   CODE=16, OPERANDS={0 6 0 16 1}
    ; unabbrev-record:
    ;   CODE=16, OPERANDS={0 7 0 15 1}
    ; abbrev-record: ABBREV-ID=5
    ;   operands: {2 14 11 1}
    ; unabbrev-record:
    ;   CODE=4, OPERANDS={0 12 12}
    ; unabbrev-record:
    ;   CODE=34, OPERANDS={0 0 3 15 8 16 17}
    ; unabbrev-record:
    ;   CODE=34, OPERANDS={0 0 2 18 15 8}
    ; unabbrev-record:
    ;   CODE=28, OPERANDS={17 10 32}
    ; unabbrev-record:
    ;   CODE=11, OPERANDS={3 1 20}
    ; abbrev-record: ABBREV-ID=8
    ;   operands: {10}
    ; abbrev-record: ABBREV-ID=8
    ;   operands: {10}
    ; enter-subblock: ID=14, SIZE=20
      ; abbrev-record: ABBREV-ID=7
      ;   operands: {2 1 (#\r #\e #\c #\u #\r)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 16 (#\t #\m #\p #\e)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 13 (#\c #\o #\n #\d)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 18 (#\m #\s #\g)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 14 (#\l #\e #\v #\e #\l #\e)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 17 (#\l #\e #\v #\e #\l #\f)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 6 (#\s #\t #\a #\r #\t)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 7 (#\t #\m #\p)}
      ; abbrev-record: ABBREV-ID=7
      ;   operands: {2 0 (#\e #\n #\t #\r #\y)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 8 (#\d #\i #\s #\t)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 20 (#\c #\o #\n #\d #\f)}
      ; abbrev-record: ABBREV-ID=7
      ;   operands: {2 3 (#\e #\n #\d)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 9 (#\l #\e #\v #\e #\l)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 15 (#\s #\t #\a #\r #\t #\e)}
      ; end-block
    ; end-block
  ; enter-subblock: ID=12, SIZE=22
    ; unabbrev-record:
    ;   CODE=1, OPERANDS={1}
    ; enter-subblock: ID=11, SIZE=4
      ; abbrev-record: ABBREV-ID=4
      ;   operands: {1 0}
      ; abbrev-record: ABBREV-ID=5
      ;   operands: {4 130}
      ; abbrev-record: ABBREV-ID=5
      ;   operands: {4 132}
      ; abbrev-record: ABBREV-ID=5
      ;   operands: {4 134}
      ; abbrev-record: ABBREV-ID=4
      ;   operands: {1 1}
      ; abbrev-record: ABBREV-ID=5
      ;   operands: {4 2}
      ; unabbrev-record:
      ;   CODE=2, OPERANDS={}
      ; end-block
    ; unabbrev-record:
    ;   CODE=4, OPERANDS={7 11}
    ; abbrev-record: ABBREV-ID=4
    ;   operands: {20 13 0 0}
    ; unabbrev-record:
    ;   CODE=34, OPERANDS={0 0 1 14}
    ; unabbrev-record:
    ;   CODE=34, OPERANDS={0 0 3 8 9 10 15}
    ; abbrev-record: ABBREV-ID=9
    ;   operands: {10 12}
    ; enter-subblock: ID=14, SIZE=7
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 7 (#\a #\r #\g #\v)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 13 (#\a #\r #\g #\v #\e)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 14 (#\n #\u #\m)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 15 (#\l #\e #\v #\e #\l)}
      ; abbrev-record: ABBREV-ID=6
      ;   operands: {1 6 (#\a #\r #\g #\c)}
      ; end-block
    ; end-block
  ; enter-subblock: ID=15, SIZE=2
    ; unabbrev-record:
    ;   CODE=6, OPERANDS={0 100 98 103}
    ; end-block
  ; enter-subblock: ID=14, SIZE=8
    ; abbrev-record: ABBREV-ID=6
    ;   operands: {1 2 (#\p #\r #\i #\n #\t #\f)}
    ; abbrev-record: ABBREV-ID=6
    ;   operands: {1 0 (#\. #\g #\m #\a)}
    ; abbrev-record: ABBREV-ID=6
    ;   operands: {1 4 (#\m #\a #\i #\n)}
    ; abbrev-record: ABBREV-ID=6
    ;   operands: {1 1 (#\a #\t #\o #\i)}
    ; abbrev-record: ABBREV-ID=6
    ;   operands: {1 3 (#\h #\a #\n #\o #\i)}
    ; end-block
  ; end-block
; end-block

((:MAGIC-NUMBERS (66 67 192 222))
 (:BLOCK (:ID 8 :ABBR-WIDTH 3 :BLOCK-SIZE 144)
  (:BLOCK (:ID 0 :ABBR-WIDTH 2 :BLOCK-SIZE 18)
   (:RECORD :UNABBREV (:CODE 1 :OPERANDS (14)))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES
     ((:ENC (:FIXED :WIDTH 3)) (:ENC (:VBR :WIDTH 8)) (:ENC (:ARRAY))
      (:ENC (:FIXED :WIDTH 8)))))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES
     ((:LITERAL 1) (:ENC (:VBR :WIDTH 8)) (:ENC (:ARRAY))
      (:ENC (:FIXED :WIDTH 7)))))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES
     ((:LITERAL 1) (:ENC (:VBR :WIDTH 8)) (:ENC (:ARRAY)) (:ENC (:CHAR6)))))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES
     ((:LITERAL 2) (:ENC (:VBR :WIDTH 8)) (:ENC (:ARRAY)) (:ENC (:CHAR6)))))
   (:RECORD :UNABBREV (:CODE 1 :OPERANDS (11)))
   (:DEFINE :ABBREV (:OPERAND-TYPES ((:LITERAL 1) (:ENC (:FIXED :WIDTH 5)))))
   (:DEFINE :ABBREV (:OPERAND-TYPES ((:LITERAL 4) (:ENC (:VBR :WIDTH 8)))))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES
     ((:LITERAL 11) (:ENC (:FIXED :WIDTH 4)) (:ENC (:FIXED :WIDTH 5))
      (:ENC (:VBR :WIDTH 8)))))
   (:DEFINE :ABBREV (:OPERAND-TYPES ((:LITERAL 2))))
   (:RECORD :UNABBREV (:CODE 1 :OPERANDS (12)))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES
     ((:LITERAL 20) (:ENC (:VBR :WIDTH 6)) (:ENC (:VBR :WIDTH 4))
      (:ENC (:FIXED :WIDTH 1)))))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES
     ((:LITERAL 2) (:ENC (:VBR :WIDTH 6)) (:ENC (:VBR :WIDTH 6))
      (:ENC (:FIXED :WIDTH 4)))))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES
     ((:LITERAL 2) (:ENC (:VBR :WIDTH 6)) (:ENC (:VBR :WIDTH 6))
      (:ENC (:FIXED :WIDTH 4)) (:ENC (:FIXED :WIDTH 7)))))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES
     ((:LITERAL 3) (:ENC (:VBR :WIDTH 6)) (:ENC (:FIXED :WIDTH 5))
      (:ENC (:FIXED :WIDTH 4)))))
   (:DEFINE :ABBREV (:OPERAND-TYPES ((:LITERAL 10))))
   (:DEFINE :ABBREV (:OPERAND-TYPES ((:LITERAL 10) (:ENC (:VBR :WIDTH 6)))))
   (:DEFINE :ABBREV (:OPERAND-TYPES ((:LITERAL 15)))))
  (:BLOCK (:ID 10 :ABBR-WIDTH 4 :BLOCK-SIZE 17)
   (:DEFINE :ABBREV
    (:OPERAND-TYPES ((:LITERAL 8) (:ENC (:FIXED :WIDTH 5)) (:LITERAL 0))))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES
     ((:LITERAL 9) (:ENC (:FIXED :WIDTH 1)) (:LITERAL 0) (:ENC (:ARRAY))
      (:ENC (:FIXED :WIDTH 5)))))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES
     ((:LITERAL 10) (:ENC (:FIXED :WIDTH 1)) (:ENC (:ARRAY))
      (:ENC (:FIXED :WIDTH 5)))))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES
     ((:LITERAL 11) (:ENC (:VBR :WIDTH 8)) (:ENC (:FIXED :WIDTH 5)))))
   (:RECORD :UNABBREV (:CODE 1 :OPERANDS (18)))
   (:RECORD :UNABBREV (:CODE 7 :OPERANDS (8)))
   (:RECORD :UNABBREV (:CODE 7 :OPERANDS (32)))
   (:RECORD :UNABBREV (:CODE 5 :OPERANDS NIL))
   (:RECORD :ABBREV (:ABBR-ID 4 :OPERANDS (8 0 0)))
   (:RECORD :ABBREV (:ABBR-ID 4 :OPERANDS (8 17 0)))
   (:RECORD :ABBREV (:ABBR-ID 4 :OPERANDS (8 3 0)))
   (:RECORD :UNABBREV (:CODE 7 :OPERANDS (1)))
   (:RECORD :ABBREV (:ABBR-ID 4 :OPERANDS (8 15 0)))
   (:RECORD :ABBREV (:ABBR-ID 4 :OPERANDS (8 12 0)))
   (:RECORD :UNABBREV (:CODE 7 :OPERANDS (64)))
   (:RECORD :ABBREV (:ABBR-ID 4 :OPERANDS (8 13 0)))
   (:RECORD :ABBREV (:ABBR-ID 4 :OPERANDS (8 14 0)))
   (:RECORD :ABBREV (:ABBR-ID 7 :OPERANDS (11 10 0)))
   (:RECORD :ABBREV (:ABBR-ID 5 :OPERANDS (9 0 0 (1 3))))
   (:RECORD :ABBREV (:ABBR-ID 5 :OPERANDS (9 0 0 (1 3 0 0))))
   (:RECORD :ABBREV (:ABBR-ID 5 :OPERANDS (9 0 0 (16 0 0 0 1))))
   (:RECORD :UNABBREV (:CODE 2 :OPERANDS NIL))
   (:RECORD :ABBREV (:ABBR-ID 5 :OPERANDS (9 0 0 (1 1 5)))))
  (:DEFINE :ABBREV
   (:OPERAND-TYPES
    ((:LITERAL 7) (:ENC (:FIXED :WIDTH 4)) (:ENC (:FIXED :WIDTH 1))
     (:ENC (:VBR :WIDTH 6)) (:ENC (:FIXED :WIDTH 4)) (:LITERAL 0)
     (:LITERAL 0))))
  (:RECORD :ABBREV (:ABBR-ID 4 :OPERANDS (7 8 1 6 3 0 0)))
  (:RECORD :UNABBREV (:CODE 8 :OPERANDS (10 0 1 0 0 0 0 0 0)))
  (:RECORD :UNABBREV (:CODE 8 :OPERANDS (11 0 1 0 0 0 0 0 0)))
  (:RECORD :UNABBREV (:CODE 8 :OPERANDS (7 0 0 0 0 0 0 0 0)))
  (:RECORD :UNABBREV (:CODE 8 :OPERANDS (4 0 0 0 0 0 0 0 0)))
  (:BLOCK (:ID 11 :ABBR-WIDTH 4 :BLOCK-SIZE 7)
   (:DEFINE :ABBREV
    (:OPERAND-TYPES ((:LITERAL 7) (:ENC (:ARRAY)) (:ENC (:FIXED :WIDTH 3)))))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES ((:LITERAL 8) (:ENC (:ARRAY)) (:ENC (:FIXED :WIDTH 8)))))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES ((:LITERAL 9) (:ENC (:ARRAY)) (:ENC (:FIXED :WIDTH 7)))))
   (:DEFINE :ABBREV
    (:OPERAND-TYPES ((:LITERAL 9) (:ENC (:ARRAY)) (:ENC (:CHAR6)))))
   (:RECORD :ABBREV (:ABBR-ID 4 :OPERANDS (1 12)))
   (:RECORD :ABBREV (:ABBR-ID 10 :OPERANDS (9 (37 99 32 45 62 32 37 99 10)))))
  (:BLOCK (:ID 12 :ABBR-WIDTH 4 :BLOCK-SIZE 44)
   (:RECORD :UNABBREV (:CODE 1 :OPERANDS (4)))
   (:BLOCK (:ID 11 :ABBR-WIDTH 4 :BLOCK-SIZE 3)
    (:RECORD :ABBREV (:ABBR-ID 4 :OPERANDS (1 1)))
    (:RECORD :UNABBREV (:CODE 2 :OPERANDS NIL))
    (:RECORD :ABBREV (:ABBR-ID 5 :OPERANDS (4 2)))
    (:RECORD :ABBREV (:ABBR-ID 4 :OPERANDS (1 9)))
    (:RECORD :UNABBREV (:CODE 2 :OPERANDS NIL)))
   (:RECORD :UNABBREV (:CODE 28 :OPERANDS (9 10 32)))
   (:RECORD :UNABBREV (:CODE 11 :OPERANDS (3 1 13)))
   (:RECORD :UNABBREV (:CODE 16 :OPERANDS (1 9 0 17 1)))
   (:RECORD :UNABBREV (:CODE 16 :OPERANDS (0 6 0 16 1)))
   (:RECORD :UNABBREV (:CODE 16 :OPERANDS (0 7 0 15 1)))
   (:RECORD :ABBREV (:ABBR-ID 5 :OPERANDS (2 14 11 1)))
   (:RECORD :UNABBREV (:CODE 4 :OPERANDS (0 12 12)))
   (:RECORD :UNABBREV (:CODE 34 :OPERANDS (0 0 3 15 8 16 17)))
   (:RECORD :UNABBREV (:CODE 34 :OPERANDS (0 0 2 18 15 8)))
   (:RECORD :UNABBREV (:CODE 28 :OPERANDS (17 10 32)))
   (:RECORD :UNABBREV (:CODE 11 :OPERANDS (3 1 20)))
   (:RECORD :ABBREV (:ABBR-ID 8 :OPERANDS (10)))
   (:RECORD :ABBREV (:ABBR-ID 8 :OPERANDS (10)))
   (:BLOCK (:ID 14 :ABBR-WIDTH 4 :BLOCK-SIZE 20)
    (:RECORD :ABBREV (:ABBR-ID 7 :OPERANDS (2 1 (#\r #\e #\c #\u #\r))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 16 (#\t #\m #\p #\e))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 13 (#\c #\o #\n #\d))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 18 (#\m #\s #\g))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 14 (#\l #\e #\v #\e #\l #\e))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 17 (#\l #\e #\v #\e #\l #\f))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 6 (#\s #\t #\a #\r #\t))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 7 (#\t #\m #\p))))
    (:RECORD :ABBREV (:ABBR-ID 7 :OPERANDS (2 0 (#\e #\n #\t #\r #\y))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 8 (#\d #\i #\s #\t))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 20 (#\c #\o #\n #\d #\f))))
    (:RECORD :ABBREV (:ABBR-ID 7 :OPERANDS (2 3 (#\e #\n #\d))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 9 (#\l #\e #\v #\e #\l))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 15 (#\s #\t #\a #\r #\t #\e))))))
  (:BLOCK (:ID 12 :ABBR-WIDTH 4 :BLOCK-SIZE 22)
   (:RECORD :UNABBREV (:CODE 1 :OPERANDS (1)))
   (:BLOCK (:ID 11 :ABBR-WIDTH 4 :BLOCK-SIZE 4)
    (:RECORD :ABBREV (:ABBR-ID 4 :OPERANDS (1 0)))
    (:RECORD :ABBREV (:ABBR-ID 5 :OPERANDS (4 130)))
    (:RECORD :ABBREV (:ABBR-ID 5 :OPERANDS (4 132)))
    (:RECORD :ABBREV (:ABBR-ID 5 :OPERANDS (4 134)))
    (:RECORD :ABBREV (:ABBR-ID 4 :OPERANDS (1 1)))
    (:RECORD :ABBREV (:ABBR-ID 5 :OPERANDS (4 2)))
    (:RECORD :UNABBREV (:CODE 2 :OPERANDS NIL)))
   (:RECORD :UNABBREV (:CODE 4 :OPERANDS (7 11)))
   (:RECORD :ABBREV (:ABBR-ID 4 :OPERANDS (20 13 0 0)))
   (:RECORD :UNABBREV (:CODE 34 :OPERANDS (0 0 1 14)))
   (:RECORD :UNABBREV (:CODE 34 :OPERANDS (0 0 3 8 9 10 15)))
   (:RECORD :ABBREV (:ABBR-ID 9 :OPERANDS (10 12)))
   (:BLOCK (:ID 14 :ABBR-WIDTH 4 :BLOCK-SIZE 7)
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 7 (#\a #\r #\g #\v))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 13 (#\a #\r #\g #\v #\e))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 14 (#\n #\u #\m))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 15 (#\l #\e #\v #\e #\l))))
    (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 6 (#\a #\r #\g #\c))))))
  (:BLOCK (:ID 15 :ABBR-WIDTH 3 :BLOCK-SIZE 2)
   (:RECORD :UNABBREV (:CODE 6 :OPERANDS (0 100 98 103))))
  (:BLOCK (:ID 14 :ABBR-WIDTH 4 :BLOCK-SIZE 8)
   (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 2 (#\p #\r #\i #\n #\t #\f))))
   (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 0 (#\. #\g #\m #\a))))
   (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 4 (#\m #\a #\i #\n))))
   (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 1 (#\a #\t #\o #\i))))
   (:RECORD :ABBREV (:ABBR-ID 6 :OPERANDS (1 3 (#\h #\a #\n #\o #\i)))))))

この例に関しては、ちゃんと読み込めてるっぽい。
まだバグがありそうだけど ... 。

*1:フィールドの数と各フィールドのデータの型

*2:パースするだけなら、これでも特に問題はない。パースした結果得られる(後述の)ブロックやレコードなどにアプリケーション定義の情報(名前など)を付与することができないので、表示が見にくくはなるけど。

*3:実装は極めて非効率