[インデックス 1302] ファイルの概要
このコミットは、Go言語の初期段階における低レベルな型変換のメカニズムを、アセンブリ言語で実装されたキャスト関数から、よりGo言語のイディオムに沿ったunsafe.Pointer
の使用へと移行する重要な変更を記録しています。これにより、Go言語のシステムプログラミングにおけるポインタ操作の安全性が向上し、コードの可読性と保守性が改善されました。
コミット
commit 0d9c1abb58987571be6db5b1d1acbd4de18f195f
Author: Russ Cox <rsc@golang.org>
Date: Tue Dec 9 10:57:55 2008 -0800
replace assembly casts with unsafe.pointer
R=r
DELTA=178 (18 added, 101 deleted, 59 changed)
OCL=20822
CL=20826
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0d9c1abb58987571be6db5b1d1acbd4de18f195f
元コミット内容
このコミットの目的は、Go言語の標準ライブラリ、特にsyscall
パッケージ内で使用されていたアセンブリ言語による型キャスト関数を、Go言語の組み込みパッケージであるunsafe
のPointer
型を用いた明示的なポインタ変換に置き換えることです。これにより、コードベースから特定のアセンブリファイル(cast_amd64.s
など)が削除され、関連するGoコードがunsafe.Pointer
を使用するように修正されました。
変更の背景
Go言語は、システムプログラミングを念頭に置いて設計されており、C言語のような低レベルな操作を可能にしつつ、メモリ安全性や並行処理の容易さを提供することを目指しています。初期のGo言語では、システムコールへの引数渡しや、異なるデータ構造間でのポインタ変換など、特定の低レベル操作においてアセンブリ言語で書かれたヘルパー関数が使用されていました。
しかし、アセンブリ言語によるキャストは、以下のような課題を抱えていました。
- 可読性と保守性: アセンブリコードは、Goコードに比べて理解が難しく、デバッグや変更が困難です。
- 移植性: アセンブリコードは特定のアーキテクチャ(この場合はamd64)に依存するため、異なるアーキテクチャへの移植性を妨げます。
- Go言語のイディオムとの乖離: Go言語は型安全性を重視する言語であり、アセンブリによる暗黙的なキャストは、その設計思想と矛盾する部分がありました。
unsafe.Pointer
は、Go言語が提供する「安全ではない」操作を明示的に行うためのメカニズムです。これは、Goの型システムを迂回して任意の型へのポインタを保持できる汎用ポインタであり、C言語のvoid*
に似ています。このコミットは、Go言語が成熟するにつれて、アセンブリに頼るのではなく、Go言語自身が提供するunsafe.Pointer
という標準的なメカニズムを通じて低レベルな操作を統一的に扱う方針へと転換したことを示しています。これにより、コードベースのGo言語への統合が進み、将来的なメンテナンスやクロスプラットフォーム対応が容易になります。
前提知識の解説
Go言語の型システムとポインタ
Go言語は静的型付け言語であり、厳格な型システムを持っています。異なる型の変数間での代入や変換は、明示的なキャスト(型変換)が必要であり、多くの場合、コンパイラによって型安全性が保証されます。ポインタはメモリ上のアドレスを指す変数であり、Goでは*T
のように宣言されます。GoのポインタはC言語のポインタとは異なり、ポインタ演算が制限されており、ガベージコレクションの対象となります。
unsafe
パッケージとunsafe.Pointer
unsafe
パッケージは、Go言語の型システムが提供する安全性を意図的にバイパスするための機能を提供します。このパッケージは、Goの標準ライブラリの一部ですが、その名前が示す通り、使用には細心の注意が必要です。
unsafe.Pointer
: 任意の型のポインタを保持できる汎用ポインタです。*T
からunsafe.Pointer
へ、またはunsafe.Pointer
から*T
への変換が可能です。また、uintptr
型との間で相互変換が可能です。uintptr
: ポインタの値を整数として表現する型です。unsafe.Pointer
をuintptr
に変換することで、ポインタ演算(アドレスの加算・減算)が可能になります。ただし、uintptr
はガベージコレクタの対象外であるため、uintptr
に変換されたポインタが指すメモリがガベージコレクションによって解放される可能性があります。
unsafe.Pointer
は、主に以下のような高度な用途で使用されます。
- C言語との連携: C言語のライブラリをGoから呼び出す際に、Goの型とCの型の間でポインタを変換する必要がある場合。
- システムコール: オペレーティングシステムのシステムコールを直接呼び出す際に、特定のメモリレイアウトやポインタの渡し方が要求される場合。
- メモリレイアウトの操作: 構造体の内部レイアウトを直接操作したり、アラインメントを調整したりする場合。
- パフォーマンス最適化: 非常にパフォーマンスが要求される場面で、型チェックのオーバーヘッドを避けるために使用されることがあります。
アセンブリ言語によるキャスト
Go言語の初期には、特にシステムコールのような低レベルな操作において、Go言語の型システムでは直接表現できない、あるいは効率的に扱えないポインタ変換が必要な場合がありました。このようなケースでは、アセンブリ言語で書かれた関数が、特定の型のポインタを別の型のポインタとして扱うための「キャスト」機能を提供していました。これらは、Goのコードからは通常の関数呼び出しのように見えますが、内部ではCPUのレジスタ操作などを通じて直接ポインタの値を操作していました。
例えば、syscall.BytePtr(b *byte) int64
のような関数は、*byte
型のポインタを受け取り、それをシステムコールに渡すためのint64
(アドレス値)に変換する役割を担っていました。
技術的詳細
このコミットの技術的な核心は、Go言語のコンパイラとランタイムがunsafe.Pointer
を適切に扱うようになったことで、これまでアセンブリ言語で実装されていたポインタ変換ロジックが不要になった点にあります。
具体的には、以下の変更が行われました。
-
src/lib/syscall/cast_amd64.s
の削除: このファイルには、syscall.BytePtr
,syscall.Int32Ptr
,syscall.SockaddrPtr
など、様々な型をint64
(ポインタのアドレス値)に変換するためのアセンブリ関数が定義されていました。これらの関数は、Goのポインタを直接レジスタにロードし、それをint64
として返すという単純な操作を行っていました。unsafe.Pointer
の導入により、これらのアセンブリ関数はGoコード内で直接int64(uintptr(unsafe.Pointer(ptr)))
という形式で置き換え可能になったため、削除されました。 -
unsafe
パッケージのインポート:src/lib/net/net_darwin.go
,src/lib/net/net_linux.go
,src/lib/syscall/file_darwin.go
,src/lib/syscall/file_linux.go
,src/lib/syscall/socket_darwin.go
,src/lib/syscall/socket_linux.go
,src/lib/syscall/time_amd64_linux.go
,src/lib/time/tick.go
などのファイルで、unsafe
パッケージがインポートされるようになりました。 -
アセンブリキャスト関数の呼び出しの置き換え:
- 旧:
syscall.BytePtr(&namebuf[0])
- 新:
int64(uintptr(unsafe.pointer(&namebuf[0])))
- 旧:
syscall.SockaddrToSockaddrInet4(sa1)
- 新:
unsafe.pointer(sa1).(*syscall.SockaddrInet4)
- 旧:
syscall.SockaddrPtr(sa)
- 新:
int64(uintptr(unsafe.pointer(sa)))
- 旧:
syscall.Int32Ptr(&n)
- 新:
int64(uintptr(unsafe.pointer(&n)))
- 旧:
syscall.StatPtr(buf)
- 新:
int64(uintptr(unsafe.pointer(buf)))
- 旧:
syscall.TimevalPtr(&tv)
- 新:
int64(uintptr(unsafe.pointer(&tv)))
- 旧:
syscall.LingerPtr(&l)
- 新:
int64(uintptr(unsafe.pointer(&l)))
- 旧:
syscall.KeventPtr(&changes[0])
- 新:
int64(uintptr(unsafe.pointer(&changes[0])))
- 旧:
syscall.EpollEventPtr(ev)
- 新:
int64(uintptr(unsafe.pointer(ev)))
これらの変更は、Goのポインタ(
*T
)をunsafe.Pointer
に変換し、さらにそれをuintptr
に変換してint64
にキャストすることで、ポインタのアドレス値をシステムコールに渡すという一般的なパターンに統一されたことを示しています。また、SockaddrToSockaddrInet4
のような型変換関数は、unsafe.Pointer
を介した型アサーション(unsafe.pointer(sa1).(*syscall.SockaddrInet4)
)に置き換えられました。 - 旧:
-
syscall
パッケージからの旧キャスト関数のエクスポート削除:src/lib/syscall/syscall.go
,src/lib/syscall/types_amd64_darwin.go
,src/lib/syscall/types_amd64_linux.go
から、削除されたアセンブリキャスト関数の宣言(export func BytePtr(b *byte) int64;
など)が削除されました。これにより、これらの関数は外部から呼び出せなくなり、完全にunsafe.Pointer
ベースの新しいアプローチに移行したことが明確になります。 -
Makefileの変更:
src/lib/syscall/Makefile
からcast_amd64.s
がビルド対象から除外されました。これは、アセンブリファイルがもはや必要ないことを示しています。
この変更は、Go言語のランタイムとコンパイラが、unsafe.Pointer
を介したポインタ操作を効率的かつ正しく処理できるようになったことを意味します。これにより、Go言語のコードベースはよりGoらしくなり、アセンブリ言語への依存が減少し、将来的なGo言語の進化(例えば、新しいアーキテクチャへの対応)が容易になります。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、src/lib/syscall/cast_amd64.s
ファイルの削除と、Goコード内でのアセンブリキャスト関数呼び出しのunsafe.Pointer
への置き換えです。
削除されたファイル:
src/lib/syscall/cast_amd64.s
変更例 (src/lib/net/net_darwin.go):
--- a/src/lib/net/net_darwin.go
+++ b/src/lib/net/net_darwin.go
@@ -7,7 +7,8 @@ package net
import (
"os";
"syscall";
-"net"
+"net";
+"unsafe";
)
export func IPv4ToSockaddr(p *[]byte, port int) (sa1 *syscall.Sockaddr, err *os.Error) {
@@ -23,7 +24,7 @@ export func IPv4ToSockaddr(p *[]byte, port int) (sa1 *syscall.Sockaddr, err *os.
for i := 0; i < IPv4len; i++ {
sa.addr[i] = p[i]
}
- return syscall.SockaddrInet4ToSockaddr(sa), nil
+ return unsafe.pointer(sa).(*syscall.Sockaddr), nil
}
export func IPv6ToSockaddr(p *[]byte, port int) (sa1 *syscall.Sockaddr, err *os.Error) {
@@ -39,20 +40,20 @@ export func IPv6ToSockaddr(p *[]byte, port int) (sa1 *syscall.Sockaddr, err *os.
for i := 0; i < IPv6len; i++ {
sa.addr[i] = p[i]
}
- return syscall.SockaddrInet6ToSockaddr(sa), nil
+ return unsafe.pointer(sa).(*syscall.Sockaddr), nil
}
export func SockaddrToIP(sa1 *syscall.Sockaddr) (p *[]byte, port int, err *os.Error) {
switch sa1.family {
case syscall.AF_INET:
- sa := syscall.SockaddrToSockaddrInet4(sa1);
+ sa := unsafe.pointer(sa1).(*syscall.SockaddrInet4);
a := ToIPv6(&sa.addr);
if a == nil {
return nil, 0, os.EINVAL
}
return a, int(sa.port[0])<<8 + int(sa.port[1]), nil;
case syscall.AF_INET6:
- sa := syscall.SockaddrToSockaddrInet6(sa1);
+ sa := unsafe.pointer(sa1).(*syscall.SockaddrInet6);
a := ToIPv6(&sa.addr);
if a == nil {
return nil, 0, os.EINVAL
変更例 (src/lib/syscall/file_darwin.go):
--- a/src/lib/syscall/file_darwin.go
+++ b/src/lib/syscall/file_darwin.go
@@ -2,11 +2,14 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package syscall
-
// File operations for Darwin
-import syscall "syscall"
+package syscall
+
+import (
+ "syscall";
+ "unsafe";
+)
const NameBufsize = 512
@@ -15,7 +18,7 @@ export func open(name string, mode int64, perm int64) (ret int64, errno int64) {
if !StringToBytes(&namebuf, name) {
return -1, ENAMETOOLONG
}
- r1, r2, err := Syscall(SYS_OPEN, BytePtr(&namebuf[0]), mode, perm);
+ r1, r2, err := Syscall(SYS_OPEN, int64(uintptr(unsafe.pointer(&namebuf[0]))), mode, perm);
return r1, err;
}
@@ -24,7 +27,7 @@ export func creat(name string, perm int64) (ret int64, errno int64) {
if !StringToBytes(&namebuf, name) {
return -1, ENAMETOOLONG
}
- r1, r2, err := Syscall(SYS_OPEN, BytePtr(&namebuf[0]), O_CREAT|O_WRONLY|O_TRUNC, perm);
+ r1, r2, err := Syscall(SYS_OPEN, int64(uintptr(unsafe.pointer(&namebuf[0]))), O_CREAT|O_WRONLY|O_TRUNC, perm);
return r1, err;
}
@@ -34,12 +37,12 @@ export func close(fd int64) (ret int64, errno int64) {
}
export func read(fd int64, buf *byte, nbytes int64) (ret int64, errno int64) {
- r1, r2, err := Syscall(SYS_READ, fd, BytePtr(buf), nbytes);
+ r1, r2, err := Syscall(SYS_READ, fd, int64(uintptr(unsafe.pointer(buf))), nbytes);
return r1, err;
}
export func write(fd int64, buf *byte, nbytes int64) (ret int64, errno int64) {
- r1, r2, err := Syscall(SYS_WRITE, fd, BytePtr(buf), nbytes);
+ r1, r2, err := Syscall(SYS_WRITE, fd, int64(uintptr(unsafe.pointer(buf))), nbytes);
return r1, err;
}
@@ -58,17 +61,17 @@ export func stat(name string, buf *Stat) (ret int64, errno int64) {
if !StringToBytes(&namebuf, name) {
return -1, ENAMETOOLONG
}
- r1, r2, err := Syscall(SYS_STAT64, BytePtr(&namebuf[0]), StatPtr(buf), 0);
+ r1, r2, err := Syscall(SYS_STAT64, int64(uintptr(unsafe.pointer(&namebuf[0]))), int64(uintptr(unsafe.pointer(buf))), 0);
return r1, err;
}
export func lstat(name *byte, buf *Stat) (ret int64, errno int64) {
- r1, r2, err := Syscall(SYS_LSTAT, BytePtr(name), StatPtr(buf), 0);
+ r1, r2, err := Syscall(SYS_LSTAT, int64(uintptr(unsafe.pointer(name))), int64(uintptr(unsafe.pointer(buf))), 0);
return r1, err;
}
export func fstat(fd int64, buf *Stat) (ret int64, errno int64) {
- r1, r2, err := Syscall(SYS_FSTAT, fd, StatPtr(buf), 0);
+ r1, r2, err := Syscall(SYS_FSTAT, fd, int64(uintptr(unsafe.pointer(buf))), 0);
return r1, err;
}
@@ -77,7 +80,7 @@ export func unlink(name string) (ret int64, errno int64) {
if !StringToBytes(&namebuf, name) {
return -1, ENAMETOOLONG
}
- r1, r2, err := Syscall(SYS_UNLINK, BytePtr(&namebuf[0]), 0, 0);
+ r1, r2, err := Syscall(SYS_UNLINK, int64(uintptr(unsafe.pointer(&namebuf[0]))), 0, 0);
return r1, err;
}
@@ -91,7 +94,7 @@ export func mkdir(name string, perm int64) (ret int64, errno int64) {
if !StringToBytes(&namebuf, name) {
return -1, ENAMETOOLONG
}
- r1, r2, err := Syscall(SYS_MKDIR, BytePtr(&namebuf[0]), perm, 0);
+ r1, r2, err := Syscall(SYS_MKDIR, int64(uintptr(unsafe.pointer(&namebuf[0]))), perm, 0);
return r1, err;
}
コアとなるコードの解説
上記のコード変更は、Go言語がシステムコールに引数を渡す際や、異なる構造体間でポインタを変換する際の基本的なアプローチが変更されたことを示しています。
-
import "unsafe"
の追加:unsafe
パッケージをインポートすることで、Goの型システムが通常課す制約をバイパスする機能が利用可能になります。これは、低レベルなメモリ操作を行うための前提条件です。 -
syscall.SockaddrInet4ToSockaddr(sa)
からunsafe.pointer(sa).(*syscall.Sockaddr)
への変更:- 旧来の
syscall.SockaddrInet4ToSockaddr
のような関数は、おそらく内部でアセンブリコードを使用して、SockaddrInet4
型のポインタをSockaddr
型のポインタに変換していました。 - 新しいコードでは、
unsafe.pointer(sa)
によってsa
(*syscall.SockaddrInet4
型)のポインタが汎用的なunsafe.Pointer
に変換されます。 - 次に、
.(*syscall.Sockaddr)
という型アサーションによって、このunsafe.Pointer
が*syscall.Sockaddr
型として解釈されます。これは、Goのコンパイラに対して「このメモリ領域はsyscall.Sockaddr
型の構造体として扱ってよい」と明示的に指示するものです。この操作は、Goの型システムによる安全チェックをバイパスするため、プログラマがメモリレイアウトを正確に理解している必要があります。
- 旧来の
-
BytePtr(&namebuf[0])
からint64(uintptr(unsafe.pointer(&namebuf[0])))
への変更:- 旧来の
BytePtr
関数は、バイトスライス(またはバイト配列)の先頭要素へのポインタを受け取り、そのアドレスをint64
として返していました。これは、システムコールがポインタを数値として受け取る場合に必要でした。 - 新しいコードでは、
&namebuf[0]
によってバイト配列の先頭要素へのポインタ(*byte
型)を取得します。 - これを
unsafe.pointer(...)
でunsafe.Pointer
に変換します。 - さらに
uintptr(...)
でunsafe.Pointer
をポインタのアドレスを表す整数型uintptr
に変換します。 - 最後に
int64(...)
でuintptr
をint64
にキャストします。これは、システムコールが引数としてint64
を期待するためです。
- 旧来の
これらの変更は、Go言語の低レベルな操作が、アセンブリ言語に依存するのではなく、Go言語自身が提供するunsafe
パッケージを介して行われるようになったことを明確に示しています。これにより、Goコードの可読性と移植性が向上し、Go言語の設計思想である「シンプルさ」と「効率性」がより一貫して実現されることになります。
関連リンク
- Go言語の
unsafe
パッケージに関する公式ドキュメント: https://pkg.go.dev/unsafe - Go言語のシステムコールに関する議論やドキュメント(Goのバージョンによって内容が異なる可能性がありますが、概念的な理解に役立ちます)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(特に
src/unsafe
ディレクトリやsrc/syscall
ディレクトリの歴史的なコミット) - Go言語の初期の設計に関するブログ記事やメーリングリストのアーカイブ(例: golang-nutsメーリングリスト)
unsafe.Pointer
の利用に関する技術記事や解説- A Guide to the Go
unsafe
Package: https://www.ardanlabs.com/blog/2019/02/a-guide-to-the-go-unsafe-package.html - Go:
unsafe.Pointer
anduintptr
: https://medium.com/@ankur_anand/go-unsafe-pointer-and-uintptr-1c74a408728f - The Laws of
unsafe.Pointer
: https://go.dev/blog/laws-of-reflection (これはreflect
パッケージに関するものですが、unsafe.Pointer
の利用に関する重要な原則が述べられています)
- A Guide to the Go
- Go言語の初期のコミット履歴(GitHubリポジトリ)# [インデックス 1302] ファイルの概要
このコミットは、Go言語の初期段階における低レベルな型変換のメカニズムを、アセンブリ言語で実装されたキャスト関数から、よりGo言語のイディオムに沿ったunsafe.Pointer
の使用へと移行する重要な変更を記録しています。これにより、Go言語のシステムプログラミングにおけるポインタ操作の安全性が向上し、コードの可読性と保守性が改善されました。
コミット
commit 0d9c1abb58987571be6db5b1d1acbd4de18f195f
Author: Russ Cox <rsc@golang.org>
Date: Tue Dec 9 10:57:55 2008 -0800
replace assembly casts with unsafe.pointer
R=r
DELTA=178 (18 added, 101 deleted, 59 changed)
OCL=20822
CL=20826
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0d9c1abb58987571be6db5b1d1acbd4de18f195f
元コミット内容
このコミットの目的は、Go言語の標準ライブラリ、特にsyscall
パッケージ内で使用されていたアセンブリ言語による型キャスト関数を、Go言語の組み込みパッケージであるunsafe
のPointer
型を用いた明示的なポインタ変換に置き換えることです。これにより、コードベースから特定のアセンブリファイル(cast_amd64.s
など)が削除され、関連するGoコードがunsafe.Pointer
を使用するように修正されました。
変更の背景
Go言語は、システムプログラミングを念頭に置いて設計されており、C言語のような低レベルな操作を可能にしつつ、メモリ安全性や並行処理の容易さを提供することを目指しています。初期のGo言語では、システムコールへの引数渡しや、異なるデータ構造間でのポインタ変換など、特定の低レベル操作においてアセンブリ言語で書かれたヘルパー関数が使用されていました。
しかし、アセンブリ言語によるキャストは、以下のような課題を抱えていました。
- 可読性と保守性: アセンブリコードは、Goコードに比べて理解が難しく、デバッグや変更が困難です。
- 移植性: アセンブリコードは特定のアーキテクチャ(この場合はamd64)に依存するため、異なるアーキテクチャへの移植性を妨げます。
- Go言語のイディオムとの乖離: Go言語は型安全性を重視する言語であり、アセンブリによる暗黙的なキャストは、その設計思想と矛盾する部分がありました。
unsafe.Pointer
は、Go言語が提供する「安全ではない」操作を明示的に行うためのメカニズムです。これは、Goの型システムを迂回して任意の型へのポインタを保持できる汎用ポインタであり、C言語のvoid*
に似ています。このコミットは、Go言語が成熟するにつれて、アセンブリに頼るのではなく、Go言語自身が提供するunsafe.Pointer
という標準的なメカニズムを通じて低レベルな操作を統一的に扱う方針へと転換したことを示しています。これにより、コードベースのGo言語への統合が進み、将来的なメンテナンスやクロスプラットフォーム対応が容易になります。
前提知識の解説
Go言語の型システムとポインタ
Go言語は静的型付け言語であり、厳格な型システムを持っています。異なる型の変数間での代入や変換は、明示的なキャスト(型変換)が必要であり、多くの場合、コンパイラによって型安全性が保証されます。ポインタはメモリ上のアドレスを指す変数であり、Goでは*T
のように宣言されます。GoのポインタはC言語のポインタとは異なり、ポインタ演算が制限されており、ガベージコレクションの対象となります。
unsafe
パッケージとunsafe.Pointer
unsafe
パッケージは、Go言語の型システムが提供する安全性を意図的にバイパスするための機能を提供します。このパッケージは、Goの標準ライブラリの一部ですが、その名前が示す通り、使用には細心の注意が必要です。
unsafe.Pointer
: 任意の型のポインタを保持できる汎用ポインタです。*T
からunsafe.Pointer
へ、またはunsafe.Pointer
から*T
への変換が可能です。また、uintptr
型との間で相互変換が可能です。uintptr
: ポインタの値を整数として表現する型です。unsafe.Pointer
をuintptr
に変換することで、ポインタ演算(アドレスの加算・減算)が可能になります。ただし、uintptr
はガベージコレクタの対象外であるため、uintptr
に変換されたポインタが指すメモリがガベージコレクションによって解放される可能性があります。
unsafe.Pointer
は、主に以下のような高度な用途で使用されます。
- C言語との連携: C言語のライブラリをGoから呼び出す際に、Goの型とCの型の間でポインタを変換する必要がある場合。
- システムコール: オペレーティングシステムのシステムコールを直接呼び出す際に、特定のメモリレイアウトやポインタの渡し方が要求される場合。
- メモリレイアウトの操作: 構造体の内部レイアウトを直接操作したり、アラインメントを調整したりする場合。
- パフォーマンス最適化: 非常にパフォーマンスが要求される場面で、型チェックのオーバーヘッドを避けるために使用されることがあります。
アセンブリ言語によるキャスト
Go言語の初期には、特にシステムコールのような低レベルな操作において、Go言語の型システムでは直接表現できない、あるいは効率的に扱えないポインタ変換が必要な場合がありました。このようなケースでは、アセンブリ言語で書かれた関数が、特定の型のポインタを別の型のポインタとして扱うための「キャスト」機能を提供していました。これらは、Goのコードからは通常の関数呼び出しのように見えますが、内部ではCPUのレジスタ操作などを通じて直接ポインタの値を操作していました。
例えば、syscall.BytePtr(b *byte) int64
のような関数は、*byte
型のポインタを受け取り、それをシステムコールに渡すためのint64
(アドレス値)に変換する役割を担っていました。
技術的詳細
このコミットの技術的な核心は、Go言語のコンパイラとランタイムがunsafe.Pointer
を適切に扱うようになったことで、これまでアセンブリ言語で実装されていたポインタ変換ロジックが不要になった点にあります。
具体的には、以下の変更が行われました。
-
src/lib/syscall/cast_amd64.s
の削除: このファイルには、syscall.BytePtr
,syscall.Int32Ptr
,syscall.SockaddrPtr
など、様々な型をint64
(ポインタのアドレス値)に変換するためのアセンブリ関数が定義されていました。これらの関数は、Goのポインタを直接レジスタにロードし、それをint64
として返すという単純な操作を行っていました。unsafe.Pointer
の導入により、これらのアセンブリ関数はGoコード内で直接int64(uintptr(unsafe.Pointer(ptr)))
という形式で置き換え可能になったため、削除されました。 -
unsafe
パッケージのインポート:src/lib/net/net_darwin.go
,src/lib/net/net_linux.go
,src/lib/syscall/file_darwin.go
,src/lib/syscall/file_linux.go
,src/lib/syscall/socket_darwin.go
,src/lib/syscall/socket_linux.go
,src/lib/syscall/time_amd64_linux.go
,src/lib/time/tick.go
などのファイルで、unsafe
パッケージがインポートされるようになりました。 -
アセンブリキャスト関数の呼び出しの置き換え:
- 旧:
syscall.BytePtr(&namebuf[0])
- 新:
int64(uintptr(unsafe.pointer(&namebuf[0])))
- 旧:
syscall.SockaddrToSockaddrInet4(sa1)
- 新:
unsafe.pointer(sa1).(*syscall.Sockaddr)
- 旧:
syscall.SockaddrPtr(sa)
- 新:
int64(uintptr(unsafe.pointer(sa)))
- 旧:
syscall.Int32Ptr(&n)
- 新:
int64(uintptr(unsafe.pointer(&n)))
- 旧:
syscall.StatPtr(buf)
- 新:
int64(uintptr(unsafe.pointer(buf)))
- 旧:
syscall.TimevalPtr(&tv)
- 新:
int64(uintptr(unsafe.pointer(&tv)))
- 旧:
syscall.LingerPtr(&l)
- 新:
int64(uintptr(unsafe.pointer(&l)))
- 旧:
syscall.KeventPtr(&changes[0])
- 新:
int64(uintptr(unsafe.pointer(&changes[0])))
- 旧:
syscall.EpollEventPtr(ev)
- 新:
int64(uintptr(unsafe.pointer(ev)))
これらの変更は、Goのポインタ(
*T
)をunsafe.Pointer
に変換し、さらにそれをuintptr
に変換してint64
にキャストすることで、ポインタのアドレス値をシステムコールに渡すという一般的なパターンに統一されたことを示しています。また、SockaddrToSockaddrInet4
のような型変換関数は、unsafe.Pointer
を介した型アサーション(unsafe.pointer(sa1).(*syscall.SockaddrInet4)
)に置き換えられました。 - 旧:
-
syscall
パッケージからの旧キャスト関数のエクスポート削除:src/lib/syscall/syscall.go
,src/lib/syscall/types_amd64_darwin.go
,src/lib/syscall/types_amd64_linux.go
から、削除されたアセンブリキャスト関数の宣言(export func BytePtr(b *byte) int64;
など)が削除されました。これにより、これらの関数は外部から呼び出せなくなり、完全にunsafe.Pointer
ベースの新しいアプローチに移行したことが明確になります。 -
Makefileの変更:
src/lib/syscall/Makefile
からcast_amd64.s
がビルド対象から除外されました。これは、アセンブリファイルがもはや必要ないことを示しています。
この変更は、Go言語のランタイムとコンパイラが、unsafe.Pointer
を介したポインタ操作を効率的かつ正しく処理できるようになったことを意味します。これにより、Go言語のコードベースはよりGoらしくなり、アセンブリ言語への依存が減少し、将来的なGo言語の進化(例えば、新しいアーキテクチャへの対応)が容易になります。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、src/lib/syscall/cast_amd64.s
ファイルの削除と、Goコード内でのアセンブリキャスト関数呼び出しのunsafe.Pointer
への置き換えです。
削除されたファイル:
src/lib/syscall/cast_amd64.s
変更例 (src/lib/net/net_darwin.go):
--- a/src/lib/net/net_darwin.go
+++ b/src/lib/net/net_darwin.go
@@ -7,7 +7,8 @@ package net
import (
"os";
"syscall";
-"net"
+"net";
+"unsafe";
)
export func IPv4ToSockaddr(p *[]byte, port int) (sa1 *syscall.Sockaddr, err *os.Error) {
@@ -23,7 +24,7 @@ export func IPv4ToSockaddr(p *[]byte, port int) (sa1 *syscall.Sockaddr, err *os.
for i := 0; i < IPv4len; i++ {
sa.addr[i] = p[i]
}
- return syscall.SockaddrInet4ToSockaddr(sa), nil
+ return unsafe.pointer(sa).(*syscall.Sockaddr), nil
}
export func IPv6ToSockaddr(p *[]byte, port int) (sa1 *syscall.Sockaddr, err *os.Error) {
@@ -39,20 +40,20 @@ export func IPv6ToSockaddr(p *[]byte, port int) (sa1 *syscall.Sockaddr, err *os.
for i := 0; i < IPv6len; i++ {
sa.addr[i] = p[i]
}
- return syscall.SockaddrInet6ToSockaddr(sa), nil
+ return unsafe.pointer(sa).(*syscall.Sockaddr), nil
}
export func SockaddrToIP(sa1 *syscall.Sockaddr) (p *[]byte, port int, err *os.Error) {
switch sa1.family {
case syscall.AF_INET:
- sa := syscall.SockaddrToSockaddrInet4(sa1);
+ sa := unsafe.pointer(sa1).(*syscall.SockaddrInet4);
a := ToIPv6(&sa.addr);
if a == nil {
return nil, 0, os.EINVAL
}
return a, int(sa.port[0])<<8 + int(sa.port[1]), nil;
case syscall.AF_INET6:
- sa := syscall.SockaddrToSockaddrInet6(sa1);
+ sa := unsafe.pointer(sa1).(*syscall.SockaddrInet6);
a := ToIPv6(&sa.addr);
if a == nil {
return nil, 0, os.EINVAL
変更例 (src/lib/syscall/file_darwin.go):
--- a/src/lib/syscall/file_darwin.go
+++ b/src/lib/syscall/file_darwin.go
@@ -2,11 +2,14 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package syscall
-
// File operations for Darwin
-import syscall "syscall"
+package syscall
+
+import (
+ "syscall";
+ "unsafe";
+)
const NameBufsize = 512
@@ -15,7 +18,7 @@ export func open(name string, mode int64, perm int64) (ret int64, errno int64) {
if !StringToBytes(&namebuf, name) {
return -1, ENAMETOOLONG
}
- r1, r2, err := Syscall(SYS_OPEN, BytePtr(&namebuf[0]), mode, perm);
+ r1, r2, err := Syscall(SYS_OPEN, int64(uintptr(unsafe.pointer(&namebuf[0]))), mode, perm);
return r1, err;
}
@@ -24,7 +27,7 @@ export func creat(name string, perm int64) (ret int64, errno int64) {
if !StringToBytes(&namebuf, name) {
return -1, ENAMETOOLONG
}
- r1, r2, err := Syscall(SYS_OPEN, BytePtr(&namebuf[0]), O_CREAT|O_WRONLY|O_TRUNC, perm);
+ r1, r2, err := Syscall(SYS_OPEN, int64(uintptr(unsafe.pointer(&namebuf[0]))), O_CREAT|O_WRONLY|O_TRUNC, perm);
return r1, err;
}
@@ -34,12 +37,12 @@ export func close(fd int64) (ret int64, errno int64) {
}
export func read(fd int64, buf *byte, nbytes int64) (ret int64, errno int64) {
- r1, r2, err := Syscall(SYS_READ, fd, BytePtr(buf), nbytes);
+ r1, r2, err := Syscall(SYS_READ, fd, int64(uintptr(unsafe.pointer(buf))), nbytes);
return r1, err;
}
export func write(fd int64, buf *byte, nbytes int64) (ret int64, errno int64) {
- r1, r2, err := Syscall(SYS_WRITE, fd, BytePtr(buf), nbytes);
+ r1, r2, err := Syscall(SYS_WRITE, fd, int64(uintptr(unsafe.pointer(buf))), nbytes);
return r1, err;
}
@@ -58,17 +61,17 @@ export func stat(name string, buf *Stat) (ret int64, errno int64) {
if !StringToBytes(&namebuf, name) {
return -1, ENAMETOOLONG
}
- r1, r2, err := Syscall(SYS_STAT64, BytePtr(&namebuf[0]), StatPtr(buf), 0);
+ r1, r2, err := Syscall(SYS_STAT64, int64(uintptr(unsafe.pointer(&namebuf[0]))), int64(uintptr(unsafe.pointer(buf))), 0);
return r1, err;
}
export func lstat(name *byte, buf *Stat) (ret int64, errno int64) {
- r1, r2, err := Syscall(SYS_LSTAT, BytePtr(name), StatPtr(buf), 0);
+ r1, r2, err := Syscall(SYS_LSTAT, int64(uintptr(unsafe.pointer(name))), int64(uintptr(unsafe.pointer(buf))), 0);
return r1, err;
}
export func fstat(fd int64, buf *Stat) (ret int64, errno int64) {
- r1, r2, err := Syscall(SYS_FSTAT, fd, StatPtr(buf), 0);
+ r1, r2, err := Syscall(SYS_FSTAT, fd, int64(uintptr(unsafe.pointer(buf))), 0);
return r1, err;
}
@@ -77,7 +80,7 @@ export func unlink(name string) (ret int64, errno int64) {
if !StringToBytes(&namebuf, name) {
return -1, ENAMETOOLONG
}
- r1, r2, err := Syscall(SYS_UNLINK, BytePtr(&namebuf[0]), 0, 0);
+ r1, r2, err := Syscall(SYS_UNLINK, int64(uintptr(unsafe.pointer(&namebuf[0]))), 0, 0);
return r1, err;
}
@@ -91,7 +94,7 @@ export func mkdir(name string, perm int64) (ret int64, errno int64) {
if !StringToBytes(&namebuf, name) {
return -1, ENAMETOOLONG
}
- r1, r2, err := Syscall(SYS_MKDIR, BytePtr(&namebuf[0]), perm, 0);
+ r1, r2, err := Syscall(SYS_MKDIR, int64(uintptr(unsafe.pointer(&namebuf[0]))), perm, 0);
return r1, err;
}
コアとなるコードの解説
上記のコード変更は、Go言語がシステムコールに引数を渡す際や、異なる構造体間でポインタを変換する際の基本的なアプローチが変更されたことを示しています。
-
import "unsafe"
の追加:unsafe
パッケージをインポートすることで、Goの型システムが通常課す制約をバイパスする機能が利用可能になります。これは、低レベルなメモリ操作を行うための前提条件です。 -
syscall.SockaddrInet4ToSockaddr(sa)
からunsafe.pointer(sa).(*syscall.Sockaddr)
への変更:- 旧来の
syscall.SockaddrInet4ToSockaddr
のような関数は、おそらく内部でアセンブリコードを使用して、SockaddrInet4
型のポインタをSockaddr
型のポインタに変換していました。 - 新しいコードでは、
unsafe.pointer(sa)
によってsa
(*syscall.SockaddrInet4
型)のポインタが汎用的なunsafe.Pointer
に変換されます。 - 次に、
.(*syscall.Sockaddr)
という型アサーションによって、このunsafe.Pointer
が*syscall.Sockaddr
型として解釈されます。これは、Goのコンパイラに対して「このメモリ領域はsyscall.Sockaddr
型の構造体として扱ってよい」と明示的に指示するものです。この操作は、Goの型システムによる安全チェックをバイパスするため、プログラマがメモリレイアウトを正確に理解している必要があります。
- 旧来の
-
BytePtr(&namebuf[0])
からint64(uintptr(unsafe.pointer(&namebuf[0])))
への変更:- 旧来の
BytePtr
関数は、バイトスライス(またはバイト配列)の先頭要素へのポインタを受け取り、そのアドレスをint64
として返していました。これは、システムコールがポインタを数値として受け取る場合に必要でした。 - 新しいコードでは、
&namebuf[0]
によってバイト配列の先頭要素へのポインタ(*byte
型)を取得します。 - これを
unsafe.pointer(...)
でunsafe.Pointer
に変換します。 - さらに
uintptr(...)
でunsafe.Pointer
をポインタのアドレスを表す整数型uintptr
に変換します。 - 最後に
int64(...)
でuintptr
をint64
にキャストします。これは、システムコールが引数としてint64
を期待するためです。
- 旧来の
これらの変更は、Go言語の低レベルな操作が、アセンブリ言語に依存するのではなく、Go言語自身が提供するunsafe
パッケージを介して行われるようになったことを明確に示しています。これにより、Goコードの可読性と移植性が向上し、Go言語の設計思想である「シンプルさ」と「効率性」がより一貫して実現されることになります。
関連リンク
- Go言語の
unsafe
パッケージに関する公式ドキュメント: https://pkg.go.dev/unsafe - Go言語のシステムコールに関する議論やドキュメント(Goのバージョンによって内容が異なる可能性がありますが、概念的な理解に役立ちます)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(特に
src/unsafe
ディレクトリやsrc/syscall
ディレクトリの歴史的なコミット) - Go言語の初期の設計に関するブログ記事やメーリングリストのアーカイブ(例: golang-nutsメーリングリスト)
unsafe.Pointer
の利用に関する技術記事や解説- A Guide to the Go
unsafe
Package: https://www.ardanlabs.com/blog/2019/02/a-guide-to-the-go-unsafe-package.html - Go:
unsafe.Pointer
anduintptr
: https://medium.com/@ankur_anand/go-unsafe-pointer-and-uintptr-1c74a408728f - The Laws of
unsafe.Pointer
: https://go.dev/blog/laws-of-reflection (これはreflect
パッケージに関するものですが、unsafe.Pointer
の利用に関する重要な原則が述べられています)
- A Guide to the Go
- Go言語の初期のコミット履歴(GitHubリポジトリ)