← すべての記事

goplan9assemblycompileros

図で理解する Plan 9 アセンブリと Go の中の Plan 9

Goで .s ファイルを書くと、TEXT ·Add(SB), NOSPLIT, $0-24 のような見慣れない記法に出会います。これは「Plan 9というOSそのものを使っている」という意味ではなく、Plan 9系アセンブラの入力スタイルを受け継いだ、Go専用アセンブラを使うという意味です。[^go-asm]

Goで .s ファイルを書くと、TEXT ·Add(SB), NOSPLIT, $0-24 のような見慣れない記法に出会います。これは「Plan 9というOSそのものを使っている」という意味ではなく、Plan 9系アセンブラの入力スタイルを受け継いだ、Go専用アセンブラを使うという意味です。[^go-asm]

この記事では、細かい命令表よりも「なぜそう見えるのか」を図でつかみます。

この記事の地図

flowchart TD
  A["Plan 9 の歴史"] --> B["Plan 9 の設計思想"]
  B --> C["Plan 9 アセンブラ"]
  C --> D["Go のアセンブラ"]
  D --> E["TEXT / SB / FP / SP"]
  E --> F["Go から呼ぶ小さな例"]
  F --> G["今の Plan 9 と学びどころ"]

Plan 9とは

Plan 9は、UnixやC言語を生んだBell Labsで1980年代後半から開発された研究用OSです。中心にある発想は「分散環境でも、資源をファイルのように扱えるようにする」ことです。プロセスごとの名前空間、9P、UTF-8、rioacme などが特徴です。[^plan9-overview]

flowchart LR
  U["Unix の経験"] --> P["Plan 9"]
  P --> N["プロセスごとの名前空間"]
  P --> F["資源をファイルとして見せる"]
  P --> P9["9P: ファイルプロトコル"]
  P --> T["UTF-8 をシステム全体へ"]
  P --> W["rio / acme などの環境"]

歴史をざっくり見る

Plan 9は「Unixの後継OS」というより、Unixで得たアイデアをネットワーク時代に向けて再設計した実験場でした。

flowchart LR
  A["1960s-70s\nUnix"] --> B["late 1980s\nPlan 9 開発開始"]
  B --> C["1992\n第1版"]
  C --> D["1995\n第2版"]
  D --> E["2000\n第3版\nWebで配布"]
  E --> F["2002\n第4版\n9P2000"]
  F --> G["2021\nPlan 9 Foundation\nMIT License"]

UnixとPlan 9の見方の違い

Unixも「everything is a file」と言われますが、Plan 9はその考えをネットワーク、ウィンドウ、プロセス、認証などへさらに押し広げます。

flowchart TB
  subgraph U["Unix 的な見方"]
    U1["ローカルファイル"] --> U2["プロセス"]
    U2 --> U3["ソケット"]
    U2 --> U4["端末"]
  end

  subgraph P["Plan 9 的な見方"]
    P1["/net"] --> P2["ネットワーク"]
    P3["/dev"] --> P4["デバイス"]
    P5["/mnt/wsys"] --> P6["ウィンドウ"]
    P7["9P server"] --> P8["リモート資源"]
  end

名前空間がプロセスごとに違う

Plan 9では、各プロセスが自分用の名前空間を持てます。同じ /mnt/data でも、プロセスAとBで別のファイルサーバを見せられます。

flowchart LR
  subgraph A["プロセスAの名前空間"]
    A1["/mnt/data"] --> A2["File Server A"]
    A3["/net"] --> A4["local network"]
  end

  subgraph B["プロセスBの名前空間"]
    B1["/mnt/data"] --> B2["File Server B"]
    B3["/net"] --> B4["remote network"]
  end

9Pの役割

9Pは「ファイルサーバと話すための小さな共通語」です。ローカルでもリモートでも、同じように open/read/write の感覚で扱えるのがPlan 9らしさです。

flowchart TD
  C["Client process"] -->|"walk / open / read / write"| P9["9P"]
  P9 --> FS["File server"]
  P9 --> NET["Network service"]
  P9 --> WIN["Window system"]
  P9 --> TIME["Dump / history"]

  FS --> V1["ファイルに見える"]
  NET --> V2["ファイルに見える"]
  WIN --> V3["ファイルに見える"]
  TIME --> V4["ファイルに見える"]

Plan 9は今どうなったのか

Plan 9は主流デスクトップOSにはなりませんでした。一方で、公式系のコードはPlan 9 Foundationへ移り、2021年以降はMIT Licenseで公開されています。さらに、9frontなどのコミュニティフォークも継続しています。[^plan9-overview] [^ninefront]

flowchart TD
  A["Plan 9 from Bell Labs"] --> B["Plan 9 Foundation"]
  B --> C["MIT License"]
  A --> D["9legacy"]
  A --> E["9front"]
  A --> F["研究・趣味・教育"]
  A --> G["思想の継承"]

  G --> H["9P"]
  G --> I["UTF-8"]
  G --> J["namespace"]
  G --> K["Go assembler の見た目"]

