Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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点です。

  1. プログラムがGoのビルドシステムでビルド可能になるように修正。
  2. os.EINVALruntime.Cgocalls() といったGoのAPIが変更されたため、それらに対応する。
  3. GMPライブラリの mpz_div_2exp()mpz_mul_2exp() 関数が、GMP 5.0.0以降で引数の型が変更されたことに対し、それ以前のバージョンと以降のバージョンの両方をサポートできるようにラッパー関数を導入する。これにより、特定のGMPバージョンに依存しない柔軟な対応を目指す。

変更の背景

この変更が行われた背景には、主に以下の2つの要因があります。

  1. Go 1のリリースとAPIの安定化: Go言語は、2012年3月28日にバージョン1.0がリリースされました。Go 1は、言語仕様と標準ライブラリの安定化を目的としており、それまでの開発版で頻繁に行われていたAPIの変更が最小限に抑えられることになりました。このコミットは、Go 1のリリースに先立って、既存のコードベースを新しい安定版APIに適合させるための作業の一環です。特に、エラーハンドリングに関する os.EINVAL の変更や、Cgo呼び出しの統計情報取得に関する runtime.Cgocalls() の変更が影響しています。
  2. 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 を計算します。つまり、op1op2 ビットだけ左シフトする操作に相当します。
  • mpz_div_2exp(rop, op1, op2): rop = op1 / 2^op2 を計算します。つまり、op1op2 ビットだけ右シフトする操作に相当します。
  • mp_bitcnt_t: GMP 5.0.0以降で導入された型で、ビット数を表すために使用されます。通常、符号なし整数型(例: unsigned long)のエイリアスですが、バージョンによって具体的な型が異なる可能性があります。

os.EINVALos.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.goSetString 関数内で、文字列から多倍長整数への変換に失敗した場合のエラーとして、os.EINVALos.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.gomain 関数内で、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 関数:
    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() へ
    }
    
    プログラムの最後に、Cgo呼び出しの総数を表示しています。Go 1のAPI変更に合わせて、runtime.Cgocalls()runtime.NumCgoCall() に変更されました。

関連リンク

参考にした情報源リンク

  • Go 1リリースノート (特にAPI変更に関するセクション)
  • GMPライブラリのドキュメント (特に mpz_mul_2exp, mpz_div_2exp の関数シグネチャと mp_bitcnt_t の説明)
  • Go言語のCgoに関する公式ブログ記事やドキュメント
  • Go言語のエラーハンドリングに関する情報 (特に os.EINVALos.ErrInvalid の違い)
  • Go言語の runtime パッケージに関するドキュメント (特に runtime.Cgocallsruntime.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点です。

  1. プログラムがGoのビルドシステムでビルド可能になるように修正。
  2. os.EINVALruntime.Cgocalls() といったGoのAPIが変更されたため、それらに対応する。
  3. GMPライブラリの mpz_div_2exp()mpz_mul_2exp() 関数が、GMP 5.0.0以降で引数の型が変更されたことに対し、それ以前のバージョンと以降のバージョンの両方をサポートできるようにラッパー関数を導入する。これにより、特定のGMPバージョンに依存しない柔軟な対応を目指す。

変更の背景

この変更が行われた背景には、主に以下の2つの要因があります。

  1. Go 1のリリースとAPIの安定化: Go言語は、2012年3月28日にバージョン1.0がリリースされました。Go 1は、言語仕様と標準ライブラリの安定化を目的としており、それまでの開発版で頻繁に行われていたAPIの変更が最小限に抑えられることになりました。このコミットは、Go 1のリリースに先立って、既存のコードベースを新しい安定版APIに適合させるための作業の一環です。特に、エラーハンドリングに関する os.EINVAL の変更や、Cgo呼び出しの統計情報取得に関する runtime.Cgocalls() の変更が影響しています。
  2. 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 を計算します。つまり、op1op2 ビットだけ左シフトする操作に相当します。
  • mpz_div_2exp(rop, op1, op2): rop = op1 / 2^op2 を計算します。つまり、op1op2 ビットだけ右シフトする操作に相当します。
  • mp_bitcnt_t: GMP 5.0.0以降で導入された型で、ビット数を表すために使用されます。通常、符号なし整数型(例: unsigned long)のエイリアスですが、バージョンによって具体的な型が異なる可能性があります。

os.EINVALos.ErrInvalid

Go 1より前のバージョンでは、無効な引数エラーを表すために os.EINVAL が使用されていました。Go 1では、より汎用的なエラー表現として os.ErrInvalid が導入され、os.EINVAL は非推奨となりました。os.ErrInvalidos パッケージ内で定義された事前定義エラー変数であり、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.goSetString 関数内で、文字列から多倍長整数への変換に失敗した場合のエラーとして、os.EINVALos.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.gomain 関数内で、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 関数:
    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() へ
    }
    
    プログラムの最後に、Cgo呼び出しの総数を表示しています。Go 1のAPI変更に合わせて、runtime.Cgocalls()runtime.NumCgoCall() に変更されました。

関連リンク

参考にした情報源リンク

  • Go 1リリースノート (特にAPI変更に関するセクション)
  • GMPライブラリのドキュメント (特に mpz_mul_2exp, mpz_div_2exp の関数シグネチャと mp_bitcnt_t の説明)
  • Go言語のCgoに関する公式ブログ記事やドキュメント
  • Go言語のエラーハンドリングに関する情報 (特に os.EINVALos.ErrInvalid の違い)
  • Go言語の runtime パッケージに関するドキュメント (特に runtime.Cgocallsruntime.NumCgoCall の変更点)