type FileSet struct { mutex sync.RWMutex // protects the file set base int // base offset for the next file files []*File // list of files in the order added to the set last atomic.Pointer[File] // cache of last file looked up } func (s *FileSet) AddFile(filename string, base, size int) *File { /* - - 以下抜粋 - - */ if base < s.base { panic(fmt.Sprintf("invalid base %d (should be >= %d)", …) } // base >= s.base && size >= 0 base += size + 1 // +1 because EOF also has a position // add the file to the file set s.base = base } 管理下にあるすべてのファイルに対し て、連続的で重複のない「アドレス空間」 を形成するというモデルを採用していま す。 FileSetに新たに追加されるファイルは、 そのbaseオフセットが、直前に追加され たファイルの範囲(base + size)よりも大 きい値でなければなりません。 ファイルは常にアドレス空間の末尾に追 加される、つまり追記専用の操作しか許 されません。 https://cs.opensource.google/go/go/+/refs/tags/go1.24.7:src/go/token/position.go;l=426
type FileSet struct { mutex sync.RWMutex // protects the file set base int // base offset for the next file files []*File // list of files in the order added to the set last atomic.Pointer[File] // cache of last file looked up } func (s *FileSet) AddFile(filename string, base, size int) *File { /* - - 以下抜粋 - - */ if base < s.base { panic(fmt.Sprintf("invalid base %d (should be >= %d)", …) } // base >= s.base && size >= 0 base += size + 1 // +1 because EOF also has a position // add the file to the file set s.base = base } 管理下にあるすべてのファイルに対し て、連続的で重複のない「アドレス空間」 を形成するというモデルを採用していま す。 FileSetに新たに追加されるファイルは、 そのbaseオフセットが、直前に追加され たファイルの範囲(base + size)よりも大 きい値でなければなりません。 ファイルは常にアドレス空間の末尾に追 加される、つまり追記専用の操作しか許 されません。 https://cs.opensource.google/go/go/+/refs/tags/go1.24.7:src/go/token/position.go;l=426 FileSet file_a.go (size=100) base=102 base+100+1 file_b.go (size=200) FileSet file_a.go (size=100) file_b.go (size=200) base=303 base+200+1 FileSet base=1 file_a.go (size=100)
type File struct { base int // Pos value range for this file is [base...base+size] } func searchFiles(a []*File, x int) int { i, found := slices.BinarySearchFunc(a, x, func(a *File, x int) int { return cmp.Compare(a.base, x) }) if !found { i-- } return i } func (s *FileSet) file(p Pos) *File { /* - - 抜粋 - - */ if i := searchFiles(s.files, int(p)); i >= 0 { f := s.files[i] } } token.Posは、FileSet内で管理されるす べてのファイルを通じて一意な値を持つ ように設計されています。 例えば、token.Posからどの*Fileに属す るかを特定するFile(s Pos)は二分探索 で検索するためファイル数が nの場合、 探索の計算量は O(log n)となり、非常に 効率的。 FileSetにファイルを追加する際は単純 な末尾追記なので 計算量は O(1) で爆 速。 https://cs.opensource.google/go/go/+/refs/tags/ go1.24.7:src/go/token/position.go;l=539
*token.FileSet, files []*token.File) { type tokenFileSet struct { // This type remained essentially consistent from go1.16 to go1.21. mutex sync.RWMutex base int files []*token.File _ *token.File // changed to atomic.Pointer[token.File] in go1.19 } const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{})) var _ [-delta * delta]int type uP = unsafe.Pointer var ptr *tokenFileSet *(*uP)(uP(&ptr)) = uP(fset) ptr.mutex.Lock() defer ptr.mutex.Unlock() } FileSetハック界(?)に流星の如く現 れたAddExistingFiles これは先ほどやりたかった FileSet のマージの為のhelperです。
struct { mutex sync.RWMutex // protects the file set base int // base offset for the next file files []*File // list of files in the order added to the set last atomic.Pointer[File] // cache of last file looked up } func AddExistingFiles(fset *token.FileSet, files []*token.File) { type tokenFileSet struct { // This type remained essentially consistent from go1.16 to go1.21. mutex sync.RWMutex base int files []*token.File _ *token.File // changed to atomic.Pointer[token.File] in go1.19 } type uP = unsafe.Pointer var ptr *tokenFileSet *(*uP)(uP(&ptr)) = uP(fset) } unsafe.Pointerを使った型変換 は、メモリ上のデータ配置(レイア ウト)が同一であるという強い仮定 に基づいている。 つまり、元のFileSetの定義が変 更されれば壊れる。 やってんな!
*token.FileSet, files []*token.File) { type tokenFileSet struct { // This type remained essentially consistent from go1.16 to go1.21. mutex sync.RWMutex base int files []*token.File _ *token.File // changed to atomic.Pointer[token.File] in go1.19 } // If the size of token.FileSet changes, this will fail to compile. const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{})) var _ [-delta * delta]int }
*token.FileSet, files []*token.File) { type tokenFileSet struct { // This type remained essentially consistent from go1.16 to go1.21. mutex sync.RWMutex base int files []*token.File _ *token.File // changed to atomic.Pointer[token.File] in go1.19 } // If the size of token.FileSet changes, this will fail to compile. const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{})) var _ [-delta * delta]int } 元のFileSetが変更された場合を 考慮し、unsafe.Pointerでの型変 換の暴発を防ぐためにコンパイル アサーションの仕組みを導入。 正常: 自作tokenFileSet とtoken.FileSet のサイズが同じなら、 delta は 0 になる。[0]int という配列 の宣言は有効なので、問題なくコンパイルが通る 異常: token.FileSet の構造が変わってサイズが変化すると、 delta は 0 以外になる。 -delta * delta は負の数になる。マイナスサイズの配列は作れないため、ここでコンパイルエラーが発 生
https://cs.opensource.google/go/go/+/refs/tags/go1.25.0:src/go/token/position.go;l=404 type FileSet struct { mutex sync.RWMutex // protects the file set base int // base offset for the next file tree tree // tree of files in ascending base order last atomic.Pointer[File] // cache of last file looked up } func (s *FileSet) AddExistingFiles(files ...*File) { s.mutex.Lock() defer s.mutex.Unlock() for _, f := range files { s.tree.add(f) s.base = max(s.base, f.Base()+f.Size()+1) } } []*token.Fileが、より効率的に データを扱えるTreeに代わり #73205 の要望にある AddExistingFilesも追加。 パフォーマンスも向上して unsafe なx/toolsを使わなくて良くなっ た! 万々歳!
https://cs.opensource.google/go/go/+/refs/tags/go1.24.7:src/go/token/position.go;l=426 https://cs.opensource.google/go/go/+/refs/tags/go1.25.0:src/go/token/position.go;l=404 // Go 1.24 までのFileSet type FileSet struct { mutex sync.RWMutex // protects the file set base int // base offset for the next file files []*File // list of files in the order added to the set last atomic.Pointer[File] // cache of last file looked up } // Go 1.25 のFileSet type FileSet struct { mutex sync.RWMutex // protects the file set base int // base offset for the next file tree tree // tree of files in ascending base order last atomic.Pointer[File] // cache of last file looked up } []*token.Fileが、より効率的に データを扱えるTreeに代わり #73205 の要望にある AddExistingFilesも追加。 パフォーマンスも向上して unsafe なx/toolsを使わなくて良くなっ た! 万々歳!
*token.FileSet, files []*token.File) { type tokenFileSet struct { // This type remained essentially consistent from go1.16 to go1.21. mutex sync.RWMutex base int files []*token.File _ *token.File // changed to atomic.Pointer[token.File] in go1.19 } // If the size of token.FileSet changes, this will fail to compile. const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{})) var _ [-delta * delta]int } 元のFileSetが変更された場合を 考慮し、unsafe.Pointerでの型変 換の暴発を防ぐためにコンパイル アサーションの仕組み 正常: 自作tokenFileSet とtoken.FileSet のサイズが同じなら、 delta は 0 になる。[0]int という配列 の宣言は有効なので、問題なくコンパイルが通る 異常: token.FileSet の構造が変わってサイズが変化すると、delta は 0 以外になる。 -delta * delta は負の数になる。マイナスサイズの配列は作れないため、ここでコンパイルエラーが 発生
“Goの†黒魔術†に対する防衛術 ~Defence Against the Go's dark Arts~” https://tech.andpad.co.jp/entry/2025/09/23/100000 「go:linknameディレクティブ」を詳しく知りたい人向け記事 “Go界隈で巻き起こった go:linkname 騒動について ” https://tech.andpad.co.jp/entry/2024/06/20/140000