低レイヤー技術を教えるにあたって Part 1: 用語集

# はじめに
こんにちは、Latte72 です。

慶應義塾大学公認サークル [*Computer Society*](https://www.kcs1959.jp/) で低レイヤーを扱うシステム班の班長を務めることになったので、後輩たちに**低レイヤー技術**（特に自作言語やコンパイラ・インタプリタの実装）について興味を持ってもらうにはどうしたらいいかと考えながらこの記事を書いています。

この記事は私のサークルに入会した新入生や、プログラミング言語がどのようにして動いているのかに興味がある人、低レイヤーにあまり詳しくないけど自作言語や自作コンパイラに興味がある人たちに向けたものです。 **プログラミングに関する事前知識がなくても読めるように、多くの補足をつけています。** 既にプログラミングに精通している方にとっては説明が不適切に感じる部分があるかもしれません。温かい目でご覧いただき、コメント欄にてご指摘いただければありがたいです。

当初は１つの記事として公開しようと思っていたのですが、長くなりそうだったのでいくつかに分けて公開することにしました。この Part 1 の記事は、新入生に低レイヤー技術を解説する際に用語の知識を補うために、低レイヤー関連の用語をまとめています。続編の Part 2 の記事では自作言語やコンパイラ・インタプリタの実装についての紹介をしています。ぜひご覧ください！

　⇓ Part 2 の記事はこちら ⇓

 https://qiita.com/Latte72R/items/19bf9d0ceb929ae6aa93

# 用語の説明
低レイヤー技術に関連する用語について解説していきます。

:::note info
一部だけではイメージが掴みづらいものもあるかもしれませんが、記事全体を通して理解できれば問題ありません。少し疑問が残っていても読み進めていただいて大丈夫です。
:::

## 低レイヤー
**低レイヤー**とはハードウェアに近い領域を指します。低レイヤー開発とは、単にコードを書くだけでなく、コンピュータの根幹部分を深く理解し、その上でシステムの基盤となる部分である[OS](#os)や[コンパイラ](#コンパイラ)を自ら設計・実装することを指します。また、今回の記事ではあまり解説しませんが、ネットワークやセキュリティに関する技術も低レイヤーに含まれます。

## OS
[**OS**](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%9A%E3%83%AC%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0) (Operation System) とはハードウェアとアプリケーションの中間層として動作するソフトウェアのことです。普段パソコンやスマホを使って生活している人でもOSの存在を意識していない人は多いのではないでしょうか。

これはOSの存在を意識しなくても使えるほど便利に設計されているという点ではとてもありがたいことなのですが、低レイヤー開発をするにあたっては少し厄介です。低レイヤー技術はOSや実際のパソコン自体の性質に強く影響を受ける分野です。そのため、開発するシステムの性質をしっかりと理解しておくことが大切です。

### UNIX
[**UNIX**](https://ja.wikipedia.org/wiki/UNIX) は1970年代に開発されたOSで、マルチユーザー・マルチタスクの概念を初めて実現しました。シンプルで堅牢な設計が特徴であり、現在でも多くのシステムやOSの設計思想の基盤となっています。
UNIX系のOSにはいろいろな種類があり、個々のOSは**POSIX規格**というものに準拠しているため、異なるUNIX系OS間での互換性が高いです。

:::note info
#### POSIX規格
[**POSIX規格**](https://ja.wikipedia.org/wiki/POSIX)とは、移植性の高いソフトウェアの開発を容易にするために、主にUNIX系のOSに関して各OSが共通して持つべき仕組みを定めた規格です。
:::

### Windows
[**Windows**](https://ja.wikipedia.org/wiki/Microsoft_Windows)はMicrosoftが開発するOSで、GUIを中心としたユーザーフレンドリーな設計が特徴です。かなり多くの人が利用していますが、実はそのままではあまり開発には向かないOSです。これはWindowsは基本的にクローズドソースであるため、OS内部の詳細な動作が不透明で、POSIX規格にもほとんど対応していないからです。しかし安心してください。現在のWindowsには **WSL** という機能があり後述するLinuxをWindows上で仮想的に動作させることができます。

:::note info
#### WSL
[**WSL**](https://ja.wikipedia.org/wiki/Windows_Subsystem_for_Linux) (Windows Subsystem for Linux) はWindows上で実際のLinuxカーネルを動かす方式でとても便利です。
このWSLという機能ができる前にもWindows機でLinuxを利用する方法はありました。例えば**デュアルブート**と言って、１つのパソコン上にWindowsとLinuxが共存していて、起動時にどちらかを選択するという方法です。
デュアルブートはLinuxとしての動作性は優れているものの、操作ミスによりLinuxだけでなくWindowsも破壊される、WindowsとLinuxを同時に起動することができない、などの問題がありました。
これらの問題点は最近のWSLではほぼ解決されているため、開発にWindowsを利用する場合はWSLを利用するのがおすすめです。
:::

### Linux
[**Linux**](https://ja.wikipedia.org/wiki/Linux) はUNIXの設計思想を引き継ぎながら、オープンソースとして発展してきたOSです。カスタマイズ性に優れ、サーバー、クラウド、組み込みシステムなど多岐にわたる用途で利用されています。
LinuxというのはたくさんのOSの総称で、個々のOSは**ディストリビューション**と呼ばれます。具体的にはUbuntuやCentOSなどのディストリビューションがあります。
Linuxはカーネルやシステムコール、デバイスドライバなどの内部構造がオープンにされているため、低レイヤー開発者にとって理想的なプラットフォームです。デバイスドライバの開発やシステムの最適化などの実践的な学習と開発が可能です。

:::note info
#### オープンソース
[**オープンソース**](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%BC%E3%83%97%E3%83%B3%E3%82%BD%E3%83%BC%E3%82%B9)とは、ソースコードが公開され、誰でも自由に閲覧・修正・再配布できるソフトウェア開発モデルです。
:::

### MacOS
[**MacOS**](https://ja.wikipedia.org/wiki/MacOS) はAppleが開発するUNIXベースのOSで、美しいGUIと安定性、高いセキュリティが特徴で、クリエイティブな分野や一般ユーザー向けに広く利用されています。Terminalや各種コマンドラインツール、UNIXライクな操作環境を提供しており、カスタマイズ性に関して制限はありますが、低レイヤー開発も可能です。

## CPU / メモリ
### CPU
[**CPU**](https://ja.wikipedia.org/wiki/CPU) (Central Processing Unit) は、プログラムの命令を実行する演算装置です。**メモリ**から命令を読み出し、その命令を解釈して「加算」「減算」「比較」などの演算やデータの移動などの実際の処理を行います。

### 機械語
[**機械語**](https://ja.wikipedia.org/wiki/%E6%A9%9F%E6%A2%B0%E8%AA%9E)とはCPUが直接解釈できるデータのことです。世間的なイメージで言うと「 `0` と `1` がたくさん並んでいるやつ」です。
（実際には現在利用されているほとんどのコンピュータではすべてのデータを `0` と `1` の羅列で表しています。したがって、画像も文字列もコンピュータ上では「 `0` と `1` がたくさん並んでいるやつ」に該当します。）

プログラム言語は確かに機械が読みやすいように設計していますが、実際に実行するにはこのあと解説する[コンパイラ](#コンパイラ)や[インタプリタ](#インタプリタ)が必要であり、CPUがそのまま実行できるのは機械語だけです。

<details><summary>機械語の例</summary>
Wikipediaから適当に拾ってきただけなのでそんなに深い意味はないです。

```
0111000000000001
0000000000000000
0111000000000010
0000000000000000
0001001000010000
1000000000001101
0001001000100000
1000000000001100
```
</details>

### アセンブリ言語
[**アセンブリ言語**](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%82%BB%E3%83%B3%E3%83%96%E3%83%AA%E8%A8%80%E8%AA%9E)とは、機械語を人間が読める形にした低レベル言語です。アセンブラを用いることで機械語に変換することができます。アセンブリ言語での一つの命令は基本的には一つの機械語の命令に対応していますが、命令によっては複数の機械語の命令に対応しているものもあります。
<details><summary>x86-64とarm64のアセンブリの例</summary>

以下に[C言語](#c言語)でのプログラムと、それを変換したx86-64とarm64のアセンブリを示します。（x86-64とarm64に関しては下の[アーキテクチャ](#アーキテクチャ)で解説しています。

```c
int main() { 
    int x = 5;
    int y = 3;
    return x * y; 
}
```

上のプログラムをx86-64のアセンブリに変換したものです。

```nasm
main:
  push rbp
  mov rbp, rsp
  mov dword ptr [rbp - 4], 0
  mov dword ptr [rbp - 8], 5
  mov dword ptr [rbp - 12], 3
  mov eax, dword ptr [rbp - 8]
  imul eax, dword ptr [rbp - 12]
  pop rbp
  ret
```

上のプログラムをarm64のアセンブリに変換したものです。

```nasm
main:
  sub sp, sp, #16
  str wzr, [sp, #12]
  mov w8, #5
  str w8, [sp, #8]
  mov w8, #3
  str w8, [sp, #4]
  ldr w8, [sp, #8]
  ldr w9, [sp, #4]
  mul w0, w8, w9
  add sp, sp, #16
  ret
```
</details>

x86-64 アセンブリの入門スライドを作成しました。よかったらご覧になってください。

<script defer class="speakerdeck-embed" data-id="3759771ab93f4cc092984f3f3fb74c78" data-ratio="1.7777777777777777" src="//speakerdeck.com/assets/embed.js"></script>

:::note info
#### 実際のアセンブリ言語
もしあなたがC言語を少し書くことができるなら、以下のサイトで実際に生成されるアセンブリ言語を眺めてみるのがおすすめです。
⇒ [CompilerExplorer](https://godbolt.org/z/Px6hGc6rv)
:::

### アーキテクチャ
[**アーキテクチャ**](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%BB%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3) とは、特定の機械語の命令の総称である**命令セット**を表します。命令セットはCPUごとに自由にデザインしてかまいません。しかし、機械語レベルの互換性がないと同じプログラムを動かせないので、命令セットのバリエーションはそれほど多くありません。現代のアーキテクチャはその設計思想によって、**CISC**と**RISC**の２種類に分けることができます。

#### CISC
[**CISC**](https://ja.wikipedia.org/wiki/CISC) (Complex Instruction Set Computer) は、多くの複雑な命令が用意され、１つの命令で複数の処理を行うことができます。例としてIntelやAMDが生産し一般的なWindows機に採用されている **x86-64** があります。

:::note info
x86-64は、AMD64やx64などと呼ばれることもあります。
これはx86-64が、32bitのx86と呼ばれていたアーキテクチャの64bit拡張で、その開発がAMDにより行われたことに起因しています。
:::

#### RISC
[**RISC**](https://ja.wikipedia.org/wiki/RISC) (Reduced Instruction Set Computer) は、単純な命令セットを採用し、１つの命令あたりの処理内容が限定的です。例として2015年以降のMacに採用されている **arm64** や、カリフォルニア大学バークレー校で開発されオープンソースで提供されている **RISC-V** があります。

### メモリ
[**メモリ**](https://ja.wikipedia.org/wiki/Random_Access_Memory)とは、CPUがプログラムを実行する際に必要な命令やデータを一時的に保存するための装置です。
基本的には **RAM** (Random Access Memory) のことを指しますが、文脈によってはキャッシュメモリやSSDを指すこともあります。

## コンパイラ / インタプリタ
プログラムを処理する方法（処理系）はその処理の方法によっていくつかの種類に分けられます。ここではその方法について解説します。

### コンパイラ
[**コンパイラ**](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9)は、プログラミング言語で書かれたソースコードを一括で[機械語](#機械語)や中間表現に変換するプログラムです。変換後のコードは独立して実行可能です。代表的な例は、[C言語](#c言語)のコードを実行ファイルに変換する [GCC](#gcc) や [Clang](#clang) です。
```mermaid
graph LR
    A[ソースコード] --> B[機械語]
    B --> C[実行]
```

### インタプリタ
[**インタプリタ**](https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%97%E3%83%AA%E3%82%BF) は、ソースコードを逐次的に解釈し、直接実行するプログラムです。コンパイルの事前処理を必要とせず、実行時に 1 行ずつ処理します。代表例は [Python](#python) や Ruby のインタプリタです。
```mermaid
graph LR
    D[ソースコード] --> E[逐次解釈]
    E --> F[直接実行]
```
#### REPL
[**REPL**](https://ja.wikipedia.org/wiki/REPL) (Read-Eval-Print Loop) とは、インタプリタを使う処理系において、ユーザーとインタプリタが対話をするようにコードを実行できる仕組みです。
コードの動作やエラーを確認しながら処理することができる﻿のでデバッグに役立ちます。

### JITコンパイル
[**JITコンパイル**](https://ja.wikipedia.org/wiki/%E5%AE%9F%E8%A1%8C%E6%99%82%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9) (Just-In-Time Compilation) とは、プログラムのうち頻繁に実行されるコード（「ホットスポット」と呼ばれる）を先に機械語に変換しておくことで実行速度の高速化を図る技術です。具体的にはよく使われる関数やループなどがホットスポットに該当します
なお、JITコンパイルと区別する目的で通常のコンパイルを「 **AOTコンパイル** (Ahead-Of-Time Compilation) 」と呼ぶこともあります。
（具体的な利用例は個々のプログラミング言語の項で紹介します。）
```mermaid
graph LR
    G[ソースコード] --> H[バイトコード]
    H --> I[ホットスポット検出]
    I --> J[機械語]
    J --> K[実行]
```

### JVM
[**JVM**](https://ja.wikipedia.org/wiki/Java%E4%BB%AE%E6%83%B3%E3%83%9E%E3%82%B7%E3%83%B3) (Java Virtual Machine) とは、[Java](#java)を実行するための仮想マシンです。
Javaのソースコードはまずコンパイラによって中間表現である Javaバイトコード に変換されます。このバイトコードはプラットフォームに依存しない形式となっており、JVM上で実行されます。JVMは内部でJITコンパイルを利用しており、実行の仕組みも少しJITコンパイルに似ています。

```mermaid
graph LR
  A[Javaソースコード] --> B[Javaバイトコード]
  B --> C[ホットスポット検出]
  C --> D[機械語]
  D --> E[実行]
```

|  | 利点 | 欠点 |
|----|----|----|
| **コンパイラ** | ソースコード全体を事前に最適化されたネイティブコードに変換できるため、高速な実行が可能である | プラットフォームごとに再コンパイルが必要である<br>実行時の環境適応が難しい |
| **インタプリタ** | ソースコードを逐次解釈するため、即時実行やデバッグが容易である<br>移植性が高く、プラットフォーム依存が少ない | 毎回解釈するため、実行速度が遅くなる |
| **JITコンパイル** | 実行時に最適化を行うため、初回はインタプリタの柔軟性、後半はコンパイラの高速性を活かせる | システムが複雑化し、メモリ使用量が増加する可能性がある |
| **JVM** | Javaバイトコードによりプラットフォーム非依存性が実現できる<br>内部でJITコンパイルを利用し実行時高速化を図れる | 仮想マシン層によるオーバーヘッドが存在するため、ネイティブ実行に比べ遅延が発生する |

:::note warn
#### 「コンパイラ言語」 「インタプリタ言語」
多くの書籍やサイトで「コンパイラ言語」や「インタプリタ言語」という言葉が使われていますが、この使い方は誤解を招くことがあるので注意が必要です。

なぜならば「コンパイラ」や「インタプリタ」はそのプログラムをどうやってに処理するか（処理系）を表すものであり言語の基本的な構文などとは独立に考えられるべきものだからです。

一般的には C言語は「コンパイラ言語」、Python言語は「インタプリタ言語」と言われていますが、理論上は C言語のインタプリタや Python言語のコンパイラも作ることは可能です。

また、「インタプリタ言語」はJITコンパイルを利用していることがあり、これも混乱を招く可能性があります。

上記のことを踏まえたうえで、一般には「コンパイラ言語」は主な処理系がAOTコンパイラを利用するもの、「インタプリタ言語」は主な処理系がインタプリタ（一部はJITコンパイラも含む）を意味しています。
:::

## プログラミングパラダイム
[**プログラミングパラダイム**](https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%83%91%E3%83%A9%E3%83%80%E3%82%A4%E3%83%A0)とは、効率的なプログラムを書くためのプログラムの設計方法をいくつかのグループに分けたものです。ここでは**手続き型プログラミング**と**オブジェクト指向**について解説します。

:::note warn
言語によってどのプログラミングパラダイムが使いやすいかは異なります。仕様によって特定のプログラミングパラダイムを使いやすいように設計している言語もあります。しかし、どの考え方でプログラムを書くかの問題なので、「この言語ではこのプログラミングパラダイムしか使うことができない」というようなことはありません。
:::

### 手続き型プログラミング
[**手続き型プログラミング**](https://ja.wikipedia.org/wiki/%E6%89%8B%E7%B6%9A%E3%81%8D%E5%9E%8B%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0) とは、プログラムの「処理の流れ」（手続き）を中心に設計する考え方です。手続き型プログラミングではデータと処理が分離されています。あるデータを処理する際には、まずそのデータを受け取り、処理を実行し、結果を返すという一連の流れを明確に定義していきます。データをまとめるためには**構造体**を利用することが多いです。

#### 手続き型プログラミングの利点と欠点
手続き型プログラミングはシンプルな構造で理解しやすいので、小規模なプログラムに向いています。しかし、グローバル変数が増えやすく、状態管理が煩雑になるので大規模なプログラムには向いていません。

### オブジェクト指向
[**オブジェクト指向**](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91)とは、データとそれに対する操作（**メソッド**）を一体化させた**オブジェクト**という概念を基本単位としてプログラムを設計する方法です。オブジェクト指向では、データとそれを扱うメソッドがひとつのまとまりとなるため、情報の再利用がしやすく、物事をモデル化するのに適した設計手法です。

#### クラス
[**クラス**](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%A9%E3%82%B9_\(%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF\)) は、オブジェクトを生み出すための設計図として機能し、同じ種類のオブジェクトが共有する属性（変数）や処理（メソッド）を定義します。オブジェクト指向におけるメソッドは、関数とほぼ同義です。

#### 継承
[**継承**](https://ja.wikipedia.org/wiki/%E7%B6%99%E6%89%BF_\(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0\))とは、既存のクラスを基に新しいクラスを作る仕組みで、親クラスの特徴を引き継ぎつつ新機能を追加できます。

#### オブジェクト指向の利点と欠点
オブジェクト指向では、継承などの柔軟で再利用性の高い設計を可能にする機能が備わっています。そのため、プログラムの拡張や保守が容易になり、複雑なシステムを効率的に管理することができます。ただし、オブジェクト指向の設計はその抽象度の高さから、シンプルなプログラムに対してはシステムが不必要に複雑になる可能性があります。

この記事を読んだ方から「オブジェクト指向の説明がわかりにくい」という声がいくつか寄せられたので、[Python](#python)での具体的な例を用いた解説を追加しました。

<details><summary>Pythonでのオブジェクト指向の例</summary>

Pythonでオブジェクト指向を利用する際のソースコードの具体的な例です。

```python
# 基本キャラクタークラスを定義
class Character:
    # オブジェクト生成時の初期化動作を定義
    def __init__(self, name):
        self.name = name  # 名前属性
        self.hp = 100     # 体力属性

    # 攻撃メソッドを定義
    def attack(self):
        print(f"{self.name}の攻撃！")

# 基本キャラクタークラスを継承して魔法使いクラスを定義
class Wizard(Character):
    # 追加メソッドとして呪文メソッドを定義
    def spell(self):
        print(f"{self.name}が呪文を唱えた！")

# 実際のキャラクターのオブジェクトを作成
warrior = Character("勇者")  # 基本キャラ作成
mage = Wizard("魔導師")      # 魔法使い作成

warrior.attack()  # 「勇者の攻撃！」と表示
mage.attack()     # 「魔導師の攻撃！」と表示（継承した機能）
mage.spell()      # 「魔導師が呪文を唱えた！」と表示（追加機能）
```

上のプログラムでのクラスの設計を図を用いて簡略化すると以下のようになります。
```mermaid
flowchart LR
    subgraph **Wizard Class**
        A[属性（変数）] --> B[名前]
        A --> C[体力]
        D[操作（メソッド）] --> E[攻撃する]
        D --> F[**呪文を唱える**]
    end
    style F fill:#ffcccc,stroke:#b33,stroke-width:2px
    subgraph **Character Class**
        G[属性（変数）] --> H[名前]
        G --> I[体力]
        J[操作（メソッド）] --> K[攻撃する]
    end
```
</details>

:::note info
Pythonでのオブジェクト指向について、かなり詳しくまとめているサイトを見つけたので、リンクを貼っておきます。
⇒ [Pythonのオブジェクト指向プログラミングを完全理解](https://qiita.com/kaitolucifer/items/926ed9bc08426ad8e835)
:::

## ガベージコレクション
[**ガベージコレクション**](https://ja.wikipedia.org/wiki/%E3%82%AC%E3%83%99%E3%83%BC%E3%82%B8%E3%82%B3%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3) (GC) は、プログラムが使用しなくなったメモリ領域を自動的に解放する[メモリ管理システム](#メモリ)です。[Python](#python)や[Java](#java)などで利用されています。[C/C++](#c--c)にはガベージコレクション機能がないので`malloc`や`free`などの関数を用いて明示的にメモリ操作をしなければいけないです。一方で、[ガベージコレクション](#ガベージコレクション)があるPythonやJavaではその必要はありません。
```mermaid
flowchart LR
    A[メモリ割当] --> B[オブジェクト使用]
    B --> C{参照あり？}
    C -->|Yes| D[保持]
    C -->|No| E[メモリ解放]
```

## 動的型付け / 静的型付け
プログラミング言語が持つデータ（**変数**）には「**型**」というものがあり、その変数にはどの程度の大きさの[メモリ](#メモリ)を割り当てるか、そのデータの演算はどうやってに行うか、などを規定しています。具体的には整数を表す`int`型や文字を表す`char`型などです。

ある変数を扱うにはその変数に型を付与しなければなりません。そのためにはどの型を付与するのかを決定する必要があります。この決定方法によりプログラミング言語は大きく２つに分けられます。

### 動的型付け
[**動的型付け**](https://ja.wikipedia.org/wiki/%E5%8B%95%E7%9A%84%E5%9E%8B%E4%BB%98%E3%81%91) とは、変数の型が実行時に決定されるプログラミング方式です。動的型付けを利用することでコードを柔軟に記述することができます。動的型付けは[インタプリタ](#インタプリタ)と組み合わせられることが多いです。これにより同じ変数に異なる型のデータを扱う場合でも柔軟に対応できます。一方で、大規模なプロジェクトでは、変数の型が明確でないためにコードの理解や保守が難しくなってしまいます。

### 静的型付け
[**静的型付け**](https://ja.wikipedia.org/wiki/%E9%9D%99%E7%9A%84%E5%9E%8B%E4%BB%98%E3%81%91) とは、変数の型が実行前に決定され、型の整合性が事前にチェックされる方式です。静的型付けはコンパイラと組み合わされることが多いです。[コンパイラ](#コンパイラ)が型情報を利用して最適なコード生成を行うため、実行時のパフォーマンスが向上します。また、型情報が明示されることで、プログラムの意図が明確になり、チーム開発や長期的な保守が容易になります。

## C / C++
### C言語
[**C言語**](https://ja.wikipedia.org/wiki/C%E8%A8%80%E8%AA%9E)とは、低レイヤー開発の定番言語で、[OS開発](#os)や組み込みシステムで広く使用されています。C言語ではポインタ操作、[メモリ管理](#メモリ)など機械語に近い操作を行うことが可能です。
<details><summary>C言語のサンプルコード</summary>

C言語でフィボナッチ数列の10番目を求めて出力するプログラムです。

```c
#include <stdio.h>

int fibo(int n) {
    if (n < 2) {
        return n;
    } else {
        return fibo(n - 1) + fibo(n - 2);
    }
}

int main(void) {
    printf("%d\n", fibo(10));
    return 0;
}
```
</details>

### C++言語
[**C++言語**](https://ja.wikipedia.org/wiki/C%2B%2B) はC言語に[オブジェクト指向](#オブジェクト指向)などの高水準な機能を追加したプログラム言語で、C言語の低レイヤー操作の利点を保持しながら抽象度の高い設計が可能です。低レイヤー開発の他にもゲームエンジンや高性能計算などに幅広く利用されます。
<details><summary>C++言語のサンプルコード</summary>

C++言語でフィボナッチ数列の10番目を求めて出力するプログラムです。

```c++
#include <iostream>

int fibo(int n) {
    if (n < 2)
        return n;
    else
        return fibo(n - 1) + fibo(n - 2);
}

int main() {
    std::cout << fibo(10) << std::endl;
    return 0;
}
```
</details>

### C / C++ のコンパイラ
C / C++ の処理系には複数の種類があります。それらのうち有名なものを３つ紹介します。
#### GCC
[**GCC**](https://ja.wikipedia.org/wiki/GNU%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E3%82%B3%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3) (GNU Compiler Collection) は、歴史の長いオープンソースのコンパイラで、C/C++以外にも複数の言語に対応しています。また、多くのプラットフォームで動作するため、クロスプラットフォーム開発に適しています。

https://gcc.gnu.org/

#### Clang
[**Clang**](https://ja.wikipedia.org/wiki/Clang) は、[LLVMプロジェクト](#llvm)の一部として開発された、モダンなコンパイラで、エラーメッセージや警告が非常に分かりやすいです。

https://clang.llvm.org/

#### MSVC
[**MSVC**](https://ja.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B) (Microsoft Visual C++) は、Microsoftが提供する公式のC/C++コンパイラで、Visual Studioに統合されています。Windowsプラットフォーム上でのパフォーマンスやデバッグ機能に優れていますが、独自の拡張や挙動があり、コードの移植性を考慮する必要があります。

https://visualstudio.microsoft.com/ja/vs/features/cplusplus/

:::note info
#### 互換性について
[**互換性**](https://ja.wikipedia.org/wiki/%E4%BA%92%E6%8F%9B%E6%80%A7) とは、異なるハードウェアやソフトウェアが問題なく動作するように設計されていることを指します。

新しいシステムやソフトウェアが、古いバージョンのデータやファイル形式を扱えることを**後方互換性**、古いシステムやソフトウェアが、新しいバージョンのデータやファイル形式を扱えることを**前方互換性**といいます。

C++は「C with Classes」として始まり、C言語の機能を多く継承しています。
そのため、Cで記述されたコードの多くは、何らかの修正を加えればC++としても実行可能です。
:::

## Python
[**Python**](https://ja.wikipedia.org/wiki/Python) は、[動的型付け言語](#動的型付け)でデバッグが容易なので、初心者でも書きやすい言語となっています。[C言語](#c言語)の資産を利用しやすい設計となっているため、AI開発やWeb開発でも広く利用されています。
しかしながら、明示的に型を記述しないために可読性が低い、[インタプリタ](#インタプリタ)を利用した処理系の宿命として実行速度が遅い、などの欠点もあります。
（ちなみに私が中学生の時に初めて書いたプログラミング言語もPythonでした。）

<details><summary>Pythonのサンプルコード</summary>

Pythonでフィボナッチ数列の10番目を求めて出力するプログラムです。

```python
def fibo(n):
    if n < 2:
        return n
    else:
        return fibo(n - 1) + fibo(n - 2)

print(fibo(10))
```
</details>

### CPython
[**CPython**](https://ja.wikipedia.org/wiki/CPython) は、Pythonの公式の処理系でインタプリタのプログラムがC言語で記述されていることからこの名前がついています。

https://www.python.org/

### PyPy
[**PyPy**](https://ja.wikipedia.org/wiki/PyPy) は、Pythonの代替実装で[JITコンパイラ](#jitコンパイル)を採用しています。特にループ処理などの繰り返し実行されるコードでCPythonよりも高速に動作し、[メモリ使用量](#メモリ)も少ない特徴があります。ただしC拡張モジュールはPyPy専用のものとして別途に用意する必要があります。

https://pypy.org/

:::note info
#### 高速化への試み
公式であるCPythonにも高速化の試みは多数実施されています。例えばPython3.13では試験的にJITコンパイラが導入されたそうです。
⇒ [「Python 3.13」で追加されたJITコンパイラとは？](https://atmarkit.itmedia.co.jp/ait/articles/2411/22/news042.html)
:::

## JavaScript / TypeScript
### JavaScript
[**JavaScript**](https://ja.wikipedia.org/wiki/JavaScript) は、ブラウザ上で動作する動的型付け言語ですが、近年は**Node.js**でサーバーサイドでも利用されています。
<details><summary>JavaScriptのサンプルコード</summary>

JavaScriptでフィボナッチ数列の10番目を求めて出力するプログラムです。

```javascript
function fibo(n) {
    if (n < 2) {
        return n;
    } else {
        return fibo(n - 1) + fibo(n - 2);
    }
}

console.log(fibo(10));
```
</details>

#### V8
[**V8**](https://ja.wikipedia.org/wiki/V8_\(JavaScript%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3\)) は、Googleが開発したJavaScriptエンジンで、ChromeブラウザやNode.jsで採用されています。[JITコンパイル技術](#jitコンパイル)により高速な実行を実現し、JavaScriptのパフォーマンス向上に大きく貢献しました。

https://v8.dev/

### TypeScript
[**TypeScript**](https://ja.wikipedia.org/wiki/TypeScript) は、JavaScriptに[静的型付け](#静的型付け)を追加した上位互換言語です。Microsoftが開発・メンテナンスしており、大規模開発での型安全性やIDE支援などが特徴です。**トランスパイル**により純粋なJavaScriptコードを生成します。
<details><summary>TypeScriptのサンプルコード</summary>

TypeScriptでフィボナッチ数列の10番目を求めて出力するプログラムです。

```typescript
function fibo(n: number): number {
    if (n < 2) {
        return n;
    } else {
        return fibo(n - 1) + fibo(n - 2);
    }
}

console.log(fibo(10));
```
</details>

https://www.typescriptlang.org/

:::note info
#### トランスパイル
[**トランスパイル**](https://ja.wikipedia.org/wiki/%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B9%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9) とは、特定のプログラミング言語で書かれたソースコードを、同じ抽象度レベルの別の言語（または同じ言語の異なるバージョン）に変換するプロセスです。
:::

## Rust
[**Rust**](https://ja.wikipedia.org/wiki/Rust_\(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E8%A8%80%E8%AA%9E\))は、[メモリ安全性](#メモリ)と並列処理の安全性を実現するために**所有権システム**を導入しているプログラミング言語です。
Rustでは所有権システムと借用チェッカーにより[コンパイル時](#コンパイラ)に厳格なチェックを行うことで、メモリリークやダングリングポインタを防ぎます。また、スレッド間での安全なメモリ共有を実現し、マルチスレッドプログラミングの難しさを軽減します。
<details><summary>Rustのサンプルコード</summary>

Rustでフィボナッチ数列の10番目を求めて出力するプログラムです。

```rust
fn fibo(n: u32) -> u32 {
    if n < 2 {
        n
    } else {
        fibo(n - 1) + fibo(n - 2)
    }
}

fn main() {
    println!("{}", fibo(10));
}
```
</details>

https://www.rust-lang.org/ja

:::note info
#### WebAssembly
[**WebAssembly**](https://ja.wikipedia.org/wiki/WebAssembly) とは、ブラウザ上でネイティブに近い速度で実行できるバイナリ形式の低レベル言語です。WASMと略記することもあります。特にRustはWebAssemblyとの親和性が高く、フロントエンドの高負荷処理（画像処理、ゲームエンジンなど）での活用が進んでいます。
:::

## Java
[**Java**](https://ja.wikipedia.org/wiki/Java)は「 *Write Once, Run Anywhere* 」を掲げた言語で、バイトコードに[コンパイル](#コンパイラ)してから、[JVM](#jvm)で実行するという少し特殊な実行形態を取っています。
大規模開発に向いている言語で、大規模な企業システム（銀行など）で利用されています。
<details><summary>Javaのサンプルコード</summary>

Javaでフィボナッチ数列の10番目を求めて出力するプログラムです。

```java
public class Fibonacci {
    public static int fibo(int n) {
        if (n < 2) {
            return n;
        } else {
            return fibo(n - 1) + fibo(n - 2);
        }
    }
    
    public static void main(String[] args) {
        System.out.println(fibo(10));
    }
}
```
</details>

https://www.java.com/ja/

:::note warn
#### JavaとJavaScript
Javaと[JavaScript](#javascript)は、グレープ（ぶどう）とグレープフルーツくらいの関係しかありません。名前は似ていますが、言語設計、実行環境、用途など全く異なる特性を持っています。

このような名前になった背景は若干複雑です。
JavaScriptはNetscape Communicationsという会社のエンジニアによって開発された言語で、当初は「LiveScript」という名前でした。Netscape Communications社は当時Javaを開発していたSun Microsystems社と提携をしていました。Javaは当時爆発的な人気を集めていたため、その知名度を借りるために「JavaScript」に改名されました。
:::

## LLVM
[**LLVM**](https://ja.wikipedia.org/wiki/LLVM) (Low Level Virtual Machine) とは、[コンパイラ](#コンパイラ)のフロントエンド（ソースコード解析）とバックエンド（[機械語生成](#機械語)）を分離し、中間表現（**LLVM IR**）を介して最適化を行うコンパイラ基盤プロジェクトです。これにより、新しいプログラミング言語の開発や既存言語の最適化が容易になります。

https://llvm.org/

### LLVM IR
LLVMでは、プログラムのソースコードを実行する際に、いきなりアーキテクチャ(x86-64やarm64)に固有の機械語に変換するのではなく、アセンブリ言語と同レベルの抽象化がなされた**LLVM IR**を経由します。LLVM IRは対応しているアーキテクチャに変換することができます。
いったんLLVM IRを経由することで、プログラミング言語の開発者は複数の種類の[アーキテクチャ](#アーキテクチャ)の機械語を出力するコンパイラを書くのではなく、LLVM IRだけを出力するコンパイラを書くことに専念できます。
### MLIR
[**MLIR**](https://ja.wikipedia.org/wiki/LLVM#MLIR) (Multi-Level Intermediate Representation) は、LLVMプロジェクトから派生した新しい中間表現フレームワークです。
LLVM IRを利用すると、複数の種類のアーキテクチャの機械語を出力しなくて良くなります。しかし、LLVM IRは[アセンブリ言語](#アセンブリ言語)と同レベルの抽象化がなされている（つまりかなり複雑である）ので、複雑な構文を導入したり、適度な最適化を施したりするにはかなりの労力がかかります。また、あるプログラミング言語に対してそれを実装したとしても、他のプログラミング言語ではうまくその技術を活用することができません。
MLIRでは、プログラミング言語からLLVM IRを出力する際に更に複数の中間言語 (**Dialect**) を挟むことで、他の言語で既に実装された複雑な構文を導入したり、適度な最適化を施したりすることが容易になります。

<details><summary>MLIRのサンプルコード</summary>

 MLIR の標準Dialect ( arith & scf ) を用いて、フィボナッチ数列の10番目を求めて出力するプログラムの一例です。

```c++
module {
  func @fibo(%n: i32) -> i32 {
    %c1 = arith.constant 1 : i32
    %c2 = arith.constant 2 : i32
    %cmp = arith.cmpi slt, %n, %c1 : i32
    scf.if %cmp -> (i32) {
      scf.yield %n : i32
    } else {
      %n_minus_1 = arith.subi %n, %c1 : i32
      %r1 = call @fibo(%n_minus_1) : (i32) -> i32
      %n_minus_2 = arith.subi %n, %c2 : i32
      %r2 = call @fibo(%n_minus_2) : (i32) -> i32
      %sum = arith.addi %r1, %r2 : i32
      scf.yield %sum : i32
    }
  }

  func @main() -> i32 {
    %c10 = arith.constant 10 : i32
    %res = call @fibo(%c10) : (i32) -> i32
    return %res : i32
  }
}
```
</details>

```mermaid
graph LR
  A[α 言語]
  E[β 言語]
  subgraph MLIR Framework
    B[δ Dialect]
    F[ε Dialect]
    G[γ Dialect]
  end
  A --> B
  E --> F
  B & F --> G
  G --> C[LLVM IR]
  subgraph 機械語
    D[x86-64]
    H[arm64]
  end
  C --> D
  C --> H
```

https://mlir.llvm.org/

# おわりに
低レイヤー技術や自作言語の世界は、一見難解に思えるかもしれません。しかし、小さなステップから始めれば、誰でも挑戦できます。

この記事にはプログラミングに関する事前知識がなくても読めるように補足をつけたつもりですが、もしわからない点やおかしいと思った点があればコメント欄にて気軽に教えてください。

現在、自作言語やコンパイラ・インタプリタの作成に関して更に踏み込んだ記事も執筆中です。執筆が終わりましたら、そちらも読んでいただけるとありがたいです。

この記事を気に入っていただけた場合は是非いいねとフォローをお願いします。記事執筆のモチベーションになります。