Plan 9アセンブリとは

Plan 9アセンブラは、各CPUごとに別々のアセンブラを持ちながら、共通した書き方を持つように設計されました。Rob Pikeのマニュアルでは、複数アーキテクチャ向けアセンブラが「1つのプログラムのバリエーション」のように説明されています。[^p9-asm]

flowchart LR
  A["Plan 9 assembler family"] --> B["MIPS"]
  A --> C["SPARC"]
  A --> D["386 / AMD64"]
  A --> E["PowerPC"]
  A --> F["ARM"]

  A --> G["共通の雰囲気"]
  G --> H["左から右へ代入"]
  G --> I["疑似レジスタ"]
  G --> J["MOVE などの擬似命令"]

GoのアセンブラはPlan 9そのものではない

GoのアセンブラはPlan 9アセンブラの入力スタイルをベースにしています。ただし、Goのツールチェーン用に作られた別プログラムであり、実CPU命令をそのまま書くというより、やや抽象化された命令列を扱います。[^go-asm]

flowchart TD
  A["Go source"] --> B["compiler"]
  B --> C["semi-abstract instructions"]
  S[".s file"] --> AS["Go assembler"]
  C --> L["linker"]
  AS --> L
  L --> M["machine code"]

  P["Plan 9 style"] --> AS

なぜ見た目が独特なのか

Goアセンブリの独特さは、主にこの4つから来ます。

flowchart TB
  A["Go assembly の見た目"] --> B["TEXT / DATA / GLOBL"]
  A --> C["SB / FP / SP / PC"]
  A --> D["左から右へのデータ移動"]
  A --> E["Go runtime との約束"]

  B --> B1["シンボルを定義する"]
  C --> C1["本物ではない疑似レジスタ"]
  D --> D1["MOVQ src, dst"]
  E --> E1["GC / stack / ABI"]

TEXT行の読み方

最初に読むべき行は TEXT です。ここに「関数名」「スタック分割の扱い」「ローカルフレームサイズ」「引数と戻り値のサイズ」がまとまっています。

flowchart TD
  L["TEXT ·Add(SB), NOSPLIT, $0-24"]
  L --> A["·Add(SB)\n現在のパッケージの Add"]
  L --> B["NOSPLIT\nstack split check を入れない"]
  L --> C["$0\nlocal frame size"]
  L --> D["-24\nargs + return area"]

疑似レジスタを図で覚える

SBFPSPPC は、CPUの物理レジスタというより、Goツールチェーンが管理する仮想的な目印です。

flowchart TB
  A["Pseudo registers"] --> SB["SB: static base"]
  A --> FP["FP: frame pointer"]
  A --> SP["SP: stack pointer"]
  A --> PC["PC: program counter"]

  SB --> SB1["·Add(SB)"]
  SB --> SB2["globalData(SB)"]
  FP --> FP1["x+0(FP)"]
  FP --> FP2["ret+16(FP)"]
  SP --> SP1["tmp-8(SP)"]
  PC --> PC1["JMP loop"]

FPまわりの見え方

たとえば func Add(x, y uint64) uint64 をスタック引数として見ると、xyretFP からのオフセットとして並びます。

flowchart TB
  subgraph Frame["caller frame から見た args/results"]
    X["x+0(FP)\nuint64"] --> Y["y+8(FP)\nuint64"]
    Y --> R["ret+16(FP)\nuint64"]
  end

  T["TEXT ·Add(SB), NOSPLIT, $0-24"] --> X
  T --> Y
  T --> R

左から右へ読む

Plan 9系の書き方では、データ移動は「左がソース、右が宛先」と読むのが基本です。Intel記法に慣れていると最初に混乱しやすいところです。

flowchart LR
  A["MOVQ x+0(FP), AX"] --> B["x+0(FP) を読む"]
  B --> C["AX に入る"]

  D["ADDQ y+8(FP), AX"] --> E["AX = AX + y"]

Goから呼べる最小例

Go側には関数宣言だけを書き、実装を .s に置きます。Goのプロトタイプは、go vet のチェックやGCのための情報にも関係します。[^go-asm]

package add

func Add(x, y uint64) uint64
#include "textflag.h"

TEXT ·Add(SB), NOSPLIT, $0-24
    MOVQ x+0(FP), AX
    ADDQ y+8(FP), AX
    MOVQ AX, ret+16(FP)
    RET

この例の流れ

flowchart TD
  G["Go: Add(2, 3)"] --> P["prototype: func Add(x, y uint64) uint64"]
  P --> A["asm: TEXT ·Add(SB)"]
  A --> B["MOVQ x+0(FP), AX"]
  B --> C["ADDQ y+8(FP), AX"]
  C --> D["MOVQ AX, ret+16(FP)"]
  D --> R["return 5"]

.go.s はビルド時に合流する

パッケージに .s ファイルがある場合、go buildgo_asm.h を生成し、Goの定数・構造体サイズ・フィールドオフセットなどをアセンブリ側から参照できるようにします。[^go-asm]

flowchart TD
  GO[".go files"] --> C["go compiler"]
  C --> H["go_asm.h"]
  S[".s files"] --> A["go tool asm"]
  H --> A
  C --> O1["Go object"]
  A --> O2["Asm object"]
  O1 --> L["linker"]
  O2 --> L
  L --> BIN["binary"]

TEXTDATAGLOBLの位置づけ

関数は TEXT、初期化付きデータは DATA、グローバルシンボル宣言は GLOBL で表します。

flowchart TB
  A["assembly source"] --> T["TEXT"]
  A --> D["DATA"]
  A --> G["GLOBL"]

  T --> T1["function body"]
  D --> D1["bytes at offset"]
  G --> G1["symbol size / flags"]

  D1 --> G1

GCとの約束

GoではGCがポインタ位置を知る必要があります。アセンブリはGoコンパイラの型安全な世界を一部抜けるので、「プロトタイプを書く」「ポインタを含まないデータには NOPTR を付ける」などの約束が重要です。[^go-asm]

flowchart TD
  A["Go prototype"] --> B["args/results の pointer 情報"]
  B --> C["GC が stack を正しく見る"]

  D["DATA / GLOBL"] --> E{"pointer を含む?"}
  E -->|"含まない"| F["NOPTR / RODATA"]
  E -->|"含む"| G["Go側で定義するのが安全"]

  F --> H["GC scan を省ける"]
  G --> I["型情報をGoに任せる"]

どんな時に書くべきか

手書きアセンブリは強力ですが、読みやすさ・移植性・安全性を失いやすいです。基本はGoで書き、必要が明確な部分だけに閉じ込めるのが現実的です。

flowchart TB
  Q{"アセンブリを書く?"}
  Q -->|"普通の処理"| GO["Goで書く"]
  Q -->|"特殊命令が必要"| ASM["asmを検討"]
  Q -->|"SIMD / crypto / runtime"| ASM
  Q -->|"性能差を測っていない"| GO

  ASM --> R1["小さく閉じる"]
  ASM --> R2["Go prototype を置く"]
  ASM --> R3["ベンチマークを書く"]
  GO --> R4["保守しやすい"]

Plan 9からGoへ、何が残ったのか

Plan 9の思想そのものがGoに丸ごと入っているわけではありません。しかし、低レイヤを見ると、Plan 9の「資源を単純なモデルで扱う」「ツールチェーンで抽象化する」という空気が残っています。

flowchart LR
  P["Plan 9"] --> A["assembler style"]
  P --> N["namespace thinking"]
  P --> U["UTF-8 culture"]
  P --> T["small sharp tools"]

  A --> G["Go asm syntax"]
  N --> I["system design ideas"]
  U --> S["string/text assumptions"]
  T --> C["go toolchain"]

まとめ

Plan 9アセンブリを理解するコツは、CPU命令から入るより先に、TEXTSBFPSP という「Goツールチェーンとの約束」を読むことです。

flowchart TD
  A["Plan 9"] --> B["分散OSの実験"]
  A --> C["Plan 9 assembler"]
  C --> D["Go assembler の入力スタイル"]
  D --> E["TEXT / DATA / GLOBL"]
  D --> F["SB / FP / SP / PC"]
  D --> G["GC / stack との協調"]

  H["読む順番"] --> E
  E --> F
  F --> G
  G --> I["必要な部分だけ書く"]

「GoでPlan 9を使っている」という言い方は少し雑です。より正確には、GoのアセンブラにはPlan 9アセンブラ由来の表記と設計思想が残っている、という理解が近いです。

参考資料

[^plan9-overview]: Plan 9 Foundation / 9p.io, “Plan 9 from Bell Labs — Overview”. Plan 9の概要、リリース史、2021年以降のMIT License化、現在の状態について説明しています。https://9p.io/plan9/about.html [^p9-asm]: Rob Pike, “A Manual for the Plan 9 assembler”. Plan 9アセンブラの疑似レジスタ、SB、左から右へのオペランド順などを説明しています。https://9p.io/sys/doc/asm.html [^go-asm]: The Go Programming Language, “A Quick Guide to Go's Assembler”. GoアセンブラがPlan 9アセンブラの入力スタイルに基づくこと、疑似レジスタ、TEXTDATAGLOBL、GC連携などを説明しています。https://go.dev/doc/asm [^ninefront]: OSNews, “9front Release released”, 2025-10-13. 9frontがPlan 9のメンテナンスされているフォークであり、2025年にもリリースがあったことを紹介しています。https://www.osnews.com/story/143526/9front-release-released/