いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-413 (既存関数を置き換え)(go:linkname, コンパイラディレクティブ)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。忘れないようにここにメモメモ。。。

標準ライブラリの time.Sleep() ってどんな実装になってるんだろうって思ってソース見てみたら

// Sleep pauses the current goroutine for at least the duration d.
// A negative or zero duration causes Sleep to return immediately.
func Sleep(d Duration)

https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/time/sleep.go;l=9

ん?実装が無い。。?なんで?ってなりました。

んで、情報を探すと以下を発見。

forum.golangbridge.org

回答を見ると、実際には runtime パッケージの方に定義されているよとのこと。

てことで、src/runtime/time.go というファイルを見てみると

// timeSleep puts the current goroutine to sleep for at least ns nanoseconds.
//
//go:linkname timeSleep time.Sleep
func timeSleep(ns int64) {

https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/runtime/time.go;l=178

あった。よく見ると go:linkname というディレティブが付いている。

これは何だ?って調べると以下のとても分かりやすい説明をしてくださっている記事がありました。感謝。

zenn.dev

なるほど・・・。既存関数を置き換えられるのですね。当然安全ではないので、しょっちゅう利用するものでは無い。

でも、知っておくとイザというときに役に立ちそう。

以下、自分用で試してみたサンプルです。上のzennの記事で用いられているサンプルほぼそのままとなりましたが。

サンプル

package main

import (
    "log"
    "time"
    _ "unsafe"
)

// fixedNow は、固定の時間を返す time.Now() のモックです。
//
// 標準ライブラリの time.Now() を置き換えるために
// linknameコンパイラディレクティブを付与しています。
//
// linknameコンパイラディレクティブを利用するためには
// unsafeパッケージをインポート(明示的、暗黙的問わず)する必要があります。
//
// linknameは、標準ライブラリ内で利用されており、例えば time.Sleep() も
// 実際のソースコードを見ると以下の宣言となっており、宣言のみで実装がありません。
//
// # $(go env GOROOT)/src/time/sleep.go
//
// package time
//
// func Sleep(d Duration)
//
// 実体は、runtime.timeSleep() となっています。
//
// # $(go env GOROOT)/src/runtime/time.go
//
// package runtime
//
// //go:linkname timeSleep time.Sleep
// func timeSleep(ns int64) { ... }
//
//go:linkname fixedNow time.Now
func fixedNow() time.Time {
    return time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local)
}

func init() {
    log.SetFlags(0)
}

func main() {
    // linknameコンパイラディレクティブによって time.Now が置き換えられる
    log.Println(time.Now())
}

try-golang/examples/singleapp/replace_timenow_with_golinkname_directive at main · devlights/try-golang · GitHub

実行すると以下のようになります。

$ task
task: [build] go build -o app
task: [default] ./app
2000-01-01 00:00:00 +0000 UTC

time.Now() の結果を固定日時になるように置き換えているので、どのタイミングでtime.Now()を呼んでも同じ日時となります。

参考情報

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。