[インデックス 12700] ファイルの概要
このコミットは、Go言語の標準ライブラリの一部である misc/cgo/gmp
パッケージに対する更新です。具体的には、Go 1のリリースに伴うAPI変更への対応と、GNU Multiple Precision Arithmetic Library (GMP) のバージョン互換性問題を解決するための修正が含まれています。
コミット
commit 1abd8d8fd04fd64f90d3c1cbce675ab2317ec449
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Wed Mar 21 00:51:48 2012 +0800
misc/cgo/gmp: update for Go 1
1. make the program go buildable
2. update os.EINVAL and runtime.Cgocalls()
3. wrap mpz_div_2exp() and mpz_mul_2exp to support both
pre-5.0 and post-5.0 gmp (we really have no reason to
restrict ourselves to gmp 5.0+)
R=golang-dev, remyoudompheng, iant
CC=golang-dev
https://golang.org/cl/5847061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1abd8d8fd04fd64f90d3c1cbce675ab2317ec449
元コミット内容
このコミットの元の内容は、Go 1のリリースに合わせて misc/cgo/gmp
パッケージを更新することです。主な変更点は以下の3点です。
- プログラムがGoのビルドシステムでビルド可能になるように修正。
os.EINVAL
とruntime.Cgocalls()
といったGoのAPIが変更されたため、それらに対応する。- GMPライブラリの
mpz_div_2exp()
とmpz_mul_2exp()
関数が、GMP 5.0.0以降で引数の型が変更されたことに対し、それ以前のバージョンと以降のバージョンの両方をサポートできるようにラッパー関数を導入する。これにより、特定のGMPバージョンに依存しない柔軟な対応を目指す。
変更の背景
この変更が行われた背景には、主に以下の2つの要因があります。
- Go 1のリリースとAPIの安定化: Go言語は、2012年3月28日にバージョン1.0がリリースされました。Go 1は、言語仕様と標準ライブラリの安定化を目的としており、それまでの開発版で頻繁に行われていたAPIの変更が最小限に抑えられることになりました。このコミットは、Go 1のリリースに先立って、既存のコードベースを新しい安定版APIに適合させるための作業の一環です。特に、エラーハンドリングに関する
os.EINVAL
の変更や、Cgo呼び出しの統計情報取得に関するruntime.Cgocalls()
の変更が影響しています。 - GMPライブラリのバージョン互換性: GNU Multiple Precision Arithmetic Library (GMP) は、任意精度の算術演算を提供するC言語ライブラリです。このライブラリは、Goの
math/big
パッケージのような任意精度演算を必要とする場面で、Cgoを通じて利用されることがあります。GMPライブラリはバージョンアップに伴い、一部の関数のシグネチャ(特に引数の型)が変更されることがあります。このコミットでは、mpz_div_2exp()
とmpz_mul_2exp()
関数の第3引数の型が、GMP 5.0.0以降でmp_bitcnt_t
に変更されたことに対し、それ以前のバージョン(unsigned long
を使用)との互換性を保つ必要がありました。特定のGMPバージョンに依存すると、ユーザーのシステムにインストールされているGMPのバージョンによってビルドが失敗する可能性があるため、より広範な互換性を持たせるための対応が求められました。
前提知識の解説
Go言語とGo 1
Go言語はGoogleによって開発されたオープンソースのプログラミング言語です。シンプルさ、効率性、並行処理のサポートを重視しています。Go 1は、Go言語の最初の安定版リリースであり、これ以降、言語仕様と標準ライブラリの互換性が厳密に維持されるようになりました。これにより、Go言語で書かれたプログラムの長期的な安定性と保守性が保証されることになりました。
Cgo
Cgoは、GoプログラムからC言語のコードを呼び出すためのGoの機能です。Goのソースファイル内にC言語のコードを直接記述し、GoとCの間でデータを受け渡すことができます。Cgoを使用することで、既存のCライブラリ(このケースではGMP)をGoプログラムから利用することが可能になります。
import "C"
: Goのソースファイル内でimport "C"
を記述することで、Cgoが有効になります。#cgo LDFLAGS: -lgmp
: これはCgoのディレクティブで、コンパイル時にリンカに対してlibgmp
ライブラリをリンクするように指示します。C.関数名
: GoコードからC言語の関数を呼び出す際に使用します。C.型名
: GoコードからC言語の型を使用する際に使用します。
GNU Multiple Precision Arithmetic Library (GMP)
GMPは、任意精度の整数、有理数、浮動小数点数を扱うためのC言語ライブラリです。非常に高速な多倍長演算を提供し、暗号学、数論、科学計算など、高い精度と大きな数値を扱う必要があるアプリケーションで広く利用されています。
mpz_t
: GMPにおける整数型を表す構造体です。通常、mpz_init
で初期化し、mpz_clear
で解放します。mpz_mul_2exp(rop, op1, op2)
:rop = op1 * 2^op2
を計算します。つまり、op1
をop2
ビットだけ左シフトする操作に相当します。mpz_div_2exp(rop, op1, op2)
:rop = op1 / 2^op2
を計算します。つまり、op1
をop2
ビットだけ右シフトする操作に相当します。mp_bitcnt_t
: GMP 5.0.0以降で導入された型で、ビット数を表すために使用されます。通常、符号なし整数型(例:unsigned long
)のエイリアスですが、バージョンによって具体的な型が異なる可能性があります。
os.EINVAL
と os.ErrInvalid
Go 1より前のバージョンでは、無効な引数エラーを表すために os.EINVAL
が使用されていました。Go 1では、より汎用的なエラー表現として os.ErrInvalid
が導入され、os.EINVAL
は非推奨となりました。これは、Goのエラーハンドリングの標準化と一貫性向上の一環です。
runtime.Cgocalls()
と runtime.NumCgoCall()
Go 1より前のバージョンでは、Cgo呼び出しの総数を取得するために runtime.Cgocalls()
関数が使用されていました。Go 1では、この関数が runtime.NumCgoCall()
に名称変更されました。これは、APIの命名規則の統一と明確化を目的とした変更です。
技術的詳細
このコミットの技術的な詳細を掘り下げます。
Go 1 APIへの対応
-
os.EINVAL
からos.ErrInvalid
への変更:misc/cgo/gmp/gmp.go
のSetString
関数内で、文字列から多倍長整数への変換に失敗した場合のエラーとして、os.EINVAL
がos.ErrInvalid
に変更されています。これはGo 1での標準ライブラリのAPI変更に準拠するための修正です。--- a/misc/cgo/gmp/gmp.go +++ b/misc/cgo/gmp/gmp.go @@ -182,12 +194,12 @@ func (z *Int) SetInt64(x int64) *Int { func (z *Int) SetString(s string, base int) error { z.doinit() if base < 2 || base > 36 { - return os.EINVAL + return os.ErrInvalid } p := C.CString(s) defer C.free(unsafe.Pointer(p)) if C.mpz_set_str(&z.i[0], p, C.int(base)) < 0 { - return os.EINVAL + return os.ErrInvalid } return nil }
-
runtime.Cgocalls()
からruntime.NumCgoCall()
への変更:misc/cgo/gmp/pi.go
のmain
関数内で、Cgo呼び出しの総数を表示するために使用されていたruntime.Cgocalls()
がruntime.NumCgoCall()
に変更されています。これもGo 1でのAPI変更への対応です。--- a/misc/cgo/gmp/pi.go +++ b/misc/cgo/gmp/pi.go @@ -102,5 +102,5 @@ func main() { } } - fmt.Printf("\n%d calls; bit sizes: %d %d %d\n", runtime.Cgocalls(), numer.Len(), accum.Len(), denom.Len()) + fmt.Printf("\n%d calls; bit sizes: %d %d %d\n", runtime.NumCgoCall(), numer.Len(), accum.Len(), denom.Len()) }
GMPバージョン互換性のためのラッパー関数導入
このコミットの最も重要な技術的変更は、GMPライブラリの mpz_mul_2exp()
と mpz_div_2exp()
関数の引数型変更に対応するためのラッパー関数の導入です。
-
問題点: GMP 5.0.0以降では、これらの関数の第3引数(シフト量)の型が
unsigned long
からmp_bitcnt_t
に変更されました。CgoはGoの型とCの型を厳密にマッピングするため、この型変更はGoコードからの直接呼び出しに問題を引き起こします。もしGoコードがC.mp_bitcnt_t(s)
のようにキャストして呼び出すと、GMP 5.0.0より前のバージョンではコンパイルエラーになる可能性があります。逆に、C.ulong(s)
で呼び出すと、GMP 5.0.0以降で警告やエラーが発生する可能性があります。 -
解決策: Cgoのプリプロセッサディレクティブ(
/* ... */
で囲まれたCコードブロック)内に、Goから呼び出すためのラッパー関数_mpz_mul_2exp
と_mpz_div_2exp
を定義します。これらのラッパー関数は、常にunsigned long
型の引数を受け取り、内部で実際のGMP関数を呼び出します。これにより、Goコードからは常にC.ulong(s)
で呼び出すことができ、GMPのバージョンに依存しない一貫したインターフェースを提供します。/* #cgo LDFLAGS: -lgmp #include <gmp.h> #include <stdlib.h> // gmp 5.0.0+ changed the type of the 3rd argument to mp_bitcnt_t, // so, to support older versions, we wrap these two functions. void _mpz_mul_2exp(mpz_ptr a, mpz_ptr b, unsigned long n) { mpz_mul_2exp(a, b, n); } void _mpz_div_2exp(mpz_ptr a, mpz_ptr b, unsigned long n) { mpz_div_2exp(a, b, n); } */ import "C"
そして、Goコードからはこれらのラッパー関数を呼び出すように変更されています。
--- a/misc/cgo/gmp/gmp.go +++ b/misc/cgo/gmp/gmp.go @@ -265,7 +277,7 @@ func (z *Int) Mod(x, y *Int) *Int { func (z *Int) Lsh(x *Int, s uint) *Int { x.doinit() z.doinit() - C.mpz_mul_2exp(&z.i[0], &x.i[0], C.mp_bitcnt_t(s)) + C._mpz_mul_2exp(&z.i[0], &x.i[0], C.ulong(s)) return z } @@ -273,7 +285,7 @@ func (z *Int) Lsh(x *Int, s uint) *Int { func (z *Int) Rsh(x *Int, s uint) *Int { x.doinit() z.doinit() - C.mpz_div_2exp(&z.i[0], &x.i[0], C.mp_bitcnt_t(s)) + C._mpz_div_2exp(&z.i[0], &x.i[0], C.ulong(s)) return z }
このアプローチにより、Goの uint
型のシフト量をCの unsigned long
にキャストしてラッパー関数に渡し、ラッパー関数が内部で適切なGMP関数を呼び出すことで、異なるGMPバージョン間での互換性を確保しています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルと行に集中しています。
-
misc/cgo/gmp/gmp.go
:SetString
関数内のエラー返却値がos.EINVAL
からos.ErrInvalid
に変更。- Cgoプリプロセッサブロック内に
_mpz_mul_2exp
と_mpz_div_2exp
のラッパー関数が追加。 Lsh
関数内でC.mpz_mul_2exp
の呼び出しがC._mpz_mul_2exp
に変更され、引数型がC.mp_bitcnt_t(s)
からC.ulong(s)
に変更。Rsh
関数内でC.mpz_div_2exp
の呼び出しがC._mpz_div_2exp
に変更され、引数型がC.mp_bitcnt_t(s)
からC.ulong(s)
に変更。
-
misc/cgo/gmp/pi.go
:main
関数内のruntime.Cgocalls()
の呼び出しがruntime.NumCgoCall()
に変更。
コアとなるコードの解説
misc/cgo/gmp/gmp.go
このファイルは、Goの gmp
パッケージの主要な実装を含んでいます。Goの Int
型とGMPの mpz_t
型を橋渡しし、多倍長整数演算をGoから利用できるようにしています。
-
Cgoプリプロセッサブロック:
/* #cgo LDFLAGS: -lgmp #include <gmp.h> #include <stdlib.h> // gmp 5.0.0+ changed the type of the 3rd argument to mp_bitcnt_t, // so, to support older versions, we wrap these two functions. void _mpz_mul_2exp(mpz_ptr a, mpz_ptr b, unsigned long n) { mpz_mul_2exp(a, b, n); } void _mpz_div_2exp(mpz_ptr a, mpz_ptr b, unsigned long n) { mpz_div_2exp(a, b, n); } */ import "C"
このブロックは、CgoがGoコードとCライブラリを連携させるための鍵です。
#cgo LDFLAGS: -lgmp
: コンパイル時にGMPライブラリをリンクするよう指示します。#include <gmp.h>
と#include <stdlib.h>
: GMPライブラリと標準ライブラリのヘッダファイルをインクルードします。_mpz_mul_2exp
と_mpz_div_2exp
関数: これらはC言語で書かれたラッパー関数です。Goコードからこれらの関数を呼び出すことで、GMPのバージョンによる引数型の違いを吸収します。Go側からは常にunsigned long
として扱えるため、Goコードの複雑さを軽減し、互換性を高めています。
-
SetString
関数:func (z *Int) SetString(s string, base int) error { z.doinit() if base < 2 || base > 36 { return os.ErrInvalid // 変更点: os.EINVAL から os.ErrInvalid へ } p := C.CString(s) defer C.free(unsafe.Pointer(p)) if C.mpz_set_str(&z.i[0], p, C.int(base)) < 0 { return os.ErrInvalid // 変更点: os.EINVAL から os.ErrInvalid へ } return nil }
文字列を多倍長整数に変換する関数です。Go 1のAPI変更に合わせて、エラー返却値が
os.EINVAL
からos.ErrInvalid
に変更されました。 -
Lsh
(Left Shift) 関数:func (z *Int) Lsh(x *Int, s uint) *Int { x.doinit() z.doinit() C._mpz_mul_2exp(&z.i[0], &x.i[0], C.ulong(s)) // 変更点: C.mpz_mul_2exp から C._mpz_mul_2exp へ、引数型も変更 return z }
左シフト演算を行う関数です。GMPの
mpz_mul_2exp
関数を呼び出していましたが、GMPのバージョン互換性のため、新しく定義されたラッパー関数C._mpz_mul_2exp
を呼び出すように変更されました。シフト量s
はGoのuint
型ですが、Cgoを通じてCのunsigned long
型にキャストされて渡されます。 -
Rsh
(Right Shift) 関数:func (z *Int) Rsh(x *Int, s uint) *Int { x.doinit() z.doinit() C._mpz_div_2exp(&z.i[0], &x.i[0], C.ulong(s)) // 変更点: C.mpz_div_2exp から C._mpz_div_2exp へ、引数型も変更 return z }
右シフト演算を行う関数です。
Lsh
と同様に、GMPのmpz_div_2exp
関数を呼び出す代わりに、ラッパー関数C._mpz_div_2exp
を呼び出すように変更されました。
misc/cgo/gmp/pi.go
このファイルは、GMPパッケージを使用して円周率πを計算するサンプルプログラムです。
main
関数:
プログラムの最後に、Cgo呼び出しの総数を表示しています。Go 1のAPI変更に合わせて、func main() { // ... (円周率計算ロジック) ... fmt.Printf("\n%d calls; bit sizes: %d %d %d\n", runtime.NumCgoCall(), numer.Len(), accum.Len(), denom.Len()) // 変更点: runtime.Cgocalls() から runtime.NumCgoCall() へ }
runtime.Cgocalls()
がruntime.NumCgoCall()
に変更されました。
関連リンク
- Go言語公式サイト: https://go.dev/
- Go 1リリースノート: https://go.dev/doc/go1
- GNU Multiple Precision Arithmetic Library (GMP) 公式サイト: https://gmplib.org/
- GoのCgoに関するドキュメント: https://go.dev/blog/cgo
参考にした情報源リンク
- Go 1リリースノート (特にAPI変更に関するセクション)
- GMPライブラリのドキュメント (特に
mpz_mul_2exp
,mpz_div_2exp
の関数シグネチャとmp_bitcnt_t
の説明) - Go言語のCgoに関する公式ブログ記事やドキュメント
- Go言語のエラーハンドリングに関する情報 (特に
os.EINVAL
とos.ErrInvalid
の違い) - Go言語の
runtime
パッケージに関するドキュメント (特にruntime.Cgocalls
とruntime.NumCgoCall
の変更点)
[インデックス 12700] ファイルの概要
このコミットは、Go言語の標準ライブラリの一部である misc/cgo/gmp
パッケージに対する更新です。具体的には、Go 1のリリースに伴うAPI変更への対応と、GNU Multiple Precision Arithmetic Library (GMP) のバージョン互換性問題を解決するための修正が含まれています。
コミット
commit 1abd8d8fd04fd64f90d3c1cbce675ab2317ec449
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Wed Mar 21 00:51:48 2012 +0800
misc/cgo/gmp: update for Go 1
1. make the program go buildable
2. update os.EINVAL and runtime.Cgocalls()
3. wrap mpz_div_2exp() and mpz_mul_2exp to support both
pre-5.0 and post-5.0 gmp (we really have no reason to
restrict ourselves to gmp 5.0+)
R=golang-dev, remyoudompheng, iant
CC=golang-dev
https://golang.org/cl/5847061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1abd8d8fd04fd64f90d3c1cbce675ab2317ec449
元コミット内容
このコミットの元の内容は、Go 1のリリースに合わせて misc/cgo/gmp
パッケージを更新することです。主な変更点は以下の3点です。
- プログラムがGoのビルドシステムでビルド可能になるように修正。
os.EINVAL
とruntime.Cgocalls()
といったGoのAPIが変更されたため、それらに対応する。- GMPライブラリの
mpz_div_2exp()
とmpz_mul_2exp()
関数が、GMP 5.0.0以降で引数の型が変更されたことに対し、それ以前のバージョンと以降のバージョンの両方をサポートできるようにラッパー関数を導入する。これにより、特定のGMPバージョンに依存しない柔軟な対応を目指す。
変更の背景
この変更が行われた背景には、主に以下の2つの要因があります。
- Go 1のリリースとAPIの安定化: Go言語は、2012年3月28日にバージョン1.0がリリースされました。Go 1は、言語仕様と標準ライブラリの安定化を目的としており、それまでの開発版で頻繁に行われていたAPIの変更が最小限に抑えられることになりました。このコミットは、Go 1のリリースに先立って、既存のコードベースを新しい安定版APIに適合させるための作業の一環です。特に、エラーハンドリングに関する
os.EINVAL
の変更や、Cgo呼び出しの統計情報取得に関するruntime.Cgocalls()
の変更が影響しています。 - GMPライブラリのバージョン互換性: GNU Multiple Precision Arithmetic Library (GMP) は、任意精度の算術演算を提供するC言語ライブラリです。このライブラリは、Goの
math/big
パッケージのような任意精度演算を必要とする場面で、Cgoを通じて利用されることがあります。GMPライブラリはバージョンアップに伴い、一部の関数のシグネチャ(特に引数の型)が変更されることがあります。このコミットでは、mpz_div_2exp()
とmpz_mul_2exp()
関数の第3引数の型が、GMP 5.0.0以降でmp_bitcnt_t
に変更されたことに対し、それ以前のバージョン(unsigned long
を使用)との互換性を保つ必要がありました。特定のGMPバージョンに依存すると、ユーザーのシステムにインストールされているGMPのバージョンによってビルドが失敗する可能性があるため、より広範な互換性を持たせるための対応が求められました。
前提知識の解説
Go言語とGo 1
Go言語はGoogleによって開発されたオープンソースのプログラミング言語です。シンプルさ、効率性、並行処理のサポートを重視しています。Go 1は、Go言語の最初の安定版リリースであり、これ以降、言語仕様と標準ライブラリの互換性が厳密に維持されるようになりました。これにより、Go言語で書かれたプログラムの長期的な安定性と保守性が保証されることになりました。
Cgo
Cgoは、GoプログラムからC言語のコードを呼び出すためのGoの機能です。Goのソースファイル内にC言語のコードを直接記述し、GoとCの間でデータを受け渡すことができます。Cgoを使用することで、既存のCライブラリ(このケースではGMP)をGoプログラムから利用することが可能になります。
import "C"
: Goのソースファイル内でimport "C"
を記述することで、Cgoが有効になります。#cgo LDFLAGS: -lgmp
: これはCgoのディレクティブで、コンパイル時にリンカに対してlibgmp
ライブラリをリンクするように指示します。C.関数名
: GoコードからC言語の関数を呼び出す際に使用します。C.型名
: GoコードからC言語の型を使用する際に使用します。
GNU Multiple Precision Arithmetic Library (GMP)
GMPは、任意精度の整数、有理数、浮動小数点数を扱うためのC言語ライブラリです。非常に高速な多倍長演算を提供し、暗号学、数論、科学計算など、高い精度と大きな数値を扱う必要があるアプリケーションで広く利用されています。
mpz_t
: GMPにおける整数型を表す構造体です。通常、mpz_init
で初期化し、mpz_clear
で解放します。mpz_mul_2exp(rop, op1, op2)
:rop = op1 * 2^op2
を計算します。つまり、op1
をop2
ビットだけ左シフトする操作に相当します。mpz_div_2exp(rop, op1, op2)
:rop = op1 / 2^op2
を計算します。つまり、op1
をop2
ビットだけ右シフトする操作に相当します。mp_bitcnt_t
: GMP 5.0.0以降で導入された型で、ビット数を表すために使用されます。通常、符号なし整数型(例:unsigned long
)のエイリアスですが、バージョンによって具体的な型が異なる可能性があります。
os.EINVAL
と os.ErrInvalid
Go 1より前のバージョンでは、無効な引数エラーを表すために os.EINVAL
が使用されていました。Go 1では、より汎用的なエラー表現として os.ErrInvalid
が導入され、os.EINVAL
は非推奨となりました。os.ErrInvalid
は os
パッケージ内で定義された事前定義エラー変数であり、syscall.EINVAL
のような低レベルなシステムエラーコードとは異なり、Goの慣用的なエラー表現として利用されます。これは、Goのエラーハンドリングの標準化と一貫性向上の一環です。
runtime.Cgocalls()
と runtime.NumCgoCall()
Go 1より前のバージョンでは、Cgo呼び出しの総数を取得するために runtime.Cgocalls()
関数が使用されていました。Go 1では、この関数が runtime.NumCgoCall()
に名称変更されました。これは、APIの命名規則の統一と明確化を目的とした変更です。
技術的詳細
このコミットの技術的な詳細を掘り下げます。
Go 1 APIへの対応
-
os.EINVAL
からos.ErrInvalid
への変更:misc/cgo/gmp/gmp.go
のSetString
関数内で、文字列から多倍長整数への変換に失敗した場合のエラーとして、os.EINVAL
がos.ErrInvalid
に変更されています。これはGo 1での標準ライブラリのAPI変更に準拠するための修正です。--- a/misc/cgo/gmp/gmp.go +++ b/misc/cgo/gmp/gmp.go @@ -182,12 +194,12 @@ func (z *Int) SetInt64(x int64) *Int { func (z *Int) SetString(s string, base int) error { z.doinit() if base < 2 || base > 36 { - return os.EINVAL + return os.ErrInvalid } p := C.CString(s) defer C.free(unsafe.Pointer(p)) if C.mpz_set_str(&z.i[0], p, C.int(base)) < 0 { - return os.EINVAL + return os.ErrInvalid } return nil }
-
runtime.Cgocalls()
からruntime.NumCgoCall()
への変更:misc/cgo/gmp/pi.go
のmain
関数内で、Cgo呼び出しの総数を表示するために使用されていたruntime.Cgocalls()
がruntime.NumCgoCall()
に変更されています。これもGo 1でのAPI変更への対応です。--- a/misc/cgo/gmp/pi.go +++ b/misc/cgo/gmp/pi.go @@ -102,5 +102,5 @@ func main() { } } - fmt.Printf("\n%d calls; bit sizes: %d %d %d\n", runtime.Cgocalls(), numer.Len(), accum.Len(), denom.Len()) + fmt.Printf("\n%d calls; bit sizes: %d %d %d\n", runtime.NumCgoCall(), numer.Len(), accum.Len(), denom.Len()) }
GMPバージョン互換性のためのラッパー関数導入
このコミットの最も重要な技術的変更は、GMPライブラリの mpz_mul_2exp()
と mpz_div_2exp()
関数の引数型変更に対応するためのラッパー関数の導入です。
-
問題点: GMP 5.0.0以降では、これらの関数の第3引数(シフト量)の型が
unsigned long
からmp_bitcnt_t
に変更されました。CgoはGoの型とCの型を厳密にマッピングするため、この型変更はGoコードからの直接呼び出しに問題を引き起こします。もしGoコードがC.mp_bitcnt_t(s)
のようにキャストして呼び出すと、GMP 5.0.0より前のバージョンではコンパイルエラーになる可能性があります。逆に、C.ulong(s)
で呼び出すと、GMP 5.0.0以降で警告やエラーが発生する可能性があります。 -
解決策: Cgoのプリプロセッサディレクティブ(
/* ... */
で囲まれたCコードブロック)内に、Goから呼び出すためのラッパー関数_mpz_mul_2exp
と_mpz_div_2exp
を定義します。これらのラッパー関数は、常にunsigned long
型の引数を受け取り、内部で実際のGMP関数を呼び出します。これにより、Goコードからは常にC.ulong(s)
で呼び出すことができ、GMPのバージョンに依存しない一貫したインターフェースを提供します。/* #cgo LDFLAGS: -lgmp #include <gmp.h> #include <stdlib.h> // gmp 5.0.0+ changed the type of the 3rd argument to mp_bitcnt_t, // so, to support older versions, we wrap these two functions. void _mpz_mul_2exp(mpz_ptr a, mpz_ptr b, unsigned long n) { mpz_mul_2exp(a, b, n); } void _mpz_div_2exp(mpz_ptr a, mpz_ptr b, unsigned long n) { mpz_div_2exp(a, b, n); } */ import "C"
そして、Goコードからはこれらのラッパー関数を呼び出すように変更されています。
--- a/misc/cgo/gmp/gmp.go +++ b/misc/cgo/gmp/gmp.go @@ -265,7 +277,7 @@ func (z *Int) Mod(x, y *Int) *Int { func (z *Int) Lsh(x *Int, s uint) *Int { x.doinit() z.doinit() - C.mpz_mul_2exp(&z.i[0], &x.i[0], C.mp_bitcnt_t(s)) + C._mpz_mul_2exp(&z.i[0], &x.i[0], C.ulong(s)) return z } @@ -273,7 +285,7 @@ func (z *Int) Lsh(x *Int, s uint) *Int { func (z *Int) Rsh(x *Int, s uint) *Int { x.doinit() z.doinit() - C.mpz_div_2exp(&z.i[0], &x.i[0], C.mp_bitcnt_t(s)) + C._mpz_div_2exp(&z.i[0], &x.i[0], C.ulong(s)) return z }
このアプローチにより、Goの uint
型のシフト量をCの unsigned long
にキャストしてラッパー関数に渡し、ラッパー関数が内部で適切なGMP関数を呼び出すことで、異なるGMPバージョン間での互換性を確保しています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルと行に集中しています。
-
misc/cgo/gmp/gmp.go
:SetString
関数内のエラー返却値がos.EINVAL
からos.ErrInvalid
に変更。- Cgoプリプロセッサブロック内に
_mpz_mul_2exp
と_mpz_div_2exp
のラッパー関数が追加。 Lsh
関数内でC.mpz_mul_2exp
の呼び出しがC._mpz_mul_2exp
に変更され、引数型がC.mp_bitcnt_t(s)
からC.ulong(s)
に変更。Rsh
関数内でC.mpz_div_2exp
の呼び出しがC._mpz_div_2exp
に変更され、引数型がC.mp_bitcnt_t(s)
からC.ulong(s)
に変更。
-
misc/cgo/gmp/pi.go
:main
関数内のruntime.Cgocalls()
の呼び出しがruntime.NumCgoCall()
に変更。
コアとなるコードの解説
misc/cgo/gmp/gmp.go
このファイルは、Goの gmp
パッケージの主要な実装を含んでいます。Goの Int
型とGMPの mpz_t
型を橋渡しし、多倍長整数演算をGoから利用できるようにしています。
-
Cgoプリプロセッサブロック:
/* #cgo LDFLAGS: -lgmp #include <gmp.h> #include <stdlib.h> // gmp 5.0.0+ changed the type of the 3rd argument to mp_bitcnt_t, // so, to support older versions, we wrap these two functions. void _mpz_mul_2exp(mpz_ptr a, mpz_ptr b, unsigned long n) { mpz_mul_2exp(a, b, n); } void _mpz_div_2exp(mpz_ptr a, mpz_ptr b, unsigned long n) { mpz_div_2exp(a, b, n); } */ import "C"
このブロックは、CgoがGoコードとCライブラリを連携させるための鍵です。
#cgo LDFLAGS: -lgmp
: コンパイル時にGMPライブラリをリンクするよう指示します。#include <gmp.h>
と#include <stdlib.h>
: GMPライブラリと標準ライブラリのヘッダファイルをインクルードします。_mpz_mul_2exp
と_mpz_div_2exp
関数: これらはC言語で書かれたラッパー関数です。Goコードからこれらの関数を呼び出すことで、GMPのバージョンによる引数型の違いを吸収します。Go側からは常にunsigned long
として扱えるため、Goコードの複雑さを軽減し、互換性を高めています。
-
SetString
関数:func (z *Int) SetString(s string, base int) error { z.doinit() if base < 2 || base > 36 { return os.ErrInvalid // 変更点: os.EINVAL から os.ErrInvalid へ } p := C.CString(s) defer C.free(unsafe.Pointer(p)) if C.mpz_set_str(&z.i[0], p, C.int(base)) < 0 { return os.ErrInvalid // 変更点: os.EINVAL から os.ErrInvalid へ } return nil }
文字列を多倍長整数に変換する関数です。Go 1のAPI変更に合わせて、エラー返却値が
os.EINVAL
からos.ErrInvalid
に変更されました。 -
Lsh
(Left Shift) 関数:func (z *Int) Lsh(x *Int, s uint) *Int { x.doinit() z.doinit() C._mpz_mul_2exp(&z.i[0], &x.i[0], C.ulong(s)) // 変更点: C.mpz_mul_2exp から C._mpz_mul_2exp へ、引数型も変更 return z }
左シフト演算を行う関数です。GMPの
mpz_mul_2exp
関数を呼び出していましたが、GMPのバージョン互換性のため、新しく定義されたラッパー関数C._mpz_mul_2exp
を呼び出すように変更されました。シフト量s
はGoのuint
型ですが、Cgoを通じてCのunsigned long
型にキャストされて渡されます。 -
Rsh
(Right Shift) 関数:func (z *Int) Rsh(x *Int, s uint) *Int { x.doinit() z.doinit() C._mpz_div_2exp(&z.i[0], &x.i[0], C.ulong(s)) // 変更点: C.mpz_div_2exp から C._mpz_div_2exp へ、引数型も変更 return z }
右シフト演算を行う関数です。
Lsh
と同様に、GMPのmpz_div_2exp
関数を呼び出す代わりに、ラッパー関数C._mpz_div_2exp
を呼び出すように変更されました。
misc/cgo/gmp/pi.go
このファイルは、GMPパッケージを使用して円周率πを計算するサンプルプログラムです。
main
関数:
プログラムの最後に、Cgo呼び出しの総数を表示しています。Go 1のAPI変更に合わせて、func main() { // ... (円周率計算ロジック) ... fmt.Printf("\n%d calls; bit sizes: %d %d %d\n", runtime.NumCgoCall(), numer.Len(), accum.Len(), denom.Len()) // 変更点: runtime.Cgocalls() から runtime.NumCgoCall() へ }
runtime.Cgocalls()
がruntime.NumCgoCall()
に変更されました。
関連リンク
- Go言語公式サイト: https://go.dev/
- Go 1リリースノート: https://go.dev/doc/go1
- GNU Multiple Precision Arithmetic Library (GMP) 公式サイト: https://gmplib.org/
- GoのCgoに関するドキュメント: https://go.dev/blog/cgo
参考にした情報源リンク
- Go 1リリースノート (特にAPI変更に関するセクション)
- GMPライブラリのドキュメント (特に
mpz_mul_2exp
,mpz_div_2exp
の関数シグネチャとmp_bitcnt_t
の説明) - Go言語のCgoに関する公式ブログ記事やドキュメント
- Go言語のエラーハンドリングに関する情報 (特に
os.EINVAL
とos.ErrInvalid
の違い) - Go言語の
runtime
パッケージに関するドキュメント (特にruntime.Cgocalls
とruntime.NumCgoCall
の変更点)