mirror of
https://github.com/xtaci/kcptun.git
synced 2024-04-21 12:32:32 +00:00
add missing vendor files
This commit is contained in:
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//+build arm64,!gccgo
|
||||
|
||||
// See https://www.kernel.org/doc/Documentation/arm64/cpu-feature-registers.txt
|
||||
|
||||
// func getMidr
|
||||
TEXT ·getMidr(SB), 7, $0
|
||||
WORD $0xd5380000 // mrs x0, midr_el1 /* Main ID Register */
|
||||
MOVD R0, midr+0(FP)
|
||||
RET
|
||||
|
||||
// func getProcFeatures
|
||||
TEXT ·getProcFeatures(SB), 7, $0
|
||||
WORD $0xd5380400 // mrs x0, id_aa64pfr0_el1 /* Processor Feature Register 0 */
|
||||
MOVD R0, procFeatures+0(FP)
|
||||
RET
|
||||
|
||||
// func getInstAttributes
|
||||
TEXT ·getInstAttributes(SB), 7, $0
|
||||
WORD $0xd5380600 // mrs x0, id_aa64isar0_el1 /* Instruction Set Attribute Register 0 */
|
||||
WORD $0xd5380621 // mrs x1, id_aa64isar1_el1 /* Instruction Set Attribute Register 1 */
|
||||
MOVD R0, instAttrReg0+0(FP)
|
||||
MOVD R1, instAttrReg1+8(FP)
|
||||
RET
|
||||
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//+build arm64,!gccgo,!noasm,!appengine
|
||||
|
||||
package cpuid
|
||||
|
||||
func getMidr() (midr uint64)
|
||||
func getProcFeatures() (procFeatures uint64)
|
||||
func getInstAttributes() (instAttrReg0, instAttrReg1 uint64)
|
||||
|
||||
func initCPU() {
|
||||
cpuid = func(uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
cpuidex = func(x, y uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
xgetbv = func(uint32) (a, b uint32) { return 0, 0 }
|
||||
rdtscpAsm = func() (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
}
|
||||
|
||||
func addInfo(c *CPUInfo) {
|
||||
// ARM64 disabled for now.
|
||||
if true {
|
||||
return
|
||||
}
|
||||
// midr := getMidr()
|
||||
|
||||
// MIDR_EL1 - Main ID Register
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | Implementer | [31-24] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | Variant | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | Architecture | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | PartNum | [15-4] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | Revision | [3-0] | y |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
// fmt.Printf(" implementer: 0x%02x\n", (midr>>24)&0xff)
|
||||
// fmt.Printf(" variant: 0x%01x\n", (midr>>20)&0xf)
|
||||
// fmt.Printf("architecture: 0x%01x\n", (midr>>16)&0xf)
|
||||
// fmt.Printf(" part num: 0x%03x\n", (midr>>4)&0xfff)
|
||||
// fmt.Printf(" revision: 0x%01x\n", (midr>>0)&0xf)
|
||||
|
||||
procFeatures := getProcFeatures()
|
||||
|
||||
// ID_AA64PFR0_EL1 - Processor Feature Register 0
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | DIT | [51-48] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SVE | [35-32] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | GIC | [27-24] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | AdvSIMD | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | FP | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | EL3 | [15-12] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | EL2 | [11-8] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | EL1 | [7-4] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | EL0 | [3-0] | n |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
var f ArmFlags
|
||||
// if procFeatures&(0xf<<48) != 0 {
|
||||
// fmt.Println("DIT")
|
||||
// }
|
||||
if procFeatures&(0xf<<32) != 0 {
|
||||
f |= SVE
|
||||
}
|
||||
if procFeatures&(0xf<<20) != 15<<20 {
|
||||
f |= ASIMD
|
||||
if procFeatures&(0xf<<20) == 1<<20 {
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64pfr0_el1
|
||||
// 0b0001 --> As for 0b0000, and also includes support for half-precision floating-point arithmetic.
|
||||
f |= FPHP
|
||||
f |= ASIMDHP
|
||||
}
|
||||
}
|
||||
if procFeatures&(0xf<<16) != 0 {
|
||||
f |= FP
|
||||
}
|
||||
|
||||
instAttrReg0, instAttrReg1 := getInstAttributes()
|
||||
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
|
||||
//
|
||||
// ID_AA64ISAR0_EL1 - Instruction Set Attribute Register 0
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | TS | [55-52] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | FHM | [51-48] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | DP | [47-44] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SM4 | [43-40] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SM3 | [39-36] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SHA3 | [35-32] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | RDM | [31-28] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | ATOMICS | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | CRC32 | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SHA2 | [15-12] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SHA1 | [11-8] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | AES | [7-4] | y |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
// if instAttrReg0&(0xf<<52) != 0 {
|
||||
// fmt.Println("TS")
|
||||
// }
|
||||
// if instAttrReg0&(0xf<<48) != 0 {
|
||||
// fmt.Println("FHM")
|
||||
// }
|
||||
if instAttrReg0&(0xf<<44) != 0 {
|
||||
f |= ASIMDDP
|
||||
}
|
||||
if instAttrReg0&(0xf<<40) != 0 {
|
||||
f |= SM4
|
||||
}
|
||||
if instAttrReg0&(0xf<<36) != 0 {
|
||||
f |= SM3
|
||||
}
|
||||
if instAttrReg0&(0xf<<32) != 0 {
|
||||
f |= SHA3
|
||||
}
|
||||
if instAttrReg0&(0xf<<28) != 0 {
|
||||
f |= ASIMDRDM
|
||||
}
|
||||
if instAttrReg0&(0xf<<20) != 0 {
|
||||
f |= ATOMICS
|
||||
}
|
||||
if instAttrReg0&(0xf<<16) != 0 {
|
||||
f |= CRC32
|
||||
}
|
||||
if instAttrReg0&(0xf<<12) != 0 {
|
||||
f |= SHA2
|
||||
}
|
||||
if instAttrReg0&(0xf<<12) == 2<<12 {
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
|
||||
// 0b0010 --> As 0b0001, plus SHA512H, SHA512H2, SHA512SU0, and SHA512SU1 instructions implemented.
|
||||
f |= SHA512
|
||||
}
|
||||
if instAttrReg0&(0xf<<8) != 0 {
|
||||
f |= SHA1
|
||||
}
|
||||
if instAttrReg0&(0xf<<4) != 0 {
|
||||
f |= AES
|
||||
}
|
||||
if instAttrReg0&(0xf<<4) == 2<<4 {
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
|
||||
// 0b0010 --> As for 0b0001, plus PMULL/PMULL2 instructions operating on 64-bit data quantities.
|
||||
f |= PMULL
|
||||
}
|
||||
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar1_el1
|
||||
//
|
||||
// ID_AA64ISAR1_EL1 - Instruction set attribute register 1
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | GPI | [31-28] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | GPA | [27-24] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | LRCPC | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | FCMA | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | JSCVT | [15-12] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | API | [11-8] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | APA | [7-4] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | DPB | [3-0] | y |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
// if instAttrReg1&(0xf<<28) != 0 {
|
||||
// fmt.Println("GPI")
|
||||
// }
|
||||
if instAttrReg1&(0xf<<28) != 24 {
|
||||
f |= GPA
|
||||
}
|
||||
if instAttrReg1&(0xf<<20) != 0 {
|
||||
f |= LRCPC
|
||||
}
|
||||
if instAttrReg1&(0xf<<16) != 0 {
|
||||
f |= FCMA
|
||||
}
|
||||
if instAttrReg1&(0xf<<12) != 0 {
|
||||
f |= JSCVT
|
||||
}
|
||||
// if instAttrReg1&(0xf<<8) != 0 {
|
||||
// fmt.Println("API")
|
||||
// }
|
||||
// if instAttrReg1&(0xf<<4) != 0 {
|
||||
// fmt.Println("APA")
|
||||
// }
|
||||
if instAttrReg1&(0xf<<0) != 0 {
|
||||
f |= DCPOP
|
||||
}
|
||||
c.Arm = f
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module github.com/klauspost/cpuid
|
||||
|
||||
go 1.12
|
||||
+408
@@ -0,0 +1,408 @@
|
||||
// Code generated by command: go run gen.go -out galois_gen_amd64.s -stubs galois_gen_amd64.go. DO NOT EDIT.
|
||||
|
||||
// +build !appengine
|
||||
// +build !noasm
|
||||
// +build !nogen
|
||||
// +build gc
|
||||
|
||||
package reedsolomon
|
||||
|
||||
// mulAvxTwo_1x1 takes 1 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_1x2 takes 1 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_1x3 takes 1 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_1x4 takes 1 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_1x5 takes 1 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_1x6 takes 1 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_1x7 takes 1 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_1x8 takes 1 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x1 takes 2 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x2 takes 2 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x3 takes 2 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x4 takes 2 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x5 takes 2 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x6 takes 2 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x7 takes 2 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x8 takes 2 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x1 takes 3 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x2 takes 3 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x3 takes 3 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x4 takes 3 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x5 takes 3 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x6 takes 3 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x7 takes 3 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x8 takes 3 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x1 takes 4 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x2 takes 4 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x3 takes 4 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x4 takes 4 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x5 takes 4 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x6 takes 4 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x7 takes 4 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x8 takes 4 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x1 takes 5 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x2 takes 5 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x3 takes 5 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x4 takes 5 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x5 takes 5 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x6 takes 5 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x7 takes 5 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x8 takes 5 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x1 takes 6 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x2 takes 6 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x3 takes 6 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x4 takes 6 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x5 takes 6 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x6 takes 6 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x7 takes 6 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x8 takes 6 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x1 takes 7 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x2 takes 7 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x3 takes 7 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x4 takes 7 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x5 takes 7 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x6 takes 7 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x7 takes 7 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x8 takes 7 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x1 takes 8 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x2 takes 8 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x3 takes 8 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x4 takes 8 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x5 takes 8 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x6 takes 8 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x7 takes 8 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x8 takes 8 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x1 takes 9 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x2 takes 9 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x3 takes 9 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x4 takes 9 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x5 takes 9 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x6 takes 9 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x7 takes 9 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x8 takes 9 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x1 takes 10 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x2 takes 10 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x3 takes 10 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x4 takes 10 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x5 takes 10 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x6 takes 10 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x7 takes 10 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x8 takes 10 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
+18526
File diff suppressed because it is too large
Load Diff
+11
@@ -0,0 +1,11 @@
|
||||
//+build !amd64 noasm appengine gccgo nogen
|
||||
|
||||
package reedsolomon
|
||||
|
||||
const maxAvx2Inputs = 0
|
||||
const maxAvx2Outputs = 0
|
||||
const avx2CodeGen = false
|
||||
|
||||
func galMulSlicesAvx2(matrix []byte, in, out [][]byte, start, stop int) int {
|
||||
panic("avx2 codegen not available")
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
// Code generated by command: go generate gen.go. DO NOT EDIT.
|
||||
|
||||
// +build !appengine
|
||||
// +build !noasm
|
||||
// +build gc
|
||||
// +build !nogen
|
||||
|
||||
package reedsolomon
|
||||
|
||||
import "fmt"
|
||||
|
||||
const avx2CodeGen = true
|
||||
const maxAvx2Inputs = 10
|
||||
const maxAvx2Outputs = 8
|
||||
|
||||
func galMulSlicesAvx2(matrix []byte, in, out [][]byte, start, stop int) int {
|
||||
n := stop - start
|
||||
n = (n >> 5) << 5
|
||||
|
||||
switch len(in) {
|
||||
case 1:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_1x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_1x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_1x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_1x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_1x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_1x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_1x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_1x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 2:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_2x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_2x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_2x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_2x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_2x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_2x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_2x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_2x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 3:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_3x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_3x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_3x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_3x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_3x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_3x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_3x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_3x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 4:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_4x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_4x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_4x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_4x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_4x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_4x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_4x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_4x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 5:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_5x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_5x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_5x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_5x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_5x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_5x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_5x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_5x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 6:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_6x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_6x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_6x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_6x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_6x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_6x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_6x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_6x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 7:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_7x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_7x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_7x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_7x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_7x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_7x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_7x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_7x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 8:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_8x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_8x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_8x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_8x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_8x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_8x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_8x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_8x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 9:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_9x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_9x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_9x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_9x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_9x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_9x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_9x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_9x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 10:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_10x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_10x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_10x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_10x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_10x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_10x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_10x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_10x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("unhandled size: %dx%d", len(in), len(out)))
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//+build !amd64 noasm appengine gccgo
|
||||
|
||||
// Copyright 2020, Klaus Post, see LICENSE for details.
|
||||
|
||||
package reedsolomon
|
||||
|
||||
func (r *reedSolomon) codeSomeShardsAvx512(matrixRows, inputs, outputs [][]byte, outputCount, byteCount int) {
|
||||
panic("codeSomeShardsAvx512 should not be called if built without asm")
|
||||
}
|
||||
|
||||
func (r *reedSolomon) codeSomeShardsAvx512P(matrixRows, inputs, outputs [][]byte, outputCount, byteCount int) {
|
||||
panic("codeSomeShardsAvx512P should not be called if built without asm")
|
||||
}
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
//+build generate
|
||||
|
||||
//go:generate go run gen.go -out galois_gen_amd64.s -stubs galois_gen_amd64.go
|
||||
//go:generate gofmt -w galois_gen_switch_amd64.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
. "github.com/mmcloughlin/avo/build"
|
||||
"github.com/mmcloughlin/avo/buildtags"
|
||||
. "github.com/mmcloughlin/avo/operand"
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
// Technically we can do slightly bigger, but we stay reasonable.
|
||||
const inputMax = 10
|
||||
const outputMax = 8
|
||||
|
||||
var switchDefs [inputMax][outputMax]string
|
||||
var switchDefsX [inputMax][outputMax]string
|
||||
|
||||
const perLoopBits = 5
|
||||
const perLoop = 1 << perLoopBits
|
||||
|
||||
func main() {
|
||||
Constraint(buildtags.Not("appengine").ToConstraint())
|
||||
Constraint(buildtags.Not("noasm").ToConstraint())
|
||||
Constraint(buildtags.Not("nogen").ToConstraint())
|
||||
Constraint(buildtags.Term("gc").ToConstraint())
|
||||
|
||||
for i := 1; i <= inputMax; i++ {
|
||||
for j := 1; j <= outputMax; j++ {
|
||||
//genMulAvx2(fmt.Sprintf("mulAvxTwoXor_%dx%d", i, j), i, j, true)
|
||||
genMulAvx2(fmt.Sprintf("mulAvxTwo_%dx%d", i, j), i, j, false)
|
||||
}
|
||||
}
|
||||
f, err := os.Create("galois_gen_switch_amd64.go")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
w := bufio.NewWriter(f)
|
||||
defer w.Flush()
|
||||
w.WriteString(`// Code generated by command: go generate ` + os.Getenv("GOFILE") + `. DO NOT EDIT.
|
||||
|
||||
// +build !appengine
|
||||
// +build !noasm
|
||||
// +build gc
|
||||
// +build !nogen
|
||||
|
||||
package reedsolomon
|
||||
|
||||
import "fmt"
|
||||
|
||||
`)
|
||||
|
||||
w.WriteString("const avx2CodeGen = true\n")
|
||||
w.WriteString(fmt.Sprintf("const maxAvx2Inputs = %d\nconst maxAvx2Outputs = %d\n", inputMax, outputMax))
|
||||
w.WriteString(`
|
||||
|
||||
func galMulSlicesAvx2(matrix []byte, in, out [][]byte, start, stop int) int {
|
||||
n := stop-start
|
||||
`)
|
||||
|
||||
w.WriteString(fmt.Sprintf("n = (n>>%d)<<%d\n\n", perLoopBits, perLoopBits))
|
||||
w.WriteString(`switch len(in) {
|
||||
`)
|
||||
for in, defs := range switchDefs[:] {
|
||||
w.WriteString(fmt.Sprintf(" case %d:\n switch len(out) {\n", in+1))
|
||||
for out, def := range defs[:] {
|
||||
w.WriteString(fmt.Sprintf(" case %d:\n", out+1))
|
||||
w.WriteString(def)
|
||||
}
|
||||
w.WriteString("}\n")
|
||||
}
|
||||
w.WriteString(`}
|
||||
panic(fmt.Sprintf("unhandled size: %dx%d", len(in), len(out)))
|
||||
}
|
||||
`)
|
||||
Generate()
|
||||
}
|
||||
|
||||
func genMulAvx2(name string, inputs int, outputs int, xor bool) {
|
||||
total := inputs * outputs
|
||||
|
||||
doc := []string{
|
||||
fmt.Sprintf("%s takes %d inputs and produces %d outputs.", name, inputs, outputs),
|
||||
}
|
||||
if !xor {
|
||||
doc = append(doc, "The output is initialized to 0.")
|
||||
}
|
||||
|
||||
// Load shuffle masks on every use.
|
||||
var loadNone bool
|
||||
// Use registers for destination registers.
|
||||
var regDst = true
|
||||
|
||||
// lo, hi, 1 in, 1 out, 2 tmp, 1 mask
|
||||
est := total*2 + outputs + 5
|
||||
if outputs == 1 {
|
||||
// We don't need to keep a copy of the input if only 1 output.
|
||||
est -= 2
|
||||
}
|
||||
|
||||
if est > 16 {
|
||||
loadNone = true
|
||||
// We run out of GP registers first, now.
|
||||
if inputs+outputs > 12 {
|
||||
regDst = false
|
||||
}
|
||||
}
|
||||
|
||||
TEXT(name, 0, fmt.Sprintf("func(matrix []byte, in [][]byte, out [][]byte, start, n int)"))
|
||||
|
||||
// SWITCH DEFINITION:
|
||||
s := fmt.Sprintf(" mulAvxTwo_%dx%d(matrix, in, out, start, n)\n", inputs, outputs)
|
||||
s += fmt.Sprintf("\t\t\t\treturn n\n")
|
||||
switchDefs[inputs-1][outputs-1] = s
|
||||
|
||||
if loadNone {
|
||||
Comment("Loading no tables to registers")
|
||||
} else {
|
||||
// loadNone == false
|
||||
Comment("Loading all tables to registers")
|
||||
}
|
||||
|
||||
Doc(doc...)
|
||||
Pragma("noescape")
|
||||
Commentf("Full registers estimated %d YMM used", est)
|
||||
|
||||
length := Load(Param("n"), GP64())
|
||||
matrixBase := GP64()
|
||||
MOVQ(Param("matrix").Base().MustAddr(), matrixBase)
|
||||
SHRQ(U8(perLoopBits), length)
|
||||
TESTQ(length, length)
|
||||
JZ(LabelRef(name + "_end"))
|
||||
|
||||
dst := make([]reg.VecVirtual, outputs)
|
||||
dstPtr := make([]reg.GPVirtual, outputs)
|
||||
outBase := Param("out").Base().MustAddr()
|
||||
outSlicePtr := GP64()
|
||||
MOVQ(outBase, outSlicePtr)
|
||||
for i := range dst {
|
||||
dst[i] = YMM()
|
||||
if !regDst {
|
||||
continue
|
||||
}
|
||||
ptr := GP64()
|
||||
MOVQ(Mem{Base: outSlicePtr, Disp: i * 24}, ptr)
|
||||
dstPtr[i] = ptr
|
||||
}
|
||||
|
||||
inLo := make([]reg.VecVirtual, total)
|
||||
inHi := make([]reg.VecVirtual, total)
|
||||
|
||||
for i := range inLo {
|
||||
if loadNone {
|
||||
break
|
||||
}
|
||||
tableLo := YMM()
|
||||
tableHi := YMM()
|
||||
VMOVDQU(Mem{Base: matrixBase, Disp: i * 64}, tableLo)
|
||||
VMOVDQU(Mem{Base: matrixBase, Disp: i*64 + 32}, tableHi)
|
||||
inLo[i] = tableLo
|
||||
inHi[i] = tableHi
|
||||
}
|
||||
|
||||
inPtrs := make([]reg.GPVirtual, inputs)
|
||||
inSlicePtr := GP64()
|
||||
MOVQ(Param("in").Base().MustAddr(), inSlicePtr)
|
||||
for i := range inPtrs {
|
||||
ptr := GP64()
|
||||
MOVQ(Mem{Base: inSlicePtr, Disp: i * 24}, ptr)
|
||||
inPtrs[i] = ptr
|
||||
}
|
||||
|
||||
tmpMask := GP64()
|
||||
MOVQ(U32(15), tmpMask)
|
||||
lowMask := YMM()
|
||||
MOVQ(tmpMask, lowMask.AsX())
|
||||
VPBROADCASTB(lowMask.AsX(), lowMask)
|
||||
|
||||
offset := GP64()
|
||||
MOVQ(Param("start").MustAddr(), offset)
|
||||
Label(name + "_loop")
|
||||
if xor {
|
||||
Commentf("Load %d outputs", outputs)
|
||||
} else {
|
||||
Commentf("Clear %d outputs", outputs)
|
||||
}
|
||||
for i := range dst {
|
||||
if xor {
|
||||
if regDst {
|
||||
VMOVDQU(Mem{Base: dstPtr[i], Index: offset, Scale: 1}, dst[i])
|
||||
continue
|
||||
}
|
||||
ptr := GP64()
|
||||
MOVQ(outBase, ptr)
|
||||
VMOVDQU(Mem{Base: ptr, Index: offset, Scale: 1}, dst[i])
|
||||
} else {
|
||||
VPXOR(dst[i], dst[i], dst[i])
|
||||
}
|
||||
}
|
||||
|
||||
lookLow, lookHigh := YMM(), YMM()
|
||||
inLow, inHigh := YMM(), YMM()
|
||||
for i := range inPtrs {
|
||||
Commentf("Load and process 32 bytes from input %d to %d outputs", i, outputs)
|
||||
VMOVDQU(Mem{Base: inPtrs[i], Index: offset, Scale: 1}, inLow)
|
||||
VPSRLQ(U8(4), inLow, inHigh)
|
||||
VPAND(lowMask, inLow, inLow)
|
||||
VPAND(lowMask, inHigh, inHigh)
|
||||
for j := range dst {
|
||||
if loadNone {
|
||||
VMOVDQU(Mem{Base: matrixBase, Disp: 64 * (i*outputs + j)}, lookLow)
|
||||
VMOVDQU(Mem{Base: matrixBase, Disp: 32 + 64*(i*outputs+j)}, lookHigh)
|
||||
VPSHUFB(inLow, lookLow, lookLow)
|
||||
VPSHUFB(inHigh, lookHigh, lookHigh)
|
||||
} else {
|
||||
VPSHUFB(inLow, inLo[i*outputs+j], lookLow)
|
||||
VPSHUFB(inHigh, inHi[i*outputs+j], lookHigh)
|
||||
}
|
||||
VPXOR(lookLow, lookHigh, lookLow)
|
||||
VPXOR(lookLow, dst[j], dst[j])
|
||||
}
|
||||
}
|
||||
Commentf("Store %d outputs", outputs)
|
||||
for i := range dst {
|
||||
if regDst {
|
||||
VMOVDQU(dst[i], Mem{Base: dstPtr[i], Index: offset, Scale: 1})
|
||||
continue
|
||||
}
|
||||
ptr := GP64()
|
||||
MOVQ(Mem{Base: outSlicePtr, Disp: i * 24}, ptr)
|
||||
VMOVDQU(dst[i], Mem{Base: ptr, Index: offset, Scale: 1})
|
||||
}
|
||||
Comment("Prepare for next loop")
|
||||
ADDQ(U8(perLoop), offset)
|
||||
DECQ(length)
|
||||
JNZ(LabelRef(name + "_loop"))
|
||||
VZEROUPPER()
|
||||
|
||||
Label(name + "_end")
|
||||
RET()
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
module github.com/klauspost/reedsolomon
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/klauspost/cpuid v1.2.4
|
||||
)
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
github.com/klauspost/cpuid v1.2.4 h1:EBfaK0SWSwk+fgk6efYFWdzl8MwRWoOO1gkmiaTXPW4=
|
||||
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2018, Michael McLoughlin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
// Package attr provides attributes for text and data sections.
|
||||
package attr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Attribute represents TEXT or DATA flags.
|
||||
type Attribute uint16
|
||||
|
||||
// Reference: https://github.com/golang/go/blob/aafe257390cc9048e8b5df898fabd79a9e0d4c39/src/runtime/textflag.h#L11-L37
|
||||
//
|
||||
// // Don't profile the marked routine. This flag is deprecated.
|
||||
// #define NOPROF 1
|
||||
// // It is ok for the linker to get multiple of these symbols. It will
|
||||
// // pick one of the duplicates to use.
|
||||
// #define DUPOK 2
|
||||
// // Don't insert stack check preamble.
|
||||
// #define NOSPLIT 4
|
||||
// // Put this data in a read-only section.
|
||||
// #define RODATA 8
|
||||
// // This data contains no pointers.
|
||||
// #define NOPTR 16
|
||||
// // This is a wrapper function and should not count as disabling 'recover'.
|
||||
// #define WRAPPER 32
|
||||
// // This function uses its incoming context register.
|
||||
// #define NEEDCTXT 64
|
||||
// // Allocate a word of thread local storage and store the offset from the
|
||||
// // thread local base to the thread local storage in this variable.
|
||||
// #define TLSBSS 256
|
||||
// // Do not insert instructions to allocate a stack frame for this function.
|
||||
// // Only valid on functions that declare a frame size of 0.
|
||||
// // TODO(mwhudson): only implemented for ppc64x at present.
|
||||
// #define NOFRAME 512
|
||||
// // Function can call reflect.Type.Method or reflect.Type.MethodByName.
|
||||
// #define REFLECTMETHOD 1024
|
||||
// // Function is the top of the call stack. Call stack unwinders should stop
|
||||
// // at this function.
|
||||
// #define TOPFRAME 2048
|
||||
//
|
||||
const (
|
||||
NOPROF Attribute = 1 << iota
|
||||
DUPOK
|
||||
NOSPLIT
|
||||
RODATA
|
||||
NOPTR
|
||||
WRAPPER
|
||||
NEEDCTXT
|
||||
_
|
||||
TLSBSS
|
||||
NOFRAME
|
||||
REFLECTMETHOD
|
||||
TOPFRAME
|
||||
)
|
||||
|
||||
// Asm returns a representation of the attributes in assembly syntax. This may use macros from "textflags.h"; see ContainsTextFlags() to determine if this header is required.
|
||||
func (a Attribute) Asm() string {
|
||||
parts, rest := a.split()
|
||||
if len(parts) == 0 || rest != 0 {
|
||||
parts = append(parts, fmt.Sprintf("%d", rest))
|
||||
}
|
||||
return strings.Join(parts, "|")
|
||||
}
|
||||
|
||||
// ContainsTextFlags returns whether the Asm() representation requires macros in "textflags.h".
|
||||
func (a Attribute) ContainsTextFlags() bool {
|
||||
flags, _ := a.split()
|
||||
return len(flags) > 0
|
||||
}
|
||||
|
||||
// split splits a into known flags and any remaining bits.
|
||||
func (a Attribute) split() ([]string, Attribute) {
|
||||
var flags []string
|
||||
var rest Attribute
|
||||
for a != 0 {
|
||||
i := uint(bits.TrailingZeros16(uint16(a)))
|
||||
bit := Attribute(1) << i
|
||||
if flag := attrname[bit]; flag != "" {
|
||||
flags = append(flags, flag)
|
||||
} else {
|
||||
rest |= bit
|
||||
}
|
||||
a ^= bit
|
||||
}
|
||||
return flags, rest
|
||||
}
|
||||
|
||||
var attrname = map[Attribute]string{
|
||||
NOPROF: "NOPROF",
|
||||
DUPOK: "DUPOK",
|
||||
NOSPLIT: "NOSPLIT",
|
||||
RODATA: "RODATA",
|
||||
NOPTR: "NOPTR",
|
||||
WRAPPER: "WRAPPER",
|
||||
NEEDCTXT: "NEEDCTXT",
|
||||
TLSBSS: "TLSBSS",
|
||||
NOFRAME: "NOFRAME",
|
||||
REFLECTMETHOD: "REFLECTMETHOD",
|
||||
TOPFRAME: "TOPFRAME",
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package build
|
||||
|
||||
import "github.com/mmcloughlin/avo/attr"
|
||||
|
||||
// TEXT and DATA attribute values included for convenience.
|
||||
const (
|
||||
NOPROF = attr.NOPROF
|
||||
DUPOK = attr.DUPOK
|
||||
NOSPLIT = attr.NOSPLIT
|
||||
RODATA = attr.RODATA
|
||||
NOPTR = attr.NOPTR
|
||||
WRAPPER = attr.WRAPPER
|
||||
NEEDCTXT = attr.NEEDCTXT
|
||||
TLSBSS = attr.TLSBSS
|
||||
NOFRAME = attr.NOFRAME
|
||||
REFLECTMETHOD = attr.REFLECTMETHOD
|
||||
TOPFRAME = attr.TOPFRAME
|
||||
)
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
|
||||
"github.com/mmcloughlin/avo/pass"
|
||||
"github.com/mmcloughlin/avo/printer"
|
||||
)
|
||||
|
||||
// Config contains options for an avo main function.
|
||||
type Config struct {
|
||||
ErrOut io.Writer
|
||||
MaxErrors int // max errors to report; 0 means unlimited
|
||||
CPUProfile io.WriteCloser
|
||||
Passes []pass.Interface
|
||||
}
|
||||
|
||||
// Main is the standard main function for an avo program. This extracts the
|
||||
// result from the build Context (logging and exiting on error), and performs
|
||||
// configured passes.
|
||||
func Main(cfg *Config, context *Context) int {
|
||||
diag := log.New(cfg.ErrOut, "", 0)
|
||||
|
||||
if cfg.CPUProfile != nil {
|
||||
defer cfg.CPUProfile.Close()
|
||||
if err := pprof.StartCPUProfile(cfg.CPUProfile); err != nil {
|
||||
diag.Println("could not start CPU profile: ", err)
|
||||
return 1
|
||||
}
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
f, err := context.Result()
|
||||
if err != nil {
|
||||
LogError(diag, err, cfg.MaxErrors)
|
||||
return 1
|
||||
}
|
||||
|
||||
p := pass.Concat(cfg.Passes...)
|
||||
if err := p.Execute(f); err != nil {
|
||||
diag.Println(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// Flags represents CLI flags for an avo program.
|
||||
type Flags struct {
|
||||
errout *outputValue
|
||||
allerrors bool
|
||||
cpuprof *outputValue
|
||||
pkg string
|
||||
printers []*printerValue
|
||||
}
|
||||
|
||||
// NewFlags initializes avo flags for the given FlagSet.
|
||||
func NewFlags(fs *flag.FlagSet) *Flags {
|
||||
f := &Flags{}
|
||||
|
||||
f.errout = newOutputValue(os.Stderr)
|
||||
fs.Var(f.errout, "log", "diagnostics output")
|
||||
|
||||
fs.BoolVar(&f.allerrors, "e", false, "no limit on number of errors reported")
|
||||
|
||||
f.cpuprof = newOutputValue(nil)
|
||||
fs.Var(f.cpuprof, "cpuprofile", "write cpu profile to `file`")
|
||||
|
||||
fs.StringVar(&f.pkg, "pkg", "", "package name (defaults to current directory name)")
|
||||
|
||||
goasm := newPrinterValue(printer.NewGoAsm, os.Stdout)
|
||||
fs.Var(goasm, "out", "assembly output")
|
||||
f.printers = append(f.printers, goasm)
|
||||
|
||||
stubs := newPrinterValue(printer.NewStubs, nil)
|
||||
fs.Var(stubs, "stubs", "go stub file")
|
||||
f.printers = append(f.printers, stubs)
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// Config builds a configuration object based on flag values.
|
||||
func (f *Flags) Config() *Config {
|
||||
pc := printer.NewGoRunConfig()
|
||||
if f.pkg != "" {
|
||||
pc.Pkg = f.pkg
|
||||
}
|
||||
passes := []pass.Interface{pass.Compile}
|
||||
for _, pv := range f.printers {
|
||||
p := pv.Build(pc)
|
||||
if p != nil {
|
||||
passes = append(passes, p)
|
||||
}
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
ErrOut: f.errout.w,
|
||||
MaxErrors: 10,
|
||||
CPUProfile: f.cpuprof.w,
|
||||
Passes: passes,
|
||||
}
|
||||
|
||||
if f.allerrors {
|
||||
cfg.MaxErrors = 0
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
type outputValue struct {
|
||||
w io.WriteCloser
|
||||
filename string
|
||||
}
|
||||
|
||||
func newOutputValue(dflt io.WriteCloser) *outputValue {
|
||||
return &outputValue{w: dflt}
|
||||
}
|
||||
|
||||
func (o *outputValue) String() string {
|
||||
if o == nil {
|
||||
return ""
|
||||
}
|
||||
return o.filename
|
||||
}
|
||||
|
||||
func (o *outputValue) Set(s string) error {
|
||||
o.filename = s
|
||||
if s == "-" {
|
||||
o.w = nopwritecloser{os.Stdout}
|
||||
return nil
|
||||
}
|
||||
f, err := os.Create(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.w = f
|
||||
return nil
|
||||
}
|
||||
|
||||
type printerValue struct {
|
||||
*outputValue
|
||||
Builder printer.Builder
|
||||
}
|
||||
|
||||
func newPrinterValue(b printer.Builder, dflt io.WriteCloser) *printerValue {
|
||||
return &printerValue{
|
||||
outputValue: newOutputValue(dflt),
|
||||
Builder: b,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *printerValue) Build(cfg printer.Config) pass.Interface {
|
||||
if p.outputValue.w == nil {
|
||||
return nil
|
||||
}
|
||||
return &pass.Output{
|
||||
Writer: p.outputValue.w,
|
||||
Printer: p.Builder(cfg),
|
||||
}
|
||||
}
|
||||
|
||||
// nopwritecloser wraps a Writer and provides a null implementation of Close().
|
||||
type nopwritecloser struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (nopwritecloser) Close() error { return nil }
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
|
||||
"github.com/mmcloughlin/avo/attr"
|
||||
"github.com/mmcloughlin/avo/buildtags"
|
||||
"github.com/mmcloughlin/avo/gotypes"
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
// Context maintains state for incrementally building an avo File.
|
||||
type Context struct {
|
||||
pkg *packages.Package
|
||||
file *ir.File
|
||||
function *ir.Function
|
||||
global *ir.Global
|
||||
errs ErrorList
|
||||
reg.Collection
|
||||
}
|
||||
|
||||
// NewContext initializes an empty build Context.
|
||||
func NewContext() *Context {
|
||||
return &Context{
|
||||
file: ir.NewFile(),
|
||||
Collection: *reg.NewCollection(),
|
||||
}
|
||||
}
|
||||
|
||||
// Package sets the package the generated file will belong to. Required to be able to reference types in the package.
|
||||
func (c *Context) Package(path string) {
|
||||
cfg := &packages.Config{
|
||||
Mode: packages.NeedTypes | packages.NeedDeps | packages.NeedImports,
|
||||
}
|
||||
pkgs, err := packages.Load(cfg, path)
|
||||
if err != nil {
|
||||
c.adderror(err)
|
||||
return
|
||||
}
|
||||
pkg := pkgs[0]
|
||||
if len(pkg.Errors) > 0 {
|
||||
for _, err := range pkg.Errors {
|
||||
c.adderror(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
c.pkg = pkg
|
||||
}
|
||||
|
||||
// Constraints sets build constraints for the file.
|
||||
func (c *Context) Constraints(t buildtags.ConstraintsConvertable) {
|
||||
cs := t.ToConstraints()
|
||||
if err := cs.Validate(); err != nil {
|
||||
c.adderror(err)
|
||||
return
|
||||
}
|
||||
c.file.Constraints = cs
|
||||
}
|
||||
|
||||
// Constraint appends a constraint to the file's build constraints.
|
||||
func (c *Context) Constraint(t buildtags.ConstraintConvertable) {
|
||||
c.Constraints(append(c.file.Constraints, t.ToConstraint()))
|
||||
}
|
||||
|
||||
// ConstraintExpr appends a constraint to the file's build constraints. The
|
||||
// constraint to add is parsed from the given expression. The expression should
|
||||
// look the same as the content following "// +build " in regular build
|
||||
// constraint comments.
|
||||
func (c *Context) ConstraintExpr(expr string) {
|
||||
constraint, err := buildtags.ParseConstraint(expr)
|
||||
if err != nil {
|
||||
c.adderror(err)
|
||||
return
|
||||
}
|
||||
c.Constraint(constraint)
|
||||
}
|
||||
|
||||
// Function starts building a new function with the given name.
|
||||
func (c *Context) Function(name string) {
|
||||
c.function = ir.NewFunction(name)
|
||||
c.file.AddSection(c.function)
|
||||
}
|
||||
|
||||
// Doc sets documentation comment lines for the currently active function.
|
||||
func (c *Context) Doc(lines ...string) {
|
||||
c.activefunc().Doc = lines
|
||||
}
|
||||
|
||||
// Pragma adds a compiler directive to the currently active function.
|
||||
func (c *Context) Pragma(directive string, args ...string) {
|
||||
c.activefunc().AddPragma(directive, args...)
|
||||
}
|
||||
|
||||
// Attributes sets function attributes for the currently active function.
|
||||
func (c *Context) Attributes(a attr.Attribute) {
|
||||
c.activefunc().Attributes = a
|
||||
}
|
||||
|
||||
// Signature sets the signature for the currently active function.
|
||||
func (c *Context) Signature(s *gotypes.Signature) {
|
||||
c.activefunc().SetSignature(s)
|
||||
}
|
||||
|
||||
// SignatureExpr parses the signature expression and sets it as the active function's signature.
|
||||
func (c *Context) SignatureExpr(expr string) {
|
||||
s, err := gotypes.ParseSignatureInPackage(c.types(), expr)
|
||||
if err != nil {
|
||||
c.adderror(err)
|
||||
return
|
||||
}
|
||||
c.Signature(s)
|
||||
}
|
||||
|
||||
// Implement starts building a function of the given name, whose type is
|
||||
// specified by a stub in the containing package.
|
||||
func (c *Context) Implement(name string) {
|
||||
pkg := c.types()
|
||||
if pkg == nil {
|
||||
c.adderrormessage("no package specified")
|
||||
return
|
||||
}
|
||||
s, err := gotypes.LookupSignature(pkg, name)
|
||||
if err != nil {
|
||||
c.adderror(err)
|
||||
return
|
||||
}
|
||||
c.Function(name)
|
||||
c.Signature(s)
|
||||
}
|
||||
|
||||
func (c *Context) types() *types.Package {
|
||||
if c.pkg == nil {
|
||||
return nil
|
||||
}
|
||||
return c.pkg.Types
|
||||
}
|
||||
|
||||
// AllocLocal allocates size bytes in the stack of the currently active function.
|
||||
// Returns a reference to the base pointer for the newly allocated region.
|
||||
func (c *Context) AllocLocal(size int) operand.Mem {
|
||||
return c.activefunc().AllocLocal(size)
|
||||
}
|
||||
|
||||
// Instruction adds an instruction to the active function.
|
||||
func (c *Context) Instruction(i *ir.Instruction) {
|
||||
c.activefunc().AddInstruction(i)
|
||||
}
|
||||
|
||||
// Label adds a label to the active function.
|
||||
func (c *Context) Label(name string) {
|
||||
c.activefunc().AddLabel(ir.Label(name))
|
||||
}
|
||||
|
||||
// Comment adds comment lines to the active function.
|
||||
func (c *Context) Comment(lines ...string) {
|
||||
c.activefunc().AddComment(lines...)
|
||||
}
|
||||
|
||||
// Commentf adds a formtted comment line.
|
||||
func (c *Context) Commentf(format string, a ...interface{}) {
|
||||
c.Comment(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func (c *Context) activefunc() *ir.Function {
|
||||
if c.function == nil {
|
||||
c.adderrormessage("no active function")
|
||||
return ir.NewFunction("")
|
||||
}
|
||||
return c.function
|
||||
}
|
||||
|
||||
//go:generate avogen -output zinstructions.go build
|
||||
|
||||
// StaticGlobal adds a new static data section to the file and returns a pointer to it.
|
||||
func (c *Context) StaticGlobal(name string) operand.Mem {
|
||||
c.global = ir.NewStaticGlobal(name)
|
||||
c.file.AddSection(c.global)
|
||||
return c.global.Base()
|
||||
}
|
||||
|
||||
// DataAttributes sets the attributes on the current active global data section.
|
||||
func (c *Context) DataAttributes(a attr.Attribute) {
|
||||
c.activeglobal().Attributes = a
|
||||
}
|
||||
|
||||
// AddDatum adds constant v at offset to the current active global data section.
|
||||
func (c *Context) AddDatum(offset int, v operand.Constant) {
|
||||
if err := c.activeglobal().AddDatum(ir.NewDatum(offset, v)); err != nil {
|
||||
c.adderror(err)
|
||||
}
|
||||
}
|
||||
|
||||
// AppendDatum appends a constant to the current active global data section.
|
||||
func (c *Context) AppendDatum(v operand.Constant) {
|
||||
c.activeglobal().Append(v)
|
||||
}
|
||||
|
||||
func (c *Context) activeglobal() *ir.Global {
|
||||
if c.global == nil {
|
||||
c.adderrormessage("no active global")
|
||||
return ir.NewStaticGlobal("")
|
||||
}
|
||||
return c.global
|
||||
}
|
||||
|
||||
func (c *Context) adderror(err error) {
|
||||
c.errs.addext(err)
|
||||
}
|
||||
|
||||
func (c *Context) adderrormessage(msg string) {
|
||||
c.adderror(errors.New(msg))
|
||||
}
|
||||
|
||||
// Result returns the built file and any accumulated errors.
|
||||
func (c *Context) Result() (*ir.File, error) {
|
||||
return c.file, c.errs.Err()
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package build provides an assembly-like interface for incremental building of avo Files.
|
||||
package build
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/mmcloughlin/avo/internal/stack"
|
||||
"github.com/mmcloughlin/avo/src"
|
||||
)
|
||||
|
||||
// Error represents an error during building, optionally tagged with the position at which it happened.
|
||||
type Error struct {
|
||||
Position src.Position
|
||||
Err error
|
||||
}
|
||||
|
||||
// exterr constructs an Error with position derived from the first frame in the
|
||||
// call stack outside this package.
|
||||
func exterr(err error) Error {
|
||||
e := Error{Err: err}
|
||||
if f := stack.ExternalCaller(); f != nil {
|
||||
e.Position = src.FramePosition(*f).Relwd()
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
msg := e.Err.Error()
|
||||
if e.Position.IsValid() {
|
||||
return e.Position.String() + ": " + msg
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// ErrorList is a collection of errors for a source file.
|
||||
type ErrorList []Error
|
||||
|
||||
// Add appends an error to the list.
|
||||
func (e *ErrorList) Add(err Error) {
|
||||
*e = append(*e, err)
|
||||
}
|
||||
|
||||
// AddAt appends an error at position p.
|
||||
func (e *ErrorList) AddAt(p src.Position, err error) {
|
||||
e.Add(Error{p, err})
|
||||
}
|
||||
|
||||
// addext appends an error to the list, tagged with the
|
||||
func (e *ErrorList) addext(err error) {
|
||||
e.Add(exterr(err))
|
||||
}
|
||||
|
||||
// Err returns an error equivalent to this error list.
|
||||
// If the list is empty, Err returns nil.
|
||||
func (e ErrorList) Err() error {
|
||||
if len(e) == 0 {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// An ErrorList implements the error interface.
|
||||
func (e ErrorList) Error() string {
|
||||
switch len(e) {
|
||||
case 0:
|
||||
return "no errors"
|
||||
case 1:
|
||||
return e[0].Error()
|
||||
}
|
||||
return fmt.Sprintf("%s (and %d more errors)", e[0], len(e)-1)
|
||||
}
|
||||
|
||||
// LogError logs a list of errors, one error per line, if the err parameter is
|
||||
// an ErrorList. Otherwise it just logs the err string. Reports at most max
|
||||
// errors, or unlimited if max is 0.
|
||||
func LogError(l *log.Logger, err error, max int) {
|
||||
if list, ok := err.(ErrorList); ok {
|
||||
for i, e := range list {
|
||||
if max > 0 && i == max {
|
||||
l.Print("too many errors")
|
||||
return
|
||||
}
|
||||
l.Printf("%s\n", e)
|
||||
}
|
||||
} else if err != nil {
|
||||
l.Printf("%s\n", err)
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/mmcloughlin/avo/attr"
|
||||
"github.com/mmcloughlin/avo/buildtags"
|
||||
"github.com/mmcloughlin/avo/gotypes"
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
// ctx provides a global build context.
|
||||
var ctx = NewContext()
|
||||
|
||||
// TEXT starts building a new function called name, with attributes a, and sets its signature (see SignatureExpr).
|
||||
func TEXT(name string, a attr.Attribute, signature string) {
|
||||
ctx.Function(name)
|
||||
ctx.Attributes(a)
|
||||
ctx.SignatureExpr(signature)
|
||||
}
|
||||
|
||||
// GLOBL declares a new static global data section with the given attributes.
|
||||
func GLOBL(name string, a attr.Attribute) operand.Mem {
|
||||
// TODO(mbm): should this be static?
|
||||
g := ctx.StaticGlobal(name)
|
||||
ctx.DataAttributes(a)
|
||||
return g
|
||||
}
|
||||
|
||||
// DATA adds a data value to the active data section.
|
||||
func DATA(offset int, v operand.Constant) {
|
||||
ctx.AddDatum(offset, v)
|
||||
}
|
||||
|
||||
var flags = NewFlags(flag.CommandLine)
|
||||
|
||||
// Generate builds and compiles the avo file built with the global context. This
|
||||
// should be the final line of any avo program. Configuration is determined from command-line flags.
|
||||
func Generate() {
|
||||
if !flag.Parsed() {
|
||||
flag.Parse()
|
||||
}
|
||||
cfg := flags.Config()
|
||||
|
||||
status := Main(cfg, ctx)
|
||||
|
||||
// To record coverage of integration tests we wrap main() functions in a test
|
||||
// functions. In this case we need the main function to terminate, therefore we
|
||||
// only exit for failure status codes.
|
||||
if status != 0 {
|
||||
os.Exit(status)
|
||||
}
|
||||
}
|
||||
|
||||
// Package sets the package the generated file will belong to. Required to be able to reference types in the package.
|
||||
func Package(path string) { ctx.Package(path) }
|
||||
|
||||
// Constraints sets build constraints for the file.
|
||||
func Constraints(t buildtags.ConstraintsConvertable) { ctx.Constraints(t) }
|
||||
|
||||
// Constraint appends a constraint to the file's build constraints.
|
||||
func Constraint(t buildtags.ConstraintConvertable) { ctx.Constraint(t) }
|
||||
|
||||
// ConstraintExpr appends a constraint to the file's build constraints. The
|
||||
// constraint to add is parsed from the given expression. The expression should
|
||||
// look the same as the content following "// +build " in regular build
|
||||
// constraint comments.
|
||||
func ConstraintExpr(expr string) { ctx.ConstraintExpr(expr) }
|
||||
|
||||
// GP8L allocates and returns a general-purpose 8-bit register (low byte).
|
||||
func GP8L() reg.GPVirtual { return ctx.GP8L() }
|
||||
|
||||
// GP8H allocates and returns a general-purpose 8-bit register (high byte).
|
||||
func GP8H() reg.GPVirtual { return ctx.GP8H() }
|
||||
|
||||
// GP8 allocates and returns a general-purpose 8-bit register (low byte).
|
||||
func GP8() reg.GPVirtual { return ctx.GP8() }
|
||||
|
||||
// GP16 allocates and returns a general-purpose 16-bit register.
|
||||
func GP16() reg.GPVirtual { return ctx.GP16() }
|
||||
|
||||
// GP32 allocates and returns a general-purpose 32-bit register.
|
||||
func GP32() reg.GPVirtual { return ctx.GP32() }
|
||||
|
||||
// GP64 allocates and returns a general-purpose 64-bit register.
|
||||
func GP64() reg.GPVirtual { return ctx.GP64() }
|
||||
|
||||
// XMM allocates and returns a 128-bit vector register.
|
||||
func XMM() reg.VecVirtual { return ctx.XMM() }
|
||||
|
||||
// YMM allocates and returns a 256-bit vector register.
|
||||
func YMM() reg.VecVirtual { return ctx.YMM() }
|
||||
|
||||
// ZMM allocates and returns a 512-bit vector register.
|
||||
func ZMM() reg.VecVirtual { return ctx.ZMM() }
|
||||
|
||||
// Param returns a the named argument of the active function.
|
||||
func Param(name string) gotypes.Component { return ctx.Param(name) }
|
||||
|
||||
// ParamIndex returns the ith argument of the active function.
|
||||
func ParamIndex(i int) gotypes.Component { return ctx.ParamIndex(i) }
|
||||
|
||||
// Return returns a the named return value of the active function.
|
||||
func Return(name string) gotypes.Component { return ctx.Return(name) }
|
||||
|
||||
// ReturnIndex returns the ith argument of the active function.
|
||||
func ReturnIndex(i int) gotypes.Component { return ctx.ReturnIndex(i) }
|
||||
|
||||
// Load the function argument src into register dst. Returns the destination
|
||||
// register. This is syntactic sugar: it will attempt to select the right MOV
|
||||
// instruction based on the types involved.
|
||||
func Load(src gotypes.Component, dst reg.Register) reg.Register { return ctx.Load(src, dst) }
|
||||
|
||||
// Store register src into return value dst. This is syntactic sugar: it will
|
||||
// attempt to select the right MOV instruction based on the types involved.
|
||||
func Store(src reg.Register, dst gotypes.Component) { ctx.Store(src, dst) }
|
||||
|
||||
// Dereference loads a pointer and returns its element type.
|
||||
func Dereference(ptr gotypes.Component) gotypes.Component { return ctx.Dereference(ptr) }
|
||||
|
||||
// Doc sets documentation comment lines for the currently active function.
|
||||
func Doc(lines ...string) { ctx.Doc(lines...) }
|
||||
|
||||
// Pragma adds a compiler directive to the currently active function.
|
||||
func Pragma(directive string, args ...string) { ctx.Pragma(directive, args...) }
|
||||
|
||||
// Attributes sets function attributes for the currently active function.
|
||||
func Attributes(a attr.Attribute) { ctx.Attributes(a) }
|
||||
|
||||
// Implement starts building a function of the given name, whose type is
|
||||
// specified by a stub in the containing package.
|
||||
func Implement(name string) { ctx.Implement(name) }
|
||||
|
||||
// AllocLocal allocates size bytes in the stack of the currently active function.
|
||||
// Returns a reference to the base pointer for the newly allocated region.
|
||||
func AllocLocal(size int) operand.Mem { return ctx.AllocLocal(size) }
|
||||
|
||||
// Label adds a label to the active function.
|
||||
func Label(name string) { ctx.Label(name) }
|
||||
|
||||
// Comment adds comment lines to the active function.
|
||||
func Comment(lines ...string) { ctx.Comment(lines...) }
|
||||
|
||||
// Commentf adds a formtted comment line.
|
||||
func Commentf(format string, a ...interface{}) { ctx.Commentf(format, a...) }
|
||||
|
||||
// ConstData builds a static data section containing just the given constant.
|
||||
func ConstData(name string, v operand.Constant) operand.Mem { return ctx.ConstData(name, v) }
|
||||
|
||||
// Instruction adds an instruction to the active function.
|
||||
func Instruction(i *ir.Instruction) { ctx.Instruction(i) }
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"github.com/mmcloughlin/avo/attr"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
|
||||
"github.com/mmcloughlin/avo/gotypes"
|
||||
)
|
||||
|
||||
//go:generate avogen -output zmov.go mov
|
||||
|
||||
// Param returns a the named argument of the active function.
|
||||
func (c *Context) Param(name string) gotypes.Component {
|
||||
return c.activefunc().Signature.Params().Lookup(name)
|
||||
}
|
||||
|
||||
// ParamIndex returns the ith argument of the active function.
|
||||
func (c *Context) ParamIndex(i int) gotypes.Component {
|
||||
return c.activefunc().Signature.Params().At(i)
|
||||
}
|
||||
|
||||
// Return returns a the named return value of the active function.
|
||||
func (c *Context) Return(name string) gotypes.Component {
|
||||
return c.activefunc().Signature.Results().Lookup(name)
|
||||
}
|
||||
|
||||
// ReturnIndex returns the ith argument of the active function.
|
||||
func (c *Context) ReturnIndex(i int) gotypes.Component {
|
||||
return c.activefunc().Signature.Results().At(i)
|
||||
}
|
||||
|
||||
// Load the function argument src into register dst. Returns the destination
|
||||
// register. This is syntactic sugar: it will attempt to select the right MOV
|
||||
// instruction based on the types involved.
|
||||
func (c *Context) Load(src gotypes.Component, dst reg.Register) reg.Register {
|
||||
b, err := src.Resolve()
|
||||
if err != nil {
|
||||
c.adderror(err)
|
||||
return dst
|
||||
}
|
||||
c.mov(b.Addr, dst, int(gotypes.Sizes.Sizeof(b.Type)), int(dst.Size()), b.Type)
|
||||
return dst
|
||||
}
|
||||
|
||||
// Store register src into return value dst. This is syntactic sugar: it will
|
||||
// attempt to select the right MOV instruction based on the types involved.
|
||||
func (c *Context) Store(src reg.Register, dst gotypes.Component) {
|
||||
b, err := dst.Resolve()
|
||||
if err != nil {
|
||||
c.adderror(err)
|
||||
return
|
||||
}
|
||||
c.mov(src, b.Addr, int(src.Size()), int(gotypes.Sizes.Sizeof(b.Type)), b.Type)
|
||||
}
|
||||
|
||||
// Dereference loads a pointer and returns its element type.
|
||||
func (c *Context) Dereference(ptr gotypes.Component) gotypes.Component {
|
||||
r := c.GP64()
|
||||
c.Load(ptr, r)
|
||||
return ptr.Dereference(r)
|
||||
}
|
||||
|
||||
// ConstData builds a static data section containing just the given constant.
|
||||
func (c *Context) ConstData(name string, v operand.Constant) operand.Mem {
|
||||
g := c.StaticGlobal(name)
|
||||
c.DataAttributes(attr.RODATA | attr.NOPTR)
|
||||
c.AppendDatum(v)
|
||||
return g
|
||||
}
|
||||
+26315
File diff suppressed because it is too large
Load Diff
+72
@@ -0,0 +1,72 @@
|
||||
// Code generated by command: avogen -output zmov.go mov. DO NOT EDIT.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
)
|
||||
|
||||
func (c *Context) mov(a, b operand.Op, an, bn int, t *types.Basic) {
|
||||
switch {
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 1 && bn == 1:
|
||||
c.MOVB(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) == 0 && an == 1 && bn == 4:
|
||||
c.MOVBLSX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) != 0 && an == 1 && bn == 4:
|
||||
c.MOVBLZX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) == 0 && an == 1 && bn == 8:
|
||||
c.MOVBQSX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) != 0 && an == 1 && bn == 8:
|
||||
c.MOVBQZX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) == 0 && an == 1 && bn == 2:
|
||||
c.MOVBWSX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) != 0 && an == 1 && bn == 2:
|
||||
c.MOVBWZX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 4 && bn == 4:
|
||||
c.MOVL(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) == 0 && an == 4 && bn == 8:
|
||||
c.MOVLQSX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) != 0 && an == 4 && bn == 8:
|
||||
c.MOVLQZX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 16 && bn == 16:
|
||||
c.MOVOU(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 4 && bn == 16:
|
||||
c.MOVQ(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 8 && bn == 8:
|
||||
c.MOVQ(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 8 && bn == 16:
|
||||
c.MOVQ(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 16 && bn == 4:
|
||||
c.MOVQ(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 16 && bn == 8:
|
||||
c.MOVQ(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 16 && bn == 16:
|
||||
c.MOVQ(a, b)
|
||||
case (t.Info()&types.IsFloat) != 0 && an == 8 && bn == 16:
|
||||
c.MOVSD(a, b)
|
||||
case (t.Info()&types.IsFloat) != 0 && an == 16 && bn == 8:
|
||||
c.MOVSD(a, b)
|
||||
case (t.Info()&types.IsFloat) != 0 && an == 16 && bn == 16:
|
||||
c.MOVSD(a, b)
|
||||
case (t.Info()&types.IsFloat) != 0 && an == 4 && bn == 16:
|
||||
c.MOVSS(a, b)
|
||||
case (t.Info()&types.IsFloat) != 0 && an == 16 && bn == 4:
|
||||
c.MOVSS(a, b)
|
||||
case (t.Info()&types.IsFloat) != 0 && an == 16 && bn == 16:
|
||||
c.MOVSS(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 2 && bn == 2:
|
||||
c.MOVW(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) == 0 && an == 2 && bn == 4:
|
||||
c.MOVWLSX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) != 0 && an == 2 && bn == 4:
|
||||
c.MOVWLZX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) == 0 && an == 2 && bn == 8:
|
||||
c.MOVWQSX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) != 0 && an == 2 && bn == 8:
|
||||
c.MOVWQZX(a, b)
|
||||
default:
|
||||
c.adderrormessage("could not deduce mov instruction")
|
||||
}
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
// Package buildtags provides types for representing and manipulating build constraints.
|
||||
//
|
||||
// In Go, build constraints are represented as comments in source code together with file naming conventions. For example
|
||||
//
|
||||
// // +build linux,386 darwin,!cgo
|
||||
// // +build !purego
|
||||
//
|
||||
// Any terms provided in the filename can be thought of as an implicit extra
|
||||
// constraint comment line. Collectively, these are referred to as
|
||||
// ``constraints''. Each line is a ``constraint''. Within each constraint the
|
||||
// space-separated terms are ``options'', and within that the comma-separated
|
||||
// items are ``terms'' which may be negated with at most one exclaimation mark.
|
||||
//
|
||||
// These represent a boolean formulae. The constraints are evaluated as the AND
|
||||
// of constraint lines; a constraint is evaluated as the OR of its options and
|
||||
// an option is evaluated as the AND of its terms. Overall build constraints are
|
||||
// a boolean formula that is an AND of ORs of ANDs.
|
||||
//
|
||||
// This level of complexity is rarely used in Go programs. Therefore this
|
||||
// package aims to provide access to all these layers of nesting if required,
|
||||
// but make it easy to forget about for basic use cases too.
|
||||
package buildtags
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Reference: https://github.com/golang/go/blob/204a8f55dc2e0ac8d27a781dab0da609b98560da/src/go/build/doc.go#L73-L92
|
||||
//
|
||||
// // A build constraint is evaluated as the OR of space-separated options;
|
||||
// // each option evaluates as the AND of its comma-separated terms;
|
||||
// // and each term is an alphanumeric word or, preceded by !, its negation.
|
||||
// // That is, the build constraint:
|
||||
// //
|
||||
// // // +build linux,386 darwin,!cgo
|
||||
// //
|
||||
// // corresponds to the boolean formula:
|
||||
// //
|
||||
// // (linux AND 386) OR (darwin AND (NOT cgo))
|
||||
// //
|
||||
// // A file may have multiple build constraints. The overall constraint is the AND
|
||||
// // of the individual constraints. That is, the build constraints:
|
||||
// //
|
||||
// // // +build linux darwin
|
||||
// // // +build 386
|
||||
// //
|
||||
// // corresponds to the boolean formula:
|
||||
// //
|
||||
// // (linux OR darwin) AND 386
|
||||
//
|
||||
|
||||
// Interface represents a build constraint.
|
||||
type Interface interface {
|
||||
ConstraintsConvertable
|
||||
fmt.GoStringer
|
||||
Evaluate(v map[string]bool) bool
|
||||
Validate() error
|
||||
}
|
||||
|
||||
// ConstraintsConvertable can be converted to a Constraints object.
|
||||
type ConstraintsConvertable interface {
|
||||
ToConstraints() Constraints
|
||||
}
|
||||
|
||||
// ConstraintConvertable can be converted to a Constraint.
|
||||
type ConstraintConvertable interface {
|
||||
ToConstraint() Constraint
|
||||
}
|
||||
|
||||
// OptionConvertable can be converted to an Option.
|
||||
type OptionConvertable interface {
|
||||
ToOption() Option
|
||||
}
|
||||
|
||||
// Constraints represents the AND of a list of Constraint lines.
|
||||
type Constraints []Constraint
|
||||
|
||||
// And builds Constraints that will be true if all of its constraints are true.
|
||||
func And(cs ...ConstraintConvertable) Constraints {
|
||||
constraints := Constraints{}
|
||||
for _, c := range cs {
|
||||
constraints = append(constraints, c.ToConstraint())
|
||||
}
|
||||
return constraints
|
||||
}
|
||||
|
||||
// ToConstraints returns cs.
|
||||
func (cs Constraints) ToConstraints() Constraints { return cs }
|
||||
|
||||
// Validate validates the constraints set.
|
||||
func (cs Constraints) Validate() error {
|
||||
for _, c := range cs {
|
||||
if err := c.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Evaluate the boolean formula represented by cs under the given assignment of
|
||||
// tag values. This is the AND of the values of the constituent Constraints.
|
||||
func (cs Constraints) Evaluate(v map[string]bool) bool {
|
||||
r := true
|
||||
for _, c := range cs {
|
||||
r = r && c.Evaluate(v)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// GoString represents Constraints as +build comment lines.
|
||||
func (cs Constraints) GoString() string {
|
||||
s := ""
|
||||
for _, c := range cs {
|
||||
s += c.GoString()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Constraint represents the OR of a list of Options.
|
||||
type Constraint []Option
|
||||
|
||||
// Any builds a Constraint that will be true if any of its options are true.
|
||||
func Any(opts ...OptionConvertable) Constraint {
|
||||
c := Constraint{}
|
||||
for _, opt := range opts {
|
||||
c = append(c, opt.ToOption())
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// ParseConstraint parses a space-separated list of options.
|
||||
func ParseConstraint(expr string) (Constraint, error) {
|
||||
c := Constraint{}
|
||||
for _, field := range strings.Fields(expr) {
|
||||
opt, err := ParseOption(field)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
c = append(c, opt)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// ToConstraints returns the list of constraints containing just c.
|
||||
func (c Constraint) ToConstraints() Constraints { return Constraints{c} }
|
||||
|
||||
// ToConstraint returns c.
|
||||
func (c Constraint) ToConstraint() Constraint { return c }
|
||||
|
||||
// Validate validates the constraint.
|
||||
func (c Constraint) Validate() error {
|
||||
for _, o := range c {
|
||||
if err := o.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Evaluate the boolean formula represented by c under the given assignment of
|
||||
// tag values. This is the OR of the values of the constituent Options.
|
||||
func (c Constraint) Evaluate(v map[string]bool) bool {
|
||||
r := false
|
||||
for _, o := range c {
|
||||
r = r || o.Evaluate(v)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// GoString represents the Constraint as one +build comment line.
|
||||
func (c Constraint) GoString() string {
|
||||
s := "// +build"
|
||||
for _, o := range c {
|
||||
s += " " + o.GoString()
|
||||
}
|
||||
return s + "\n"
|
||||
}
|
||||
|
||||
// Option represents the AND of a list of Terms.
|
||||
type Option []Term
|
||||
|
||||
// Opt builds an Option from the list of Terms.
|
||||
func Opt(terms ...Term) Option {
|
||||
return Option(terms)
|
||||
}
|
||||
|
||||
// ParseOption parses a comma-separated list of terms.
|
||||
func ParseOption(expr string) (Option, error) {
|
||||
opt := Option{}
|
||||
for _, t := range strings.Split(expr, ",") {
|
||||
opt = append(opt, Term(t))
|
||||
}
|
||||
return opt, opt.Validate()
|
||||
}
|
||||
|
||||
// ToConstraints returns Constraints containing just this option.
|
||||
func (o Option) ToConstraints() Constraints { return o.ToConstraint().ToConstraints() }
|
||||
|
||||
// ToConstraint returns a Constraint containing just this option.
|
||||
func (o Option) ToConstraint() Constraint { return Constraint{o} }
|
||||
|
||||
// ToOption returns o.
|
||||
func (o Option) ToOption() Option { return o }
|
||||
|
||||
// Validate validates o.
|
||||
func (o Option) Validate() error {
|
||||
for _, t := range o {
|
||||
if err := t.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid term \"%s\": %s", t, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Evaluate the boolean formula represented by o under the given assignment of
|
||||
// tag values. This is the AND of the values of the constituent Terms.
|
||||
func (o Option) Evaluate(v map[string]bool) bool {
|
||||
r := true
|
||||
for _, t := range o {
|
||||
r = r && t.Evaluate(v)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// GoString represents the Option as a comma-separated list of terms.
|
||||
func (o Option) GoString() string {
|
||||
var ts []string
|
||||
for _, t := range o {
|
||||
ts = append(ts, t.GoString())
|
||||
}
|
||||
return strings.Join(ts, ",")
|
||||
}
|
||||
|
||||
// Term is an atomic term in a build constraint: an identifier or its negation.
|
||||
type Term string
|
||||
|
||||
// Not returns a term for the negation of ident.
|
||||
func Not(ident string) Term {
|
||||
return Term("!" + ident)
|
||||
}
|
||||
|
||||
// ToConstraints returns Constraints containing just this term.
|
||||
func (t Term) ToConstraints() Constraints { return t.ToOption().ToConstraints() }
|
||||
|
||||
// ToConstraint returns a Constraint containing just this term.
|
||||
func (t Term) ToConstraint() Constraint { return t.ToOption().ToConstraint() }
|
||||
|
||||
// ToOption returns an Option containing just this term.
|
||||
func (t Term) ToOption() Option { return Option{t} }
|
||||
|
||||
// IsNegated reports whether t is the negation of an identifier.
|
||||
func (t Term) IsNegated() bool { return strings.HasPrefix(string(t), "!") }
|
||||
|
||||
// Name returns the identifier for this term.
|
||||
func (t Term) Name() string {
|
||||
return strings.TrimPrefix(string(t), "!")
|
||||
}
|
||||
|
||||
// Validate the term.
|
||||
func (t Term) Validate() error {
|
||||
// Reference: https://github.com/golang/go/blob/204a8f55dc2e0ac8d27a781dab0da609b98560da/src/cmd/go/internal/imports/build.go#L110-L112
|
||||
//
|
||||
// if strings.HasPrefix(name, "!!") { // bad syntax, reject always
|
||||
// return false
|
||||
// }
|
||||
//
|
||||
if strings.HasPrefix(string(t), "!!") {
|
||||
return errors.New("at most one '!' allowed")
|
||||
}
|
||||
|
||||
if len(t.Name()) == 0 {
|
||||
return errors.New("empty tag name")
|
||||
}
|
||||
|
||||
// Reference: https://github.com/golang/go/blob/204a8f55dc2e0ac8d27a781dab0da609b98560da/src/cmd/go/internal/imports/build.go#L121-L127
|
||||
//
|
||||
// // Tags must be letters, digits, underscores or dots.
|
||||
// // Unlike in Go identifiers, all digits are fine (e.g., "386").
|
||||
// for _, c := range name {
|
||||
// if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
for _, c := range t.Name() {
|
||||
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
|
||||
return fmt.Errorf("character '%c' disallowed in tags", c)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Evaluate the term under the given set of identifier values.
|
||||
func (t Term) Evaluate(v map[string]bool) bool {
|
||||
return (t.Validate() == nil) && (v[t.Name()] == !t.IsNegated())
|
||||
}
|
||||
|
||||
// GoString returns t.
|
||||
func (t Term) GoString() string { return string(t) }
|
||||
|
||||
// SetTags builds a set where the given list of identifiers are true.
|
||||
func SetTags(idents ...string) map[string]bool {
|
||||
v := map[string]bool{}
|
||||
for _, ident := range idents {
|
||||
v[ident] = true
|
||||
}
|
||||
return v
|
||||
}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
package gotypes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strconv"
|
||||
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
)
|
||||
|
||||
// Sizes provides type sizes used by the standard Go compiler on amd64.
|
||||
var Sizes = types.SizesFor("gc", "amd64")
|
||||
|
||||
// Basic represents a primitive/basic type at a given memory address.
|
||||
type Basic struct {
|
||||
Addr operand.Mem
|
||||
Type *types.Basic
|
||||
}
|
||||
|
||||
// Component provides access to sub-components of a Go type.
|
||||
type Component interface {
|
||||
// When the component has no further sub-components, Resolve will return a
|
||||
// reference to the components type and memory address. If there was an error
|
||||
// during any previous calls to Component methods, they will be returned at
|
||||
// resolution time.
|
||||
Resolve() (*Basic, error)
|
||||
|
||||
Dereference(r reg.Register) Component // dereference a pointer
|
||||
Base() Component // base pointer of a string or slice
|
||||
Len() Component // length of a string or slice
|
||||
Cap() Component // capacity of a slice
|
||||
Real() Component // real part of a complex value
|
||||
Imag() Component // imaginary part of a complex value
|
||||
Index(int) Component // index into an array
|
||||
Field(string) Component // access a struct field
|
||||
}
|
||||
|
||||
// componenterr is an error that also provides a null implementation of the
|
||||
// Component interface. This enables us to return an error from Component
|
||||
// methods whilst also allowing method chaining to continue.
|
||||
type componenterr string
|
||||
|
||||
func errorf(format string, args ...interface{}) Component {
|
||||
return componenterr(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (c componenterr) Error() string { return string(c) }
|
||||
func (c componenterr) Resolve() (*Basic, error) { return nil, c }
|
||||
func (c componenterr) Dereference(r reg.Register) Component { return c }
|
||||
func (c componenterr) Base() Component { return c }
|
||||
func (c componenterr) Len() Component { return c }
|
||||
func (c componenterr) Cap() Component { return c }
|
||||
func (c componenterr) Real() Component { return c }
|
||||
func (c componenterr) Imag() Component { return c }
|
||||
func (c componenterr) Index(int) Component { return c }
|
||||
func (c componenterr) Field(string) Component { return c }
|
||||
|
||||
type component struct {
|
||||
typ types.Type
|
||||
addr operand.Mem
|
||||
}
|
||||
|
||||
// NewComponent builds a component for the named type at the given address.
|
||||
func NewComponent(t types.Type, addr operand.Mem) Component {
|
||||
return &component{
|
||||
typ: t,
|
||||
addr: addr,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *component) Resolve() (*Basic, error) {
|
||||
b := toprimitive(c.typ)
|
||||
if b == nil {
|
||||
return nil, errors.New("component is not primitive")
|
||||
}
|
||||
return &Basic{
|
||||
Addr: c.addr,
|
||||
Type: b,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *component) Dereference(r reg.Register) Component {
|
||||
p, ok := c.typ.Underlying().(*types.Pointer)
|
||||
if !ok {
|
||||
return errorf("not pointer type")
|
||||
}
|
||||
return NewComponent(p.Elem(), operand.Mem{Base: r})
|
||||
}
|
||||
|
||||
// Reference: https://github.com/golang/go/blob/50bd1c4d4eb4fac8ddeb5f063c099daccfb71b26/src/reflect/value.go#L1800-L1804
|
||||
//
|
||||
// type SliceHeader struct {
|
||||
// Data uintptr
|
||||
// Len int
|
||||
// Cap int
|
||||
// }
|
||||
//
|
||||
var slicehdroffsets = Sizes.Offsetsof([]*types.Var{
|
||||
types.NewField(token.NoPos, nil, "Data", types.Typ[types.Uintptr], false),
|
||||
types.NewField(token.NoPos, nil, "Len", types.Typ[types.Int], false),
|
||||
types.NewField(token.NoPos, nil, "Cap", types.Typ[types.Int], false),
|
||||
})
|
||||
|
||||
func (c *component) Base() Component {
|
||||
if !isslice(c.typ) && !isstring(c.typ) {
|
||||
return errorf("only slices and strings have base pointers")
|
||||
}
|
||||
return c.sub("_base", int(slicehdroffsets[0]), types.Typ[types.Uintptr])
|
||||
}
|
||||
|
||||
func (c *component) Len() Component {
|
||||
if !isslice(c.typ) && !isstring(c.typ) {
|
||||
return errorf("only slices and strings have length fields")
|
||||
}
|
||||
return c.sub("_len", int(slicehdroffsets[1]), types.Typ[types.Int])
|
||||
}
|
||||
|
||||
func (c *component) Cap() Component {
|
||||
if !isslice(c.typ) {
|
||||
return errorf("only slices have capacity fields")
|
||||
}
|
||||
return c.sub("_cap", int(slicehdroffsets[2]), types.Typ[types.Int])
|
||||
}
|
||||
|
||||
func (c *component) Real() Component {
|
||||
if !iscomplex(c.typ) {
|
||||
return errorf("only complex types have real values")
|
||||
}
|
||||
f := complextofloat(c.typ)
|
||||
return c.sub("_real", 0, f)
|
||||
}
|
||||
|
||||
func (c *component) Imag() Component {
|
||||
if !iscomplex(c.typ) {
|
||||
return errorf("only complex types have imaginary values")
|
||||
}
|
||||
f := complextofloat(c.typ)
|
||||
return c.sub("_imag", int(Sizes.Sizeof(f)), f)
|
||||
}
|
||||
|
||||
func (c *component) Index(i int) Component {
|
||||
a, ok := c.typ.Underlying().(*types.Array)
|
||||
if !ok {
|
||||
return errorf("not array type")
|
||||
}
|
||||
if int64(i) >= a.Len() {
|
||||
return errorf("array index out of bounds")
|
||||
}
|
||||
// Reference: https://github.com/golang/tools/blob/bcd4e47d02889ebbc25c9f4bf3d27e4124b0bf9d/go/analysis/passes/asmdecl/asmdecl.go#L482-L494
|
||||
//
|
||||
// case asmArray:
|
||||
// tu := t.Underlying().(*types.Array)
|
||||
// elem := tu.Elem()
|
||||
// // Calculate offset of each element array.
|
||||
// fields := []*types.Var{
|
||||
// types.NewVar(token.NoPos, nil, "fake0", elem),
|
||||
// types.NewVar(token.NoPos, nil, "fake1", elem),
|
||||
// }
|
||||
// offsets := arch.sizes.Offsetsof(fields)
|
||||
// elemoff := int(offsets[1])
|
||||
// for i := 0; i < int(tu.Len()); i++ {
|
||||
// cc = appendComponentsRecursive(arch, elem, cc, suffix+"_"+strconv.Itoa(i), i*elemoff)
|
||||
// }
|
||||
//
|
||||
elem := a.Elem()
|
||||
elemsize := int(Sizes.Sizeof(types.NewArray(elem, 2)) - Sizes.Sizeof(types.NewArray(elem, 1)))
|
||||
return c.sub("_"+strconv.Itoa(i), i*elemsize, elem)
|
||||
}
|
||||
|
||||
func (c *component) Field(n string) Component {
|
||||
s, ok := c.typ.Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return errorf("not struct type")
|
||||
}
|
||||
// Reference: https://github.com/golang/tools/blob/13ba8ad772dfbf0f451b5dd0679e9c5605afc05d/go/analysis/passes/asmdecl/asmdecl.go#L471-L480
|
||||
//
|
||||
// case asmStruct:
|
||||
// tu := t.Underlying().(*types.Struct)
|
||||
// fields := make([]*types.Var, tu.NumFields())
|
||||
// for i := 0; i < tu.NumFields(); i++ {
|
||||
// fields[i] = tu.Field(i)
|
||||
// }
|
||||
// offsets := arch.sizes.Offsetsof(fields)
|
||||
// for i, f := range fields {
|
||||
// cc = appendComponentsRecursive(arch, f.Type(), cc, suffix+"_"+f.Name(), off+int(offsets[i]))
|
||||
// }
|
||||
//
|
||||
fields := make([]*types.Var, s.NumFields())
|
||||
for i := 0; i < s.NumFields(); i++ {
|
||||
fields[i] = s.Field(i)
|
||||
}
|
||||
offsets := Sizes.Offsetsof(fields)
|
||||
for i, f := range fields {
|
||||
if f.Name() == n {
|
||||
return c.sub("_"+n, int(offsets[i]), f.Type())
|
||||
}
|
||||
}
|
||||
return errorf("struct does not have field '%s'", n)
|
||||
}
|
||||
|
||||
func (c *component) sub(suffix string, offset int, t types.Type) *component {
|
||||
s := *c
|
||||
if s.addr.Symbol.Name != "" {
|
||||
s.addr.Symbol.Name += suffix
|
||||
}
|
||||
s.addr = s.addr.Offset(offset)
|
||||
s.typ = t
|
||||
return &s
|
||||
}
|
||||
|
||||
func isslice(t types.Type) bool {
|
||||
_, ok := t.Underlying().(*types.Slice)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isstring(t types.Type) bool {
|
||||
b, ok := t.Underlying().(*types.Basic)
|
||||
return ok && b.Kind() == types.String
|
||||
}
|
||||
|
||||
func iscomplex(t types.Type) bool {
|
||||
b, ok := t.Underlying().(*types.Basic)
|
||||
return ok && (b.Info()&types.IsComplex) != 0
|
||||
}
|
||||
|
||||
func complextofloat(t types.Type) types.Type {
|
||||
switch Sizes.Sizeof(t) {
|
||||
case 16:
|
||||
return types.Typ[types.Float64]
|
||||
case 8:
|
||||
return types.Typ[types.Float32]
|
||||
}
|
||||
panic("bad")
|
||||
}
|
||||
|
||||
// toprimitive determines whether t is primitive (cannot be reduced into
|
||||
// components). If it is, it returns the basic type for t, otherwise returns
|
||||
// nil.
|
||||
func toprimitive(t types.Type) *types.Basic {
|
||||
switch b := t.(type) {
|
||||
case *types.Basic:
|
||||
if (b.Info() & (types.IsString | types.IsComplex)) == 0 {
|
||||
return b
|
||||
}
|
||||
case *types.Pointer:
|
||||
return types.Typ[types.Uintptr]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package gotypes provides helpers for interacting with Go types within avo functions.
|
||||
package gotypes
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
package gotypes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strconv"
|
||||
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
)
|
||||
|
||||
// Signature represents a Go function signature.
|
||||
type Signature struct {
|
||||
pkg *types.Package
|
||||
sig *types.Signature
|
||||
params *Tuple
|
||||
results *Tuple
|
||||
}
|
||||
|
||||
// NewSignature constructs a Signature.
|
||||
func NewSignature(pkg *types.Package, sig *types.Signature) *Signature {
|
||||
s := &Signature{
|
||||
pkg: pkg,
|
||||
sig: sig,
|
||||
}
|
||||
s.init()
|
||||
return s
|
||||
}
|
||||
|
||||
// NewSignatureVoid builds the void signature "func()".
|
||||
func NewSignatureVoid() *Signature {
|
||||
return NewSignature(nil, types.NewSignature(nil, nil, nil, false))
|
||||
}
|
||||
|
||||
// LookupSignature returns the signature of the named function in the provided package.
|
||||
func LookupSignature(pkg *types.Package, name string) (*Signature, error) {
|
||||
scope := pkg.Scope()
|
||||
obj := scope.Lookup(name)
|
||||
if obj == nil {
|
||||
return nil, fmt.Errorf("could not find function \"%s\"", name)
|
||||
}
|
||||
s, ok := obj.Type().(*types.Signature)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("object \"%s\" does not have signature type", name)
|
||||
}
|
||||
return NewSignature(pkg, s), nil
|
||||
}
|
||||
|
||||
// ParseSignature builds a Signature by parsing a Go function type expression.
|
||||
// The function type must reference builtin types only; see
|
||||
// ParseSignatureInPackage if custom types are required.
|
||||
func ParseSignature(expr string) (*Signature, error) {
|
||||
return ParseSignatureInPackage(nil, expr)
|
||||
}
|
||||
|
||||
// ParseSignatureInPackage builds a Signature by parsing a Go function type
|
||||
// expression. The expression may reference types in the provided package.
|
||||
func ParseSignatureInPackage(pkg *types.Package, expr string) (*Signature, error) {
|
||||
tv, err := types.Eval(token.NewFileSet(), pkg, token.NoPos, expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tv.Value != nil {
|
||||
return nil, errors.New("signature expression should have nil value")
|
||||
}
|
||||
s, ok := tv.Type.(*types.Signature)
|
||||
if !ok {
|
||||
return nil, errors.New("provided type is not a function signature")
|
||||
}
|
||||
return NewSignature(pkg, s), nil
|
||||
}
|
||||
|
||||
// Params returns the function signature argument types.
|
||||
func (s *Signature) Params() *Tuple { return s.params }
|
||||
|
||||
// Results returns the function return types.
|
||||
func (s *Signature) Results() *Tuple { return s.results }
|
||||
|
||||
// Bytes returns the total size of the function arguments and return values.
|
||||
func (s *Signature) Bytes() int { return s.Params().Bytes() + s.Results().Bytes() }
|
||||
|
||||
// String writes Signature as a string. This does not include the "func" keyword.
|
||||
func (s *Signature) String() string {
|
||||
var buf bytes.Buffer
|
||||
types.WriteSignature(&buf, s.sig, func(pkg *types.Package) string {
|
||||
if pkg == s.pkg {
|
||||
return ""
|
||||
}
|
||||
return pkg.Name()
|
||||
})
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (s *Signature) init() {
|
||||
p := s.sig.Params()
|
||||
r := s.sig.Results()
|
||||
|
||||
// Compute parameter offsets.
|
||||
vs := tuplevars(p)
|
||||
vs = append(vs, types.NewParam(token.NoPos, nil, "sentinel", types.Typ[types.Uint64]))
|
||||
paramsoffsets := Sizes.Offsetsof(vs)
|
||||
paramssize := paramsoffsets[p.Len()]
|
||||
s.params = newTuple(p, paramsoffsets, paramssize, "arg")
|
||||
|
||||
// Result offsets.
|
||||
vs = tuplevars(r)
|
||||
resultsoffsets := Sizes.Offsetsof(vs)
|
||||
var resultssize int64
|
||||
if n := len(vs); n > 0 {
|
||||
resultssize = resultsoffsets[n-1] + Sizes.Sizeof(vs[n-1].Type())
|
||||
}
|
||||
for i := range resultsoffsets {
|
||||
resultsoffsets[i] += paramssize
|
||||
}
|
||||
s.results = newTuple(r, resultsoffsets, resultssize, "ret")
|
||||
}
|
||||
|
||||
// Tuple represents a tuple of variables, such as function arguments or results.
|
||||
type Tuple struct {
|
||||
components []Component
|
||||
byname map[string]Component
|
||||
size int
|
||||
}
|
||||
|
||||
func newTuple(t *types.Tuple, offsets []int64, size int64, defaultprefix string) *Tuple {
|
||||
tuple := &Tuple{
|
||||
byname: map[string]Component{},
|
||||
size: int(size),
|
||||
}
|
||||
for i := 0; i < t.Len(); i++ {
|
||||
v := t.At(i)
|
||||
name := v.Name()
|
||||
if name == "" {
|
||||
name = defaultprefix
|
||||
if i > 0 {
|
||||
name += strconv.Itoa(i)
|
||||
}
|
||||
}
|
||||
addr := operand.NewParamAddr(name, int(offsets[i]))
|
||||
c := NewComponent(v.Type(), addr)
|
||||
tuple.components = append(tuple.components, c)
|
||||
if v.Name() != "" {
|
||||
tuple.byname[v.Name()] = c
|
||||
}
|
||||
}
|
||||
return tuple
|
||||
}
|
||||
|
||||
// Lookup returns the variable with the given name.
|
||||
func (t *Tuple) Lookup(name string) Component {
|
||||
e := t.byname[name]
|
||||
if e == nil {
|
||||
return errorf("unknown variable \"%s\"", name)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// At returns the variable at index i.
|
||||
func (t *Tuple) At(i int) Component {
|
||||
if i >= len(t.components) {
|
||||
return errorf("index out of range")
|
||||
}
|
||||
return t.components[i]
|
||||
}
|
||||
|
||||
// Bytes returns the size of the Tuple. This may include additional padding.
|
||||
func (t *Tuple) Bytes() int { return t.size }
|
||||
|
||||
func tuplevars(t *types.Tuple) []*types.Var {
|
||||
vs := make([]*types.Var, t.Len())
|
||||
for i := 0; i < t.Len(); i++ {
|
||||
vs[i] = t.At(i)
|
||||
}
|
||||
return vs
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// Package prnt provides common functionality for code generators.
|
||||
package prnt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Generator provides convenience methods for code generators. In particular it
|
||||
// provides fmt-like methods which print to an internal buffer. It also allows
|
||||
// any errors to be stored so they can be checked at the end, rather than having
|
||||
// error checks obscuring the code generation.
|
||||
type Generator struct {
|
||||
buf bytes.Buffer
|
||||
err error
|
||||
}
|
||||
|
||||
// Raw provides direct access to the underlying output stream.
|
||||
func (g *Generator) Raw() io.Writer {
|
||||
return &g.buf
|
||||
}
|
||||
|
||||
// Printf prints to the internal buffer.
|
||||
func (g *Generator) Printf(format string, args ...interface{}) {
|
||||
if g.err != nil {
|
||||
return
|
||||
}
|
||||
_, err := fmt.Fprintf(&g.buf, format, args...)
|
||||
g.AddError(err)
|
||||
}
|
||||
|
||||
// NL prints a new line.
|
||||
func (g *Generator) NL() {
|
||||
g.Printf("\n")
|
||||
}
|
||||
|
||||
// Comment writes comment lines prefixed with "// ".
|
||||
func (g *Generator) Comment(lines ...string) {
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace("// " + line)
|
||||
g.Printf("%s\n", line)
|
||||
}
|
||||
}
|
||||
|
||||
// AddError records an error in code generation. The first non-nil error will
|
||||
// prevent printing operations from writing anything else, and the error will be
|
||||
// returned from Result().
|
||||
func (g *Generator) AddError(err error) {
|
||||
if err != nil && g.err == nil {
|
||||
g.err = err
|
||||
}
|
||||
}
|
||||
|
||||
// Result returns the printed bytes. If any error was recorded with AddError
|
||||
// during code generation, the first such error will be returned here.
|
||||
func (g *Generator) Result() ([]byte, error) {
|
||||
return g.buf.Bytes(), g.err
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
// Package stack provides helpers for querying the callstack.
|
||||
package stack
|
||||
|
||||
import (
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frames returns at most max callstack Frames, starting with its caller and
|
||||
// skipping skip Frames.
|
||||
func Frames(skip, max int) []runtime.Frame {
|
||||
pc := make([]uintptr, max)
|
||||
n := runtime.Callers(skip+2, pc)
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
pc = pc[:n]
|
||||
frames := runtime.CallersFrames(pc)
|
||||
var fs []runtime.Frame
|
||||
for {
|
||||
f, more := frames.Next()
|
||||
fs = append(fs, f)
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
// Match returns the first stack frame for which the predicate function returns
|
||||
// true. Returns nil if no match is found. Starts matching after skip frames,
|
||||
// starting with its caller.
|
||||
func Match(skip int, predicate func(runtime.Frame) bool) *runtime.Frame {
|
||||
i, n := skip+1, 16
|
||||
for {
|
||||
fs := Frames(i, n)
|
||||
for j, f := range fs {
|
||||
if predicate(f) {
|
||||
return &fs[j]
|
||||
}
|
||||
}
|
||||
if len(fs) < n {
|
||||
break
|
||||
}
|
||||
i += n
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Main returns the main() function Frame.
|
||||
func Main() *runtime.Frame {
|
||||
return Match(1, func(f runtime.Frame) bool {
|
||||
return f.Function == "main.main"
|
||||
})
|
||||
}
|
||||
|
||||
// ExternalCaller returns the first frame outside the callers package.
|
||||
func ExternalCaller() *runtime.Frame {
|
||||
var first *runtime.Frame
|
||||
return Match(1, func(f runtime.Frame) bool {
|
||||
if first == nil {
|
||||
first = &f
|
||||
}
|
||||
return pkg(first.Function) != pkg(f.Function)
|
||||
})
|
||||
}
|
||||
|
||||
func pkg(ident string) string {
|
||||
dir, name := path.Split(ident)
|
||||
parts := strings.Split(name, ".")
|
||||
return dir + parts[0]
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package ir provides the intermediate representation of avo programs.
|
||||
package ir
|
||||
+355
@@ -0,0 +1,355 @@
|
||||
package ir
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/mmcloughlin/avo/attr"
|
||||
"github.com/mmcloughlin/avo/buildtags"
|
||||
"github.com/mmcloughlin/avo/gotypes"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
// Node is a part of a Function.
|
||||
type Node interface {
|
||||
node()
|
||||
}
|
||||
|
||||
// Label within a function.
|
||||
type Label string
|
||||
|
||||
func (l Label) node() {}
|
||||
|
||||
// Comment represents a multi-line comment.
|
||||
type Comment struct {
|
||||
Lines []string
|
||||
}
|
||||
|
||||
func (c *Comment) node() {}
|
||||
|
||||
// NewComment builds a Comment consisting of the provided lines.
|
||||
func NewComment(lines ...string) *Comment {
|
||||
return &Comment{
|
||||
Lines: lines,
|
||||
}
|
||||
}
|
||||
|
||||
// Instruction is a single instruction in a function.
|
||||
type Instruction struct {
|
||||
Opcode string
|
||||
Operands []operand.Op
|
||||
|
||||
Inputs []operand.Op
|
||||
Outputs []operand.Op
|
||||
|
||||
IsTerminal bool
|
||||
IsBranch bool
|
||||
IsConditional bool
|
||||
CancellingInputs bool
|
||||
|
||||
// ISA is the list of required instruction set extensions.
|
||||
ISA []string
|
||||
|
||||
// CFG.
|
||||
Pred []*Instruction
|
||||
Succ []*Instruction
|
||||
|
||||
// LiveIn/LiveOut are sets of live register IDs pre/post execution.
|
||||
LiveIn reg.MaskSet
|
||||
LiveOut reg.MaskSet
|
||||
}
|
||||
|
||||
func (i *Instruction) node() {}
|
||||
|
||||
// IsUnconditionalBranch reports whether i is an unconditional branch.
|
||||
func (i Instruction) IsUnconditionalBranch() bool {
|
||||
return i.IsBranch && !i.IsConditional
|
||||
}
|
||||
|
||||
// TargetLabel returns the label referenced by this instruction. Returns nil if
|
||||
// no label is referenced.
|
||||
func (i Instruction) TargetLabel() *Label {
|
||||
if !i.IsBranch {
|
||||
return nil
|
||||
}
|
||||
if len(i.Operands) == 0 {
|
||||
return nil
|
||||
}
|
||||
if ref, ok := i.Operands[0].(operand.LabelRef); ok {
|
||||
lbl := Label(ref)
|
||||
return &lbl
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Registers returns all registers involved in the instruction.
|
||||
func (i Instruction) Registers() []reg.Register {
|
||||
var rs []reg.Register
|
||||
for _, op := range i.Operands {
|
||||
rs = append(rs, operand.Registers(op)...)
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
// InputRegisters returns all registers read by this instruction.
|
||||
func (i Instruction) InputRegisters() []reg.Register {
|
||||
var rs []reg.Register
|
||||
for _, op := range i.Inputs {
|
||||
rs = append(rs, operand.Registers(op)...)
|
||||
}
|
||||
if i.CancellingInputs && rs[0] == rs[1] {
|
||||
rs = []reg.Register{}
|
||||
}
|
||||
for _, op := range i.Outputs {
|
||||
if operand.IsMem(op) {
|
||||
rs = append(rs, operand.Registers(op)...)
|
||||
}
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
// OutputRegisters returns all registers written by this instruction.
|
||||
func (i Instruction) OutputRegisters() []reg.Register {
|
||||
var rs []reg.Register
|
||||
for _, op := range i.Outputs {
|
||||
if r, ok := op.(reg.Register); ok {
|
||||
rs = append(rs, r)
|
||||
}
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
// Section is a part of a file.
|
||||
type Section interface {
|
||||
section()
|
||||
}
|
||||
|
||||
// File represents an assembly file.
|
||||
type File struct {
|
||||
Constraints buildtags.Constraints
|
||||
Includes []string
|
||||
Sections []Section
|
||||
}
|
||||
|
||||
// NewFile initializes an empty file.
|
||||
func NewFile() *File {
|
||||
return &File{}
|
||||
}
|
||||
|
||||
// AddSection appends a Section to the file.
|
||||
func (f *File) AddSection(s Section) {
|
||||
f.Sections = append(f.Sections, s)
|
||||
}
|
||||
|
||||
// Functions returns all functions in the file.
|
||||
func (f *File) Functions() []*Function {
|
||||
var fns []*Function
|
||||
for _, s := range f.Sections {
|
||||
if fn, ok := s.(*Function); ok {
|
||||
fns = append(fns, fn)
|
||||
}
|
||||
}
|
||||
return fns
|
||||
}
|
||||
|
||||
// Pragma represents a function compiler directive.
|
||||
type Pragma struct {
|
||||
Directive string
|
||||
Arguments []string
|
||||
}
|
||||
|
||||
// Function represents an assembly function.
|
||||
type Function struct {
|
||||
Name string
|
||||
Attributes attr.Attribute
|
||||
Pragmas []Pragma
|
||||
Doc []string
|
||||
Signature *gotypes.Signature
|
||||
LocalSize int
|
||||
|
||||
Nodes []Node
|
||||
|
||||
// LabelTarget maps from label name to the following instruction.
|
||||
LabelTarget map[Label]*Instruction
|
||||
|
||||
// Register allocation.
|
||||
Allocation reg.Allocation
|
||||
|
||||
// ISA is the list of required instruction set extensions.
|
||||
ISA []string
|
||||
}
|
||||
|
||||
func (f *Function) section() {}
|
||||
|
||||
// NewFunction builds an empty function of the given name.
|
||||
func NewFunction(name string) *Function {
|
||||
return &Function{
|
||||
Name: name,
|
||||
Signature: gotypes.NewSignatureVoid(),
|
||||
}
|
||||
}
|
||||
|
||||
// AddPragma adds a pragma to this function.
|
||||
func (f *Function) AddPragma(directive string, args ...string) {
|
||||
f.Pragmas = append(f.Pragmas, Pragma{
|
||||
Directive: directive,
|
||||
Arguments: args,
|
||||
})
|
||||
}
|
||||
|
||||
// SetSignature sets the function signature.
|
||||
func (f *Function) SetSignature(s *gotypes.Signature) {
|
||||
f.Signature = s
|
||||
}
|
||||
|
||||
// AllocLocal allocates size bytes in this function's stack.
|
||||
// Returns a reference to the base pointer for the newly allocated region.
|
||||
func (f *Function) AllocLocal(size int) operand.Mem {
|
||||
ptr := operand.NewStackAddr(f.LocalSize)
|
||||
f.LocalSize += size
|
||||
return ptr
|
||||
}
|
||||
|
||||
// AddInstruction appends an instruction to f.
|
||||
func (f *Function) AddInstruction(i *Instruction) {
|
||||
f.AddNode(i)
|
||||
}
|
||||
|
||||
// AddLabel appends a label to f.
|
||||
func (f *Function) AddLabel(l Label) {
|
||||
f.AddNode(l)
|
||||
}
|
||||
|
||||
// AddComment adds comment lines to f.
|
||||
func (f *Function) AddComment(lines ...string) {
|
||||
f.AddNode(NewComment(lines...))
|
||||
}
|
||||
|
||||
// AddNode appends a Node to f.
|
||||
func (f *Function) AddNode(n Node) {
|
||||
f.Nodes = append(f.Nodes, n)
|
||||
}
|
||||
|
||||
// Instructions returns just the list of instruction nodes.
|
||||
func (f *Function) Instructions() []*Instruction {
|
||||
var is []*Instruction
|
||||
for _, n := range f.Nodes {
|
||||
i, ok := n.(*Instruction)
|
||||
if ok {
|
||||
is = append(is, i)
|
||||
}
|
||||
}
|
||||
return is
|
||||
}
|
||||
|
||||
// Labels returns just the list of label nodes.
|
||||
func (f *Function) Labels() []Label {
|
||||
var lbls []Label
|
||||
for _, n := range f.Nodes {
|
||||
lbl, ok := n.(Label)
|
||||
if ok {
|
||||
lbls = append(lbls, lbl)
|
||||
}
|
||||
}
|
||||
return lbls
|
||||
}
|
||||
|
||||
// Stub returns the Go function declaration.
|
||||
func (f *Function) Stub() string {
|
||||
return "func " + f.Name + f.Signature.String()
|
||||
}
|
||||
|
||||
// FrameBytes returns the size of the stack frame in bytes.
|
||||
func (f *Function) FrameBytes() int {
|
||||
return f.LocalSize
|
||||
}
|
||||
|
||||
// ArgumentBytes returns the size of the arguments in bytes.
|
||||
func (f *Function) ArgumentBytes() int {
|
||||
return f.Signature.Bytes()
|
||||
}
|
||||
|
||||
// Datum represents a data element at a particular offset of a data section.
|
||||
type Datum struct {
|
||||
Offset int
|
||||
Value operand.Constant
|
||||
}
|
||||
|
||||
// NewDatum builds a Datum from the given constant.
|
||||
func NewDatum(offset int, v operand.Constant) Datum {
|
||||
return Datum{
|
||||
Offset: offset,
|
||||
Value: v,
|
||||
}
|
||||
}
|
||||
|
||||
// Interval returns the range of bytes this datum will occupy within its section.
|
||||
func (d Datum) Interval() (int, int) {
|
||||
return d.Offset, d.Offset + d.Value.Bytes()
|
||||
}
|
||||
|
||||
// Overlaps returns true
|
||||
func (d Datum) Overlaps(other Datum) bool {
|
||||
s, e := d.Interval()
|
||||
so, eo := other.Interval()
|
||||
return !(eo <= s || e <= so)
|
||||
}
|
||||
|
||||
// Global represents a DATA section.
|
||||
type Global struct {
|
||||
Symbol operand.Symbol
|
||||
Attributes attr.Attribute
|
||||
Data []Datum
|
||||
Size int
|
||||
}
|
||||
|
||||
// NewGlobal constructs an empty DATA section.
|
||||
func NewGlobal(sym operand.Symbol) *Global {
|
||||
return &Global{
|
||||
Symbol: sym,
|
||||
}
|
||||
}
|
||||
|
||||
// NewStaticGlobal is a convenience for building a static DATA section.
|
||||
func NewStaticGlobal(name string) *Global {
|
||||
return NewGlobal(operand.NewStaticSymbol(name))
|
||||
}
|
||||
|
||||
func (g *Global) section() {}
|
||||
|
||||
// Base returns a pointer to the start of the data section.
|
||||
func (g *Global) Base() operand.Mem {
|
||||
return operand.NewDataAddr(g.Symbol, 0)
|
||||
}
|
||||
|
||||
// Grow ensures that the data section has at least the given size.
|
||||
func (g *Global) Grow(size int) {
|
||||
if g.Size < size {
|
||||
g.Size = size
|
||||
}
|
||||
}
|
||||
|
||||
// AddDatum adds d to this data section, growing it if necessary. Errors if the datum overlaps with existing data.
|
||||
func (g *Global) AddDatum(d Datum) error {
|
||||
for _, other := range g.Data {
|
||||
if d.Overlaps(other) {
|
||||
return errors.New("overlaps existing datum")
|
||||
}
|
||||
}
|
||||
g.add(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Append the constant to the end of the data section.
|
||||
func (g *Global) Append(v operand.Constant) {
|
||||
g.add(Datum{
|
||||
Offset: g.Size,
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Global) add(d Datum) {
|
||||
_, end := d.Interval()
|
||||
g.Grow(end)
|
||||
g.Data = append(g.Data, d)
|
||||
}
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
package operand
|
||||
|
||||
import "github.com/mmcloughlin/avo/reg"
|
||||
|
||||
// Pure type assertion checks:
|
||||
|
||||
// IsRegister returns whether op has type reg.Register.
|
||||
func IsRegister(op Op) bool { _, ok := op.(reg.Register); return ok }
|
||||
|
||||
// IsMem returns whether op has type Mem.
|
||||
func IsMem(op Op) bool { _, ok := op.(Mem); return ok }
|
||||
|
||||
// IsRel returns whether op has type Rel.
|
||||
func IsRel(op Op) bool { _, ok := op.(Rel); return ok }
|
||||
|
||||
// Checks corresponding to specific operand types in the Intel Manual:
|
||||
|
||||
// Is1 returns true if op is the immediate constant 1.
|
||||
func Is1(op Op) bool {
|
||||
i, ok := op.(U8)
|
||||
return ok && i == 1
|
||||
}
|
||||
|
||||
// Is3 returns true if op is the immediate constant 3.
|
||||
func Is3(op Op) bool {
|
||||
i, ok := op.(U8)
|
||||
return ok && i == 3
|
||||
}
|
||||
|
||||
// IsIMM2U returns true if op is a 2-bit unsigned immediate (less than 4).
|
||||
func IsIMM2U(op Op) bool {
|
||||
i, ok := op.(U8)
|
||||
return ok && i < 4
|
||||
}
|
||||
|
||||
// IsIMM8 returns true is op is an 8-bit immediate.
|
||||
func IsIMM8(op Op) bool {
|
||||
_, ok := op.(U8)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsIMM16 returns true is op is a 16-bit immediate.
|
||||
func IsIMM16(op Op) bool {
|
||||
_, ok := op.(U16)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsIMM32 returns true is op is a 32-bit immediate.
|
||||
func IsIMM32(op Op) bool {
|
||||
_, ok := op.(U32)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsIMM64 returns true is op is a 64-bit immediate.
|
||||
func IsIMM64(op Op) bool {
|
||||
_, ok := op.(U64)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsAL returns true if op is the AL register.
|
||||
func IsAL(op Op) bool {
|
||||
return op == reg.AL
|
||||
}
|
||||
|
||||
// IsCL returns true if op is the CL register.
|
||||
func IsCL(op Op) bool {
|
||||
return op == reg.CL
|
||||
}
|
||||
|
||||
// IsAX returns true if op is the 16-bit AX register.
|
||||
func IsAX(op Op) bool {
|
||||
return op == reg.AX
|
||||
}
|
||||
|
||||
// IsEAX returns true if op is the 32-bit EAX register.
|
||||
func IsEAX(op Op) bool {
|
||||
return op == reg.EAX
|
||||
}
|
||||
|
||||
// IsRAX returns true if op is the 64-bit RAX register.
|
||||
func IsRAX(op Op) bool {
|
||||
return op == reg.RAX
|
||||
}
|
||||
|
||||
// IsR8 returns true if op is an 8-bit general-purpose register.
|
||||
func IsR8(op Op) bool {
|
||||
return IsGP(op, 1)
|
||||
}
|
||||
|
||||
// IsR16 returns true if op is a 16-bit general-purpose register.
|
||||
func IsR16(op Op) bool {
|
||||
return IsGP(op, 2)
|
||||
}
|
||||
|
||||
// IsR32 returns true if op is a 32-bit general-purpose register.
|
||||
func IsR32(op Op) bool {
|
||||
return IsGP(op, 4)
|
||||
}
|
||||
|
||||
// IsR64 returns true if op is a 64-bit general-purpose register.
|
||||
func IsR64(op Op) bool {
|
||||
return IsGP(op, 8)
|
||||
}
|
||||
|
||||
// IsPseudo returns true if op is a pseudo register.
|
||||
func IsPseudo(op Op) bool {
|
||||
return IsRegisterKind(op, reg.KindPseudo)
|
||||
}
|
||||
|
||||
// IsGP returns true if op is a general-purpose register of size n bytes.
|
||||
func IsGP(op Op, n uint) bool {
|
||||
return IsRegisterKindSize(op, reg.KindGP, n)
|
||||
}
|
||||
|
||||
// IsXMM0 returns true if op is the X0 register.
|
||||
func IsXMM0(op Op) bool {
|
||||
return op == reg.X0
|
||||
}
|
||||
|
||||
// IsXMM returns true if op is a 128-bit XMM register.
|
||||
func IsXMM(op Op) bool {
|
||||
return IsRegisterKindSize(op, reg.KindVector, 16)
|
||||
}
|
||||
|
||||
// IsYMM returns true if op is a 256-bit YMM register.
|
||||
func IsYMM(op Op) bool {
|
||||
return IsRegisterKindSize(op, reg.KindVector, 32)
|
||||
}
|
||||
|
||||
// IsRegisterKindSize returns true if op is a register of the given kind and size in bytes.
|
||||
func IsRegisterKindSize(op Op, k reg.Kind, n uint) bool {
|
||||
r, ok := op.(reg.Register)
|
||||
return ok && r.Kind() == k && r.Size() == n
|
||||
}
|
||||
|
||||
// IsRegisterKind returns true if op is a register of the given kind.
|
||||
func IsRegisterKind(op Op, k reg.Kind) bool {
|
||||
r, ok := op.(reg.Register)
|
||||
return ok && r.Kind() == k
|
||||
}
|
||||
|
||||
// IsM returns true if op is a 16-, 32- or 64-bit memory operand.
|
||||
func IsM(op Op) bool {
|
||||
// TODO(mbm): confirm "m" check is defined correctly
|
||||
// Intel manual: "A 16-, 32- or 64-bit operand in memory."
|
||||
return IsM16(op) || IsM32(op) || IsM64(op)
|
||||
}
|
||||
|
||||
// IsM8 returns true if op is an 8-bit memory operand.
|
||||
func IsM8(op Op) bool {
|
||||
// TODO(mbm): confirm "m8" check is defined correctly
|
||||
// Intel manual: "A byte operand in memory, usually expressed as a variable or
|
||||
// array name, but pointed to by the DS:(E)SI or ES:(E)DI registers. In 64-bit
|
||||
// mode, it is pointed to by the RSI or RDI registers."
|
||||
return IsMSize(op, 1)
|
||||
}
|
||||
|
||||
// IsM16 returns true if op is a 16-bit memory operand.
|
||||
func IsM16(op Op) bool {
|
||||
return IsMSize(op, 2)
|
||||
}
|
||||
|
||||
// IsM32 returns true if op is a 16-bit memory operand.
|
||||
func IsM32(op Op) bool {
|
||||
return IsMSize(op, 4)
|
||||
}
|
||||
|
||||
// IsM64 returns true if op is a 64-bit memory operand.
|
||||
func IsM64(op Op) bool {
|
||||
return IsMSize(op, 8)
|
||||
}
|
||||
|
||||
// IsMSize returns true if op is a memory operand using general-purpose address
|
||||
// registers of the given size in bytes.
|
||||
func IsMSize(op Op, n uint) bool {
|
||||
// TODO(mbm): should memory operands have a size attribute as well?
|
||||
// TODO(mbm): m8,m16,m32,m64 checks do not actually check size
|
||||
m, ok := op.(Mem)
|
||||
return ok && IsMReg(m.Base) && (m.Index == nil || IsMReg(m.Index))
|
||||
}
|
||||
|
||||
// IsMReg returns true if op is a register that can be used in a memory operand.
|
||||
func IsMReg(op Op) bool {
|
||||
return IsPseudo(op) || IsRegisterKind(op, reg.KindGP)
|
||||
}
|
||||
|
||||
// IsM128 returns true if op is a 128-bit memory operand.
|
||||
func IsM128(op Op) bool {
|
||||
// TODO(mbm): should "m128" be the same as "m64"?
|
||||
return IsM64(op)
|
||||
}
|
||||
|
||||
// IsM256 returns true if op is a 256-bit memory operand.
|
||||
func IsM256(op Op) bool {
|
||||
// TODO(mbm): should "m256" be the same as "m64"?
|
||||
return IsM64(op)
|
||||
}
|
||||
|
||||
// IsVM32X returns true if op is a vector memory operand with 32-bit XMM index.
|
||||
func IsVM32X(op Op) bool {
|
||||
return IsVmx(op)
|
||||
}
|
||||
|
||||
// IsVM64X returns true if op is a vector memory operand with 64-bit XMM index.
|
||||
func IsVM64X(op Op) bool {
|
||||
return IsVmx(op)
|
||||
}
|
||||
|
||||
// IsVmx returns true if op is a vector memory operand with XMM index.
|
||||
func IsVmx(op Op) bool {
|
||||
return isvm(op, IsXMM)
|
||||
}
|
||||
|
||||
// IsVM32Y returns true if op is a vector memory operand with 32-bit YMM index.
|
||||
func IsVM32Y(op Op) bool {
|
||||
return IsVmy(op)
|
||||
}
|
||||
|
||||
// IsVM64Y returns true if op is a vector memory operand with 64-bit YMM index.
|
||||
func IsVM64Y(op Op) bool {
|
||||
return IsVmy(op)
|
||||
}
|
||||
|
||||
// IsVmy returns true if op is a vector memory operand with YMM index.
|
||||
func IsVmy(op Op) bool {
|
||||
return isvm(op, IsYMM)
|
||||
}
|
||||
|
||||
func isvm(op Op, idx func(Op) bool) bool {
|
||||
m, ok := op.(Mem)
|
||||
return ok && IsR64(m.Base) && idx(m.Index)
|
||||
}
|
||||
|
||||
// IsREL8 returns true if op is an 8-bit offset relative to instruction pointer.
|
||||
func IsREL8(op Op) bool {
|
||||
r, ok := op.(Rel)
|
||||
return ok && r == Rel(int8(r))
|
||||
}
|
||||
|
||||
// IsREL32 returns true if op is an offset relative to instruction pointer, or a
|
||||
// label reference.
|
||||
func IsREL32(op Op) bool {
|
||||
// TODO(mbm): should labels be considered separately?
|
||||
_, rel := op.(Rel)
|
||||
_, label := op.(LabelRef)
|
||||
return rel || label
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package operand
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Constant represents a constant literal.
|
||||
type Constant interface {
|
||||
Op
|
||||
Bytes() int
|
||||
constant()
|
||||
}
|
||||
|
||||
//go:generate go run make_const.go -output zconst.go
|
||||
|
||||
// String is a string constant.
|
||||
type String string
|
||||
|
||||
// Asm returns an assembly syntax representation of the string s.
|
||||
func (s String) Asm() string { return fmt.Sprintf("$%q", s) }
|
||||
|
||||
// Bytes returns the length of s.
|
||||
func (s String) Bytes() int { return len(s) }
|
||||
|
||||
func (s String) constant() {}
|
||||
|
||||
// Imm returns an unsigned integer constant with size guessed from x.
|
||||
func Imm(x uint64) Constant {
|
||||
switch {
|
||||
case uint64(uint8(x)) == x:
|
||||
return U8(x)
|
||||
case uint64(uint16(x)) == x:
|
||||
return U16(x)
|
||||
case uint64(uint32(x)) == x:
|
||||
return U32(x)
|
||||
}
|
||||
return U64(x)
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package operand provides types for instruction operands.
|
||||
package operand
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
package operand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
// Op is an operand.
|
||||
type Op interface {
|
||||
Asm() string
|
||||
}
|
||||
|
||||
// Symbol represents a symbol name.
|
||||
type Symbol struct {
|
||||
Name string
|
||||
Static bool // only visible in current source file
|
||||
}
|
||||
|
||||
// NewStaticSymbol builds a static Symbol. Static symbols are only visible in the current source file.
|
||||
func NewStaticSymbol(name string) Symbol {
|
||||
return Symbol{Name: name, Static: true}
|
||||
}
|
||||
|
||||
func (s Symbol) String() string {
|
||||
n := s.Name
|
||||
if s.Static {
|
||||
n += "<>"
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Mem represents a memory reference.
|
||||
type Mem struct {
|
||||
Symbol Symbol
|
||||
Disp int
|
||||
Base reg.Register
|
||||
Index reg.Register
|
||||
Scale uint8
|
||||
}
|
||||
|
||||
// NewParamAddr is a convenience to build a Mem operand pointing to a function
|
||||
// parameter, which is a named offset from the frame pointer pseudo register.
|
||||
func NewParamAddr(name string, offset int) Mem {
|
||||
return Mem{
|
||||
Symbol: Symbol{
|
||||
Name: name,
|
||||
Static: false,
|
||||
},
|
||||
Disp: offset,
|
||||
Base: reg.FramePointer,
|
||||
}
|
||||
}
|
||||
|
||||
// NewStackAddr returns a memory reference relative to the stack pointer.
|
||||
func NewStackAddr(offset int) Mem {
|
||||
return Mem{
|
||||
Disp: offset,
|
||||
Base: reg.StackPointer,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDataAddr returns a memory reference relative to the named data symbol.
|
||||
func NewDataAddr(sym Symbol, offset int) Mem {
|
||||
return Mem{
|
||||
Symbol: sym,
|
||||
Disp: offset,
|
||||
Base: reg.StaticBase,
|
||||
}
|
||||
}
|
||||
|
||||
// Offset returns a reference to m plus idx bytes.
|
||||
func (m Mem) Offset(idx int) Mem {
|
||||
a := m
|
||||
a.Disp += idx
|
||||
return a
|
||||
}
|
||||
|
||||
// Idx returns a new memory reference with (Index, Scale) set to (r, s).
|
||||
func (m Mem) Idx(r reg.Register, s uint8) Mem {
|
||||
a := m
|
||||
a.Index = r
|
||||
a.Scale = s
|
||||
return a
|
||||
}
|
||||
|
||||
// Asm returns an assembly syntax representation of m.
|
||||
func (m Mem) Asm() string {
|
||||
a := m.Symbol.String()
|
||||
if a != "" {
|
||||
a += fmt.Sprintf("%+d", m.Disp)
|
||||
} else if m.Disp != 0 {
|
||||
a += fmt.Sprintf("%d", m.Disp)
|
||||
}
|
||||
if m.Base != nil {
|
||||
a += fmt.Sprintf("(%s)", m.Base.Asm())
|
||||
}
|
||||
if m.Index != nil && m.Scale != 0 {
|
||||
a += fmt.Sprintf("(%s*%d)", m.Index.Asm(), m.Scale)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Rel is an offset relative to the instruction pointer.
|
||||
type Rel int32
|
||||
|
||||
// Asm returns an assembly syntax representation of r.
|
||||
func (r Rel) Asm() string {
|
||||
return fmt.Sprintf(".%+d", r)
|
||||
}
|
||||
|
||||
// LabelRef is a reference to a label.
|
||||
type LabelRef string
|
||||
|
||||
// Asm returns an assembly syntax representation of l.
|
||||
func (l LabelRef) Asm() string {
|
||||
return string(l)
|
||||
}
|
||||
|
||||
// Registers returns the list of all operands involved in the given operand.
|
||||
func Registers(op Op) []reg.Register {
|
||||
switch op := op.(type) {
|
||||
case reg.Register:
|
||||
return []reg.Register{op}
|
||||
case Mem:
|
||||
var r []reg.Register
|
||||
if op.Base != nil {
|
||||
r = append(r, op.Base)
|
||||
}
|
||||
if op.Index != nil {
|
||||
r = append(r, op.Index)
|
||||
}
|
||||
return r
|
||||
case Constant, Rel, LabelRef:
|
||||
return nil
|
||||
}
|
||||
panic("unknown operand type")
|
||||
}
|
||||
|
||||
// ApplyAllocation returns an operand with allocated registers replaced. Registers missing from the allocation are left alone.
|
||||
func ApplyAllocation(op Op, a reg.Allocation) Op {
|
||||
switch op := op.(type) {
|
||||
case reg.Register:
|
||||
return a.LookupRegisterDefault(op)
|
||||
case Mem:
|
||||
op.Base = a.LookupRegisterDefault(op.Base)
|
||||
op.Index = a.LookupRegisterDefault(op.Index)
|
||||
return op
|
||||
}
|
||||
return op
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// Code generated by make_const.go. DO NOT EDIT.
|
||||
|
||||
package operand
|
||||
|
||||
import "fmt"
|
||||
|
||||
// I8 is a 8-bit signed integer constant.
|
||||
type I8 int8
|
||||
|
||||
func (i I8) Asm() string { return fmt.Sprintf("$%+d", i) }
|
||||
func (i I8) Bytes() int { return 1 }
|
||||
func (i I8) constant() {}
|
||||
|
||||
// U8 is a 8-bit unsigned integer constant.
|
||||
type U8 uint8
|
||||
|
||||
func (u U8) Asm() string { return fmt.Sprintf("$%#02x", u) }
|
||||
func (u U8) Bytes() int { return 1 }
|
||||
func (u U8) constant() {}
|
||||
|
||||
// I16 is a 16-bit signed integer constant.
|
||||
type I16 int16
|
||||
|
||||
func (i I16) Asm() string { return fmt.Sprintf("$%+d", i) }
|
||||
func (i I16) Bytes() int { return 2 }
|
||||
func (i I16) constant() {}
|
||||
|
||||
// U16 is a 16-bit unsigned integer constant.
|
||||
type U16 uint16
|
||||
|
||||
func (u U16) Asm() string { return fmt.Sprintf("$%#04x", u) }
|
||||
func (u U16) Bytes() int { return 2 }
|
||||
func (u U16) constant() {}
|
||||
|
||||
// F32 is a 32-bit floating point constant.
|
||||
type F32 float32
|
||||
|
||||
func (f F32) Asm() string { return fmt.Sprintf("$(%#v)", f) }
|
||||
func (f F32) Bytes() int { return 4 }
|
||||
func (f F32) constant() {}
|
||||
|
||||
// I32 is a 32-bit signed integer constant.
|
||||
type I32 int32
|
||||
|
||||
func (i I32) Asm() string { return fmt.Sprintf("$%+d", i) }
|
||||
func (i I32) Bytes() int { return 4 }
|
||||
func (i I32) constant() {}
|
||||
|
||||
// U32 is a 32-bit unsigned integer constant.
|
||||
type U32 uint32
|
||||
|
||||
func (u U32) Asm() string { return fmt.Sprintf("$%#08x", u) }
|
||||
func (u U32) Bytes() int { return 4 }
|
||||
func (u U32) constant() {}
|
||||
|
||||
// F64 is a 64-bit floating point constant.
|
||||
type F64 float64
|
||||
|
||||
func (f F64) Asm() string { return fmt.Sprintf("$(%#v)", f) }
|
||||
func (f F64) Bytes() int { return 8 }
|
||||
func (f F64) constant() {}
|
||||
|
||||
// I64 is a 64-bit signed integer constant.
|
||||
type I64 int64
|
||||
|
||||
func (i I64) Asm() string { return fmt.Sprintf("$%+d", i) }
|
||||
func (i I64) Bytes() int { return 8 }
|
||||
func (i I64) constant() {}
|
||||
|
||||
// U64 is a 64-bit unsigned integer constant.
|
||||
type U64 uint64
|
||||
|
||||
func (u U64) Asm() string { return fmt.Sprintf("$%#016x", u) }
|
||||
func (u U64) Bytes() int { return 8 }
|
||||
func (u U64) constant() {}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
// edge is an edge of the interference graph, indicating that registers X and Y
|
||||
// must be in non-conflicting registers.
|
||||
type edge struct {
|
||||
X, Y reg.ID
|
||||
}
|
||||
|
||||
// Allocator is a graph-coloring register allocator.
|
||||
type Allocator struct {
|
||||
registers []reg.ID
|
||||
allocation reg.Allocation
|
||||
edges []*edge
|
||||
possible map[reg.ID][]reg.ID
|
||||
}
|
||||
|
||||
// NewAllocator builds an allocator for the given physical registers.
|
||||
func NewAllocator(rs []reg.Physical) (*Allocator, error) {
|
||||
// Set of IDs, excluding restricted registers.
|
||||
idset := map[reg.ID]bool{}
|
||||
for _, r := range rs {
|
||||
if (r.Info() & reg.Restricted) != 0 {
|
||||
continue
|
||||
}
|
||||
idset[r.ID()] = true
|
||||
}
|
||||
|
||||
if len(idset) == 0 {
|
||||
return nil, errors.New("no allocatable registers")
|
||||
}
|
||||
|
||||
// Produce slice of unique register IDs.
|
||||
var ids []reg.ID
|
||||
for id := range idset {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
||||
|
||||
return &Allocator{
|
||||
registers: ids,
|
||||
allocation: reg.NewEmptyAllocation(),
|
||||
possible: map[reg.ID][]reg.ID{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewAllocatorForKind builds an allocator for the given kind of registers.
|
||||
func NewAllocatorForKind(k reg.Kind) (*Allocator, error) {
|
||||
f := reg.FamilyOfKind(k)
|
||||
if f == nil {
|
||||
return nil, errors.New("unknown register family")
|
||||
}
|
||||
return NewAllocator(f.Registers())
|
||||
}
|
||||
|
||||
// AddInterferenceSet records that r interferes with every register in s. Convenience wrapper around AddInterference.
|
||||
func (a *Allocator) AddInterferenceSet(r reg.Register, s reg.MaskSet) {
|
||||
for id, mask := range s {
|
||||
if (r.Mask() & mask) != 0 {
|
||||
a.AddInterference(r.ID(), id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddInterference records that x and y must be assigned to non-conflicting physical registers.
|
||||
func (a *Allocator) AddInterference(x, y reg.ID) {
|
||||
a.Add(x)
|
||||
a.Add(y)
|
||||
a.edges = append(a.edges, &edge{X: x, Y: y})
|
||||
}
|
||||
|
||||
// Add adds a register to be allocated. Does nothing if the register has already been added.
|
||||
func (a *Allocator) Add(v reg.ID) {
|
||||
if !v.IsVirtual() {
|
||||
return
|
||||
}
|
||||
if _, found := a.possible[v]; found {
|
||||
return
|
||||
}
|
||||
a.possible[v] = a.possibleregisters(v)
|
||||
}
|
||||
|
||||
// Allocate allocates physical registers.
|
||||
func (a *Allocator) Allocate() (reg.Allocation, error) {
|
||||
for {
|
||||
if err := a.update(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if a.remaining() == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
v := a.mostrestricted()
|
||||
if err := a.alloc(v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return a.allocation, nil
|
||||
}
|
||||
|
||||
// update possible allocations based on edges.
|
||||
func (a *Allocator) update() error {
|
||||
var rem []*edge
|
||||
for _, e := range a.edges {
|
||||
x := a.allocation.LookupDefault(e.X)
|
||||
y := a.allocation.LookupDefault(e.Y)
|
||||
switch {
|
||||
case x.IsVirtual() && y.IsVirtual():
|
||||
rem = append(rem, e)
|
||||
continue
|
||||
case x.IsPhysical() && y.IsPhysical():
|
||||
if x == y {
|
||||
return errors.New("impossible register allocation")
|
||||
}
|
||||
case x.IsPhysical() && y.IsVirtual():
|
||||
a.discardconflicting(y, x)
|
||||
case x.IsVirtual() && y.IsPhysical():
|
||||
a.discardconflicting(x, y)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
a.edges = rem
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// mostrestricted returns the virtual register with the least possibilities.
|
||||
func (a *Allocator) mostrestricted() reg.ID {
|
||||
n := int(math.MaxInt32)
|
||||
var v reg.ID
|
||||
for w, p := range a.possible {
|
||||
// On a tie, choose the smallest ID in numeric order. This avoids
|
||||
// non-deterministic allocations due to map iteration order.
|
||||
if len(p) < n || (len(p) == n && w < v) {
|
||||
n = len(p)
|
||||
v = w
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// discardconflicting removes registers from vs possible list that conflict with p.
|
||||
func (a *Allocator) discardconflicting(v, p reg.ID) {
|
||||
a.possible[v] = filterregisters(a.possible[v], func(r reg.ID) bool {
|
||||
return r != p
|
||||
})
|
||||
}
|
||||
|
||||
// alloc attempts to allocate a register to v.
|
||||
func (a *Allocator) alloc(v reg.ID) error {
|
||||
ps := a.possible[v]
|
||||
if len(ps) == 0 {
|
||||
return errors.New("failed to allocate registers")
|
||||
}
|
||||
p := ps[0]
|
||||
a.allocation[v] = p
|
||||
delete(a.possible, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// remaining returns the number of unallocated registers.
|
||||
func (a *Allocator) remaining() int {
|
||||
return len(a.possible)
|
||||
}
|
||||
|
||||
// possibleregisters returns all allocate-able registers for the given virtual.
|
||||
func (a *Allocator) possibleregisters(v reg.ID) []reg.ID {
|
||||
return filterregisters(a.registers, func(r reg.ID) bool {
|
||||
return v.Kind() == r.Kind()
|
||||
})
|
||||
}
|
||||
|
||||
func filterregisters(in []reg.ID, predicate func(reg.ID) bool) []reg.ID {
|
||||
var rs []reg.ID
|
||||
for _, r := range in {
|
||||
if predicate(r) {
|
||||
rs = append(rs, r)
|
||||
}
|
||||
}
|
||||
return rs
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
)
|
||||
|
||||
// LabelTarget populates the LabelTarget of the given function. This maps from
|
||||
// label name to the following instruction.
|
||||
func LabelTarget(fn *ir.Function) error {
|
||||
target := map[ir.Label]*ir.Instruction{}
|
||||
var pending []ir.Label
|
||||
for _, node := range fn.Nodes {
|
||||
switch n := node.(type) {
|
||||
case ir.Label:
|
||||
if _, found := target[n]; found {
|
||||
return fmt.Errorf("duplicate label \"%s\"", n)
|
||||
}
|
||||
pending = append(pending, n)
|
||||
case *ir.Instruction:
|
||||
for _, label := range pending {
|
||||
target[label] = n
|
||||
}
|
||||
pending = nil
|
||||
}
|
||||
}
|
||||
if len(pending) != 0 {
|
||||
return errors.New("function ends with label")
|
||||
}
|
||||
fn.LabelTarget = target
|
||||
return nil
|
||||
}
|
||||
|
||||
// CFG constructs the call-flow-graph for the function.
|
||||
func CFG(fn *ir.Function) error {
|
||||
is := fn.Instructions()
|
||||
n := len(is)
|
||||
|
||||
// Populate successors.
|
||||
for i := 0; i < n; i++ {
|
||||
cur := is[i]
|
||||
var nxt *ir.Instruction
|
||||
if i+1 < n {
|
||||
nxt = is[i+1]
|
||||
}
|
||||
|
||||
// If it's a branch, locate the target.
|
||||
if cur.IsBranch {
|
||||
lbl := cur.TargetLabel()
|
||||
if lbl == nil {
|
||||
return errors.New("no label for branch instruction")
|
||||
}
|
||||
target, found := fn.LabelTarget[*lbl]
|
||||
if !found {
|
||||
return fmt.Errorf("unknown label %q", *lbl)
|
||||
}
|
||||
cur.Succ = append(cur.Succ, target)
|
||||
}
|
||||
|
||||
// Otherwise, could continue to the following instruction.
|
||||
switch {
|
||||
case cur.IsTerminal:
|
||||
case cur.IsUnconditionalBranch():
|
||||
default:
|
||||
cur.Succ = append(cur.Succ, nxt)
|
||||
}
|
||||
}
|
||||
|
||||
// Populate predecessors.
|
||||
for _, i := range is {
|
||||
for _, s := range i.Succ {
|
||||
if s != nil {
|
||||
s.Pred = append(s.Pred, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
)
|
||||
|
||||
// PruneJumpToFollowingLabel removes jump instructions that target an
|
||||
// immediately following label.
|
||||
func PruneJumpToFollowingLabel(fn *ir.Function) error {
|
||||
for i := 0; i+1 < len(fn.Nodes); i++ {
|
||||
node := fn.Nodes[i]
|
||||
next := fn.Nodes[i+1]
|
||||
|
||||
// This node is an unconditional jump.
|
||||
inst, ok := node.(*ir.Instruction)
|
||||
if !ok || !inst.IsBranch || inst.IsConditional {
|
||||
continue
|
||||
}
|
||||
|
||||
target := inst.TargetLabel()
|
||||
if target == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// And the jump target is the immediately following node.
|
||||
lbl, ok := next.(ir.Label)
|
||||
if !ok || lbl != *target {
|
||||
continue
|
||||
}
|
||||
|
||||
// Then the jump is unnecessary and can be removed.
|
||||
fn.Nodes = deletenode(fn.Nodes, i)
|
||||
i--
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PruneDanglingLabels removes labels that are not referenced by any branches.
|
||||
func PruneDanglingLabels(fn *ir.Function) error {
|
||||
// Count label references.
|
||||
count := map[ir.Label]int{}
|
||||
for _, n := range fn.Nodes {
|
||||
i, ok := n.(*ir.Instruction)
|
||||
if !ok || !i.IsBranch {
|
||||
continue
|
||||
}
|
||||
|
||||
target := i.TargetLabel()
|
||||
if target == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
count[*target]++
|
||||
}
|
||||
|
||||
// Look for labels with no references.
|
||||
for i := 0; i < len(fn.Nodes); i++ {
|
||||
node := fn.Nodes[i]
|
||||
lbl, ok := node.(ir.Label)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if count[lbl] == 0 {
|
||||
fn.Nodes = deletenode(fn.Nodes, i)
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PruneSelfMoves removes move instructions from one register to itself.
|
||||
func PruneSelfMoves(fn *ir.Function) error {
|
||||
return removeinstructions(fn, func(i *ir.Instruction) bool {
|
||||
switch i.Opcode {
|
||||
case "MOVB", "MOVW", "MOVL", "MOVQ":
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
return operand.IsRegister(i.Operands[0]) && operand.IsRegister(i.Operands[1]) && i.Operands[0] == i.Operands[1]
|
||||
})
|
||||
}
|
||||
|
||||
// removeinstructions deletes instructions from the given function which match predicate.
|
||||
func removeinstructions(fn *ir.Function, predicate func(*ir.Instruction) bool) error {
|
||||
// Removal of instructions has the potential to invalidate CFG structures.
|
||||
// Clear them to prevent accidental use of stale structures after this pass.
|
||||
invalidatecfg(fn)
|
||||
|
||||
for i := 0; i < len(fn.Nodes); i++ {
|
||||
n := fn.Nodes[i]
|
||||
|
||||
inst, ok := n.(*ir.Instruction)
|
||||
if !ok || !predicate(inst) {
|
||||
continue
|
||||
}
|
||||
|
||||
fn.Nodes = deletenode(fn.Nodes, i)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deletenode deletes node i from nodes and returns the resulting slice.
|
||||
func deletenode(nodes []ir.Node, i int) []ir.Node {
|
||||
n := len(nodes)
|
||||
copy(nodes[i:], nodes[i+1:])
|
||||
nodes[n-1] = nil
|
||||
return nodes[:n-1]
|
||||
}
|
||||
|
||||
// invalidatecfg clears CFG structures.
|
||||
func invalidatecfg(fn *ir.Function) {
|
||||
fn.LabelTarget = nil
|
||||
for _, i := range fn.Instructions() {
|
||||
i.Pred = nil
|
||||
i.Succ = nil
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
)
|
||||
|
||||
// RequiredISAExtensions determines ISA extensions required for the given
|
||||
// function. Populates the ISA field.
|
||||
func RequiredISAExtensions(fn *ir.Function) error {
|
||||
// Collect ISA set.
|
||||
set := map[string]bool{}
|
||||
for _, i := range fn.Instructions() {
|
||||
for _, isa := range i.ISA {
|
||||
set[isa] = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(set) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Populate the function's ISA field with the unique sorted list.
|
||||
for isa := range set {
|
||||
fn.ISA = append(fn.ISA, isa)
|
||||
}
|
||||
sort.Strings(fn.ISA)
|
||||
|
||||
return nil
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
// Package pass implements processing passes on avo Files.
|
||||
package pass
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
"github.com/mmcloughlin/avo/printer"
|
||||
)
|
||||
|
||||
// Compile pass compiles an avo file. Upon successful completion the avo file
|
||||
// may be printed to Go assembly.
|
||||
var Compile = Concat(
|
||||
Verify,
|
||||
FunctionPass(PruneJumpToFollowingLabel),
|
||||
FunctionPass(PruneDanglingLabels),
|
||||
FunctionPass(LabelTarget),
|
||||
FunctionPass(CFG),
|
||||
InstructionPass(ZeroExtend32BitOutputs),
|
||||
FunctionPass(Liveness),
|
||||
FunctionPass(AllocateRegisters),
|
||||
FunctionPass(BindRegisters),
|
||||
FunctionPass(VerifyAllocation),
|
||||
Func(IncludeTextFlagHeader),
|
||||
FunctionPass(PruneSelfMoves),
|
||||
FunctionPass(RequiredISAExtensions),
|
||||
)
|
||||
|
||||
// Interface for a processing pass.
|
||||
type Interface interface {
|
||||
Execute(*ir.File) error
|
||||
}
|
||||
|
||||
// Func adapts a function to the pass Interface.
|
||||
type Func func(*ir.File) error
|
||||
|
||||
// Execute calls p.
|
||||
func (p Func) Execute(f *ir.File) error {
|
||||
return p(f)
|
||||
}
|
||||
|
||||
// FunctionPass is a convenience for implementing a full file pass with a
|
||||
// function that operates on each avo Function independently.
|
||||
type FunctionPass func(*ir.Function) error
|
||||
|
||||
// Execute calls p on every function in the file. Exits on the first error.
|
||||
func (p FunctionPass) Execute(f *ir.File) error {
|
||||
for _, fn := range f.Functions() {
|
||||
if err := p(fn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InstructionPass is a convenience for implementing a full file pass with a
|
||||
// function that operates on each Instruction independently.
|
||||
type InstructionPass func(*ir.Instruction) error
|
||||
|
||||
// Execute calls p on every instruction in the file. Exits on the first error.
|
||||
func (p InstructionPass) Execute(f *ir.File) error {
|
||||
for _, fn := range f.Functions() {
|
||||
for _, i := range fn.Instructions() {
|
||||
if err := p(i); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Concat returns a pass that executes the given passes in order, stopping on the first error.
|
||||
func Concat(passes ...Interface) Interface {
|
||||
return Func(func(f *ir.File) error {
|
||||
for _, p := range passes {
|
||||
if err := p.Execute(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Output pass prints a file.
|
||||
type Output struct {
|
||||
Writer io.WriteCloser
|
||||
Printer printer.Printer
|
||||
}
|
||||
|
||||
// Execute prints f with the configured Printer and writes output to Writer.
|
||||
func (o *Output) Execute(f *ir.File) error {
|
||||
b, err := o.Printer.Print(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = o.Writer.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return o.Writer.Close()
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
// ZeroExtend32BitOutputs applies the rule that "32-bit operands generate a
|
||||
// 32-bit result, zero-extended to a 64-bit result in the destination
|
||||
// general-purpose register" (Intel Software Developer’s Manual, Volume 1,
|
||||
// 3.4.1.1).
|
||||
func ZeroExtend32BitOutputs(i *ir.Instruction) error {
|
||||
for j, op := range i.Outputs {
|
||||
if !operand.IsR32(op) {
|
||||
continue
|
||||
}
|
||||
r, ok := op.(reg.GP)
|
||||
if !ok {
|
||||
panic("r32 operand should satisfy reg.GP")
|
||||
}
|
||||
i.Outputs[j] = r.As64()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Liveness computes register liveness.
|
||||
func Liveness(fn *ir.Function) error {
|
||||
// Note this implementation is initially naive so as to be "obviously correct".
|
||||
// There are a well-known optimizations we can apply if necessary.
|
||||
|
||||
is := fn.Instructions()
|
||||
|
||||
// Process instructions in reverse: poor approximation to topological sort.
|
||||
// TODO(mbm): process instructions in topological sort order
|
||||
for l, r := 0, len(is)-1; l < r; l, r = l+1, r-1 {
|
||||
is[l], is[r] = is[r], is[l]
|
||||
}
|
||||
|
||||
// Initialize.
|
||||
for _, i := range is {
|
||||
i.LiveIn = reg.NewMaskSetFromRegisters(i.InputRegisters())
|
||||
i.LiveOut = reg.NewEmptyMaskSet()
|
||||
}
|
||||
|
||||
// Iterative dataflow analysis.
|
||||
for {
|
||||
changes := false
|
||||
|
||||
for _, i := range is {
|
||||
// out[n] = UNION[s IN succ[n]] in[s]
|
||||
for _, s := range i.Succ {
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
changes = i.LiveOut.Update(s.LiveIn) || changes
|
||||
}
|
||||
|
||||
// in[n] = use[n] UNION (out[n] - def[n])
|
||||
def := reg.NewMaskSetFromRegisters(i.OutputRegisters())
|
||||
changes = i.LiveIn.Update(i.LiveOut.Difference(def)) || changes
|
||||
}
|
||||
|
||||
if !changes {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllocateRegisters performs register allocation.
|
||||
func AllocateRegisters(fn *ir.Function) error {
|
||||
// Populate allocators (one per kind).
|
||||
as := map[reg.Kind]*Allocator{}
|
||||
for _, i := range fn.Instructions() {
|
||||
for _, r := range i.Registers() {
|
||||
k := r.Kind()
|
||||
if _, found := as[k]; !found {
|
||||
a, err := NewAllocatorForKind(k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
as[k] = a
|
||||
}
|
||||
as[k].Add(r.ID())
|
||||
}
|
||||
}
|
||||
|
||||
// Record register interferences.
|
||||
for _, i := range fn.Instructions() {
|
||||
for _, d := range i.OutputRegisters() {
|
||||
k := d.Kind()
|
||||
out := i.LiveOut.OfKind(k)
|
||||
out.DiscardRegister(d)
|
||||
as[k].AddInterferenceSet(d, out)
|
||||
}
|
||||
}
|
||||
|
||||
// Execute register allocation.
|
||||
fn.Allocation = reg.NewEmptyAllocation()
|
||||
for _, a := range as {
|
||||
al, err := a.Allocate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fn.Allocation.Merge(al); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BindRegisters applies the result of register allocation, replacing all virtual registers with their assigned physical registers.
|
||||
func BindRegisters(fn *ir.Function) error {
|
||||
for _, i := range fn.Instructions() {
|
||||
for idx := range i.Operands {
|
||||
i.Operands[idx] = operand.ApplyAllocation(i.Operands[idx], fn.Allocation)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyAllocation performs sanity checks following register allocation.
|
||||
func VerifyAllocation(fn *ir.Function) error {
|
||||
// All registers should be physical.
|
||||
for _, i := range fn.Instructions() {
|
||||
for _, r := range i.Registers() {
|
||||
if reg.ToPhysical(r) == nil {
|
||||
return errors.New("non physical register found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"github.com/mmcloughlin/avo/attr"
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
)
|
||||
|
||||
// IncludeTextFlagHeader includes textflag.h if necessary.
|
||||
func IncludeTextFlagHeader(f *ir.File) error {
|
||||
const textflagheader = "textflag.h"
|
||||
|
||||
// Check if we already have it.
|
||||
for _, path := range f.Includes {
|
||||
if path == textflagheader {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Add it if necessary.
|
||||
if requirestextflags(f) {
|
||||
f.Includes = append(f.Includes, textflagheader)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// requirestextflags returns whether the file uses flags in the textflags.h header.
|
||||
func requirestextflags(f *ir.File) bool {
|
||||
for _, s := range f.Sections {
|
||||
var a attr.Attribute
|
||||
switch s := s.(type) {
|
||||
case *ir.Function:
|
||||
a = s.Attributes
|
||||
case *ir.Global:
|
||||
a = s.Attributes
|
||||
}
|
||||
if a.ContainsTextFlags() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
)
|
||||
|
||||
// Verify pass validates an avo file.
|
||||
var Verify = Concat(
|
||||
InstructionPass(VerifyMemOperands),
|
||||
)
|
||||
|
||||
// VerifyMemOperands checks the instruction's memory operands.
|
||||
func VerifyMemOperands(i *ir.Instruction) error {
|
||||
for _, op := range i.Operands {
|
||||
m, ok := op.(operand.Mem)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if m.Base == nil {
|
||||
return errors.New("bad memory operand: missing base register")
|
||||
}
|
||||
|
||||
if m.Index != nil && m.Scale == 0 {
|
||||
return errors.New("bad memory operand: index register with scale 0")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mmcloughlin/avo/internal/prnt"
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
)
|
||||
|
||||
// dot is the pesky unicode dot used in Go assembly.
|
||||
const dot = "\u00b7"
|
||||
|
||||
type goasm struct {
|
||||
cfg Config
|
||||
prnt.Generator
|
||||
|
||||
instructions []*ir.Instruction
|
||||
clear bool
|
||||
}
|
||||
|
||||
// NewGoAsm constructs a printer for writing Go assembly files.
|
||||
func NewGoAsm(cfg Config) Printer {
|
||||
return &goasm{cfg: cfg}
|
||||
}
|
||||
|
||||
func (p *goasm) Print(f *ir.File) ([]byte, error) {
|
||||
p.header(f)
|
||||
for _, s := range f.Sections {
|
||||
switch s := s.(type) {
|
||||
case *ir.Function:
|
||||
p.function(s)
|
||||
case *ir.Global:
|
||||
p.global(s)
|
||||
default:
|
||||
panic("unknown section type")
|
||||
}
|
||||
}
|
||||
return p.Result()
|
||||
}
|
||||
|
||||
func (p *goasm) header(f *ir.File) {
|
||||
p.Comment(p.cfg.GeneratedWarning())
|
||||
|
||||
if len(f.Constraints) > 0 {
|
||||
p.NL()
|
||||
p.Printf(f.Constraints.GoString())
|
||||
}
|
||||
|
||||
if len(f.Includes) > 0 {
|
||||
p.NL()
|
||||
p.includes(f.Includes)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *goasm) includes(paths []string) {
|
||||
for _, path := range paths {
|
||||
p.Printf("#include \"%s\"\n", path)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *goasm) function(f *ir.Function) {
|
||||
p.NL()
|
||||
p.Comment(f.Stub())
|
||||
|
||||
if len(f.ISA) > 0 {
|
||||
p.Comment("Requires: " + strings.Join(f.ISA, ", "))
|
||||
}
|
||||
|
||||
// Reference: https://github.com/golang/go/blob/b115207baf6c2decc3820ada4574ef4e5ad940ec/src/cmd/internal/obj/util.go#L166-L176
|
||||
//
|
||||
// if p.As == ATEXT {
|
||||
// // If there are attributes, print them. Otherwise, skip the comma.
|
||||
// // In short, print one of these two:
|
||||
// // TEXT foo(SB), DUPOK|NOSPLIT, $0
|
||||
// // TEXT foo(SB), $0
|
||||
// s := p.From.Sym.Attribute.TextAttrString()
|
||||
// if s != "" {
|
||||
// fmt.Fprintf(&buf, "%s%s", sep, s)
|
||||
// sep = ", "
|
||||
// }
|
||||
// }
|
||||
//
|
||||
p.Printf("TEXT %s%s(SB)", dot, f.Name)
|
||||
if f.Attributes != 0 {
|
||||
p.Printf(", %s", f.Attributes.Asm())
|
||||
}
|
||||
p.Printf(", %s\n", textsize(f))
|
||||
|
||||
p.clear = true
|
||||
for _, node := range f.Nodes {
|
||||
switch n := node.(type) {
|
||||
case *ir.Instruction:
|
||||
p.instruction(n)
|
||||
if n.IsTerminal || n.IsUnconditionalBranch() {
|
||||
p.flush()
|
||||
}
|
||||
case ir.Label:
|
||||
p.flush()
|
||||
p.ensureclear()
|
||||
p.Printf("%s:\n", n)
|
||||
case *ir.Comment:
|
||||
p.flush()
|
||||
p.ensureclear()
|
||||
for _, line := range n.Lines {
|
||||
p.Printf("\t// %s\n", line)
|
||||
}
|
||||
default:
|
||||
panic("unexpected node type")
|
||||
}
|
||||
}
|
||||
p.flush()
|
||||
}
|
||||
|
||||
func (p *goasm) instruction(i *ir.Instruction) {
|
||||
p.instructions = append(p.instructions, i)
|
||||
p.clear = false
|
||||
}
|
||||
|
||||
func (p *goasm) flush() {
|
||||
if len(p.instructions) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine instruction width. Instructions with no operands are not
|
||||
// considered in this calculation.
|
||||
width := 0
|
||||
for _, i := range p.instructions {
|
||||
if len(i.Operands) > 0 && len(i.Opcode) > width {
|
||||
width = len(i.Opcode)
|
||||
}
|
||||
}
|
||||
|
||||
// Output instruction block.
|
||||
for _, i := range p.instructions {
|
||||
if len(i.Operands) > 0 {
|
||||
p.Printf("\t%-*s%s\n", width+1, i.Opcode, joinOperands(i.Operands))
|
||||
} else {
|
||||
p.Printf("\t%s\n", i.Opcode)
|
||||
}
|
||||
}
|
||||
|
||||
p.instructions = nil
|
||||
}
|
||||
|
||||
func (p *goasm) ensureclear() {
|
||||
if !p.clear {
|
||||
p.NL()
|
||||
p.clear = true
|
||||
}
|
||||
}
|
||||
|
||||
func (p *goasm) global(g *ir.Global) {
|
||||
p.NL()
|
||||
for _, d := range g.Data {
|
||||
a := operand.NewDataAddr(g.Symbol, d.Offset)
|
||||
p.Printf("DATA %s/%d, %s\n", a.Asm(), d.Value.Bytes(), d.Value.Asm())
|
||||
}
|
||||
p.Printf("GLOBL %s(SB), %s, $%d\n", g.Symbol, g.Attributes.Asm(), g.Size)
|
||||
}
|
||||
|
||||
func textsize(f *ir.Function) string {
|
||||
// Reference: https://github.com/golang/go/blob/b115207baf6c2decc3820ada4574ef4e5ad940ec/src/cmd/internal/obj/util.go#L260-L265
|
||||
//
|
||||
// case TYPE_TEXTSIZE:
|
||||
// if a.Val.(int32) == objabi.ArgsSizeUnknown {
|
||||
// str = fmt.Sprintf("$%d", a.Offset)
|
||||
// } else {
|
||||
// str = fmt.Sprintf("$%d-%d", a.Offset, a.Val.(int32))
|
||||
// }
|
||||
//
|
||||
s := "$" + strconv.Itoa(f.FrameBytes())
|
||||
if argsize := f.ArgumentBytes(); argsize > 0 {
|
||||
return s + "-" + strconv.Itoa(argsize)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func joinOperands(operands []operand.Op) string {
|
||||
asm := make([]string, len(operands))
|
||||
for i, op := range operands {
|
||||
asm[i] = op.Asm()
|
||||
}
|
||||
return strings.Join(asm, ", ")
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
// Package printer implements printing of avo files in various formats.
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mmcloughlin/avo/internal/stack"
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
)
|
||||
|
||||
// Printer can produce output for an avo File.
|
||||
type Printer interface {
|
||||
Print(*ir.File) ([]byte, error)
|
||||
}
|
||||
|
||||
// Builder can construct a printer.
|
||||
type Builder func(Config) Printer
|
||||
|
||||
// Config represents general printing configuration.
|
||||
type Config struct {
|
||||
// Command-line arguments passed to the generator. If provided, this will be
|
||||
// included in a code generation warning.
|
||||
Argv []string
|
||||
|
||||
// Name of the code generator.
|
||||
Name string
|
||||
|
||||
// Name of Go package the generated code will belong to.
|
||||
Pkg string
|
||||
}
|
||||
|
||||
// NewDefaultConfig produces a config with Name "avo".
|
||||
// The package name is guessed from the current directory.
|
||||
func NewDefaultConfig() Config {
|
||||
return Config{
|
||||
Name: "avo",
|
||||
Pkg: pkg(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewArgvConfig constructs a Config from os.Args.
|
||||
// The package name is guessed from the current directory.
|
||||
func NewArgvConfig() Config {
|
||||
return Config{
|
||||
Argv: os.Args,
|
||||
Pkg: pkg(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewGoRunConfig produces a Config for a generator that's expected to be
|
||||
// executed via "go run ...".
|
||||
func NewGoRunConfig() Config {
|
||||
path := mainfile()
|
||||
if path == "" {
|
||||
return NewDefaultConfig()
|
||||
}
|
||||
argv := []string{"go", "run", filepath.Base(path)}
|
||||
if len(os.Args) > 1 {
|
||||
argv = append(argv, os.Args[1:]...)
|
||||
}
|
||||
return Config{
|
||||
Argv: argv,
|
||||
Pkg: pkg(),
|
||||
}
|
||||
}
|
||||
|
||||
// GeneratedBy returns a description of the code generator.
|
||||
func (c Config) GeneratedBy() string {
|
||||
if c.Argv == nil {
|
||||
return c.Name
|
||||
}
|
||||
return fmt.Sprintf("command: %s", strings.Join(c.Argv, " "))
|
||||
}
|
||||
|
||||
// GeneratedWarning returns text for a code generation warning. Conforms to https://golang.org/s/generatedcode.
|
||||
func (c Config) GeneratedWarning() string {
|
||||
return fmt.Sprintf("Code generated by %s. DO NOT EDIT.", c.GeneratedBy())
|
||||
}
|
||||
|
||||
// mainfile attempts to determine the file path of the main function by
|
||||
// inspecting the stack. Returns empty string on failure.
|
||||
func mainfile() string {
|
||||
if m := stack.Main(); m != nil {
|
||||
return m.File
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// pkg guesses the name of the package from the working directory.
|
||||
func pkg() string {
|
||||
if cwd, err := os.Getwd(); err == nil {
|
||||
return filepath.Base(cwd)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"github.com/mmcloughlin/avo/internal/prnt"
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
)
|
||||
|
||||
type stubs struct {
|
||||
cfg Config
|
||||
prnt.Generator
|
||||
}
|
||||
|
||||
// NewStubs constructs a printer for writing stub function declarations.
|
||||
func NewStubs(cfg Config) Printer {
|
||||
return &stubs{cfg: cfg}
|
||||
}
|
||||
|
||||
func (s *stubs) Print(f *ir.File) ([]byte, error) {
|
||||
s.Comment(s.cfg.GeneratedWarning())
|
||||
|
||||
if len(f.Constraints) > 0 {
|
||||
s.NL()
|
||||
s.Printf(f.Constraints.GoString())
|
||||
}
|
||||
|
||||
s.NL()
|
||||
s.Printf("package %s\n", s.cfg.Pkg)
|
||||
for _, fn := range f.Functions() {
|
||||
s.NL()
|
||||
s.Comment(fn.Doc...)
|
||||
for _, pragma := range fn.Pragmas {
|
||||
s.pragma(pragma)
|
||||
}
|
||||
s.Printf("%s\n", fn.Stub())
|
||||
}
|
||||
return s.Result()
|
||||
}
|
||||
|
||||
func (s *stubs) pragma(p ir.Pragma) {
|
||||
s.Printf("//go:%s", p.Directive)
|
||||
for _, arg := range p.Arguments {
|
||||
s.Printf(" %s", arg)
|
||||
}
|
||||
s.NL()
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package reg
|
||||
|
||||
// Collection represents a collection of virtual registers. This is primarily
|
||||
// useful for allocating virtual registers with distinct IDs.
|
||||
type Collection struct {
|
||||
idx map[Kind]Index
|
||||
}
|
||||
|
||||
// NewCollection builds an empty register collection.
|
||||
func NewCollection() *Collection {
|
||||
return &Collection{
|
||||
idx: map[Kind]Index{},
|
||||
}
|
||||
}
|
||||
|
||||
// VirtualRegister allocates and returns a new virtual register of the given kind and width.
|
||||
func (c *Collection) VirtualRegister(k Kind, s Spec) Virtual {
|
||||
idx := c.idx[k]
|
||||
c.idx[k]++
|
||||
return NewVirtual(idx, k, s)
|
||||
}
|
||||
|
||||
// GP8L allocates and returns a general-purpose 8-bit register (low byte).
|
||||
func (c *Collection) GP8L() GPVirtual { return c.GP(S8L) }
|
||||
|
||||
// GP8H allocates and returns a general-purpose 8-bit register (high byte).
|
||||
func (c *Collection) GP8H() GPVirtual { return c.GP(S8H) }
|
||||
|
||||
// GP8 allocates and returns a general-purpose 8-bit register (low byte).
|
||||
func (c *Collection) GP8() GPVirtual { return c.GP8L() }
|
||||
|
||||
// GP16 allocates and returns a general-purpose 16-bit register.
|
||||
func (c *Collection) GP16() GPVirtual { return c.GP(S16) }
|
||||
|
||||
// GP32 allocates and returns a general-purpose 32-bit register.
|
||||
func (c *Collection) GP32() GPVirtual { return c.GP(S32) }
|
||||
|
||||
// GP64 allocates and returns a general-purpose 64-bit register.
|
||||
func (c *Collection) GP64() GPVirtual { return c.GP(S64) }
|
||||
|
||||
// GP allocates and returns a general-purpose register of the given width.
|
||||
func (c *Collection) GP(s Spec) GPVirtual { return newgpv(c.VirtualRegister(KindGP, s)) }
|
||||
|
||||
// XMM allocates and returns a 128-bit vector register.
|
||||
func (c *Collection) XMM() VecVirtual { return c.Vec(S128) }
|
||||
|
||||
// YMM allocates and returns a 256-bit vector register.
|
||||
func (c *Collection) YMM() VecVirtual { return c.Vec(S256) }
|
||||
|
||||
// ZMM allocates and returns a 512-bit vector register.
|
||||
func (c *Collection) ZMM() VecVirtual { return c.Vec(S512) }
|
||||
|
||||
// Vec allocates and returns a vector register of the given width.
|
||||
func (c *Collection) Vec(s Spec) VecVirtual { return newvecv(c.VirtualRegister(KindVector, s)) }
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package reg provides types for physical and virtual registers, and definitions of x86-64 register families.
|
||||
package reg
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package reg
|
||||
|
||||
// MaskSet maps register IDs to masks.
|
||||
type MaskSet map[ID]uint16
|
||||
|
||||
// NewEmptyMaskSet builds an empty register mask set.
|
||||
func NewEmptyMaskSet() MaskSet {
|
||||
return MaskSet{}
|
||||
}
|
||||
|
||||
// NewMaskSetFromRegisters forms a mask set from the given register list.
|
||||
func NewMaskSetFromRegisters(rs []Register) MaskSet {
|
||||
s := NewEmptyMaskSet()
|
||||
for _, r := range rs {
|
||||
s.AddRegister(r)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Clone returns a copy of s.
|
||||
func (s MaskSet) Clone() MaskSet {
|
||||
c := NewEmptyMaskSet()
|
||||
for id, mask := range s {
|
||||
c.Add(id, mask)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Add mask to the given register ID.
|
||||
// Reports whether this made any change to the set.
|
||||
func (s MaskSet) Add(id ID, mask uint16) bool {
|
||||
if (s[id] & mask) == mask {
|
||||
return false
|
||||
}
|
||||
s[id] |= mask
|
||||
return true
|
||||
}
|
||||
|
||||
// AddRegister is a convenience for adding the register's (ID, mask) to the set.
|
||||
// Reports whether this made any change to the set.
|
||||
func (s MaskSet) AddRegister(r Register) bool {
|
||||
return s.Add(r.ID(), r.Mask())
|
||||
}
|
||||
|
||||
// Discard clears masked bits from register ID.
|
||||
// Reports whether this made any change to the set.
|
||||
func (s MaskSet) Discard(id ID, mask uint16) bool {
|
||||
if curr, found := s[id]; !found || (curr&mask) == 0 {
|
||||
return false
|
||||
}
|
||||
s[id] &^= mask
|
||||
if s[id] == 0 {
|
||||
delete(s, id)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// DiscardRegister is a convenience for discarding the register's (ID, mask) from the set.
|
||||
// Reports whether this made any change to the set.
|
||||
func (s MaskSet) DiscardRegister(r Register) bool {
|
||||
return s.Discard(r.ID(), r.Mask())
|
||||
}
|
||||
|
||||
// Update adds masks in t to s.
|
||||
// Reports whether this made any change to the set.
|
||||
func (s MaskSet) Update(t MaskSet) bool {
|
||||
change := false
|
||||
for id, mask := range t {
|
||||
change = s.Add(id, mask) || change
|
||||
}
|
||||
return change
|
||||
}
|
||||
|
||||
// Difference returns the set of registers in s but not t.
|
||||
func (s MaskSet) Difference(t MaskSet) MaskSet {
|
||||
d := s.Clone()
|
||||
d.DifferenceUpdate(t)
|
||||
return d
|
||||
}
|
||||
|
||||
// DifferenceUpdate removes every element of t from s.
|
||||
func (s MaskSet) DifferenceUpdate(t MaskSet) bool {
|
||||
change := false
|
||||
for id, mask := range t {
|
||||
change = s.Discard(id, mask) || change
|
||||
}
|
||||
return change
|
||||
}
|
||||
|
||||
// Equals returns true if s and t contain the same masks.
|
||||
func (s MaskSet) Equals(t MaskSet) bool {
|
||||
if len(s) != len(t) {
|
||||
return false
|
||||
}
|
||||
for id, mask := range s {
|
||||
if _, found := t[id]; !found || mask != t[id] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// OfKind returns the set of elements of s with kind k.
|
||||
func (s MaskSet) OfKind(k Kind) MaskSet {
|
||||
t := NewEmptyMaskSet()
|
||||
for id, mask := range s {
|
||||
if id.Kind() == k {
|
||||
t.Add(id, mask)
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
package reg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Kind is a class of registers.
|
||||
type Kind uint8
|
||||
|
||||
// Index of a register within a kind.
|
||||
type Index uint16
|
||||
|
||||
// Family is a collection of Physical registers of a common kind.
|
||||
type Family struct {
|
||||
Kind Kind
|
||||
registers []Physical
|
||||
}
|
||||
|
||||
// define builds a register and adds it to the Family.
|
||||
func (f *Family) define(s Spec, idx Index, name string, flags ...Info) Physical {
|
||||
r := newregister(f, s, idx, name, flags...)
|
||||
f.add(r)
|
||||
return r
|
||||
}
|
||||
|
||||
// add r to the family.
|
||||
func (f *Family) add(r Physical) {
|
||||
if r.Kind() != f.Kind {
|
||||
panic("bad kind")
|
||||
}
|
||||
f.registers = append(f.registers, r)
|
||||
}
|
||||
|
||||
// Virtual returns a virtual register from this family's kind.
|
||||
func (f *Family) Virtual(idx Index, s Spec) Virtual {
|
||||
return NewVirtual(idx, f.Kind, s)
|
||||
}
|
||||
|
||||
// Registers returns the registers in this family.
|
||||
func (f *Family) Registers() []Physical {
|
||||
return append([]Physical(nil), f.registers...)
|
||||
}
|
||||
|
||||
// Lookup returns the register with given physical index and spec. Returns nil if no such register exists.
|
||||
func (f *Family) Lookup(idx Index, s Spec) Physical {
|
||||
for _, r := range f.registers {
|
||||
if r.PhysicalIndex() == idx && r.Mask() == s.Mask() {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID is a register identifier.
|
||||
type ID uint32
|
||||
|
||||
// newid builds a new register ID from the virtual flag v, kind and index.
|
||||
func newid(v uint8, kind Kind, idx Index) ID {
|
||||
return ID(v) | (ID(kind) << 8) | (ID(idx) << 16)
|
||||
}
|
||||
|
||||
// IsVirtual reports whether this is an ID for a virtual register.
|
||||
func (id ID) IsVirtual() bool { return (id & 1) == 1 }
|
||||
|
||||
// IsPhysical reports whether this is an ID for a physical register.
|
||||
func (id ID) IsPhysical() bool { return !id.IsVirtual() }
|
||||
|
||||
// Kind extracts the kind from the register ID.
|
||||
func (id ID) Kind() Kind { return Kind(id >> 8) }
|
||||
|
||||
// Index extracts the index from the register ID.
|
||||
func (id ID) Index() Index { return Index(id >> 16) }
|
||||
|
||||
// Register represents a virtual or physical register.
|
||||
type Register interface {
|
||||
ID() ID
|
||||
Kind() Kind
|
||||
Size() uint
|
||||
Mask() uint16
|
||||
Asm() string
|
||||
as(Spec) Register
|
||||
spec() Spec
|
||||
register()
|
||||
}
|
||||
|
||||
// Equal reports whether a and b are equal registers.
|
||||
func Equal(a, b Register) bool {
|
||||
return (a.ID() == b.ID()) && (a.Mask() == b.Mask())
|
||||
}
|
||||
|
||||
// Virtual is a register of a given type and size, not yet allocated to a physical register.
|
||||
type Virtual interface {
|
||||
VirtualIndex() Index
|
||||
Register
|
||||
}
|
||||
|
||||
// ToVirtual converts r to Virtual if possible, otherwise returns nil.
|
||||
func ToVirtual(r Register) Virtual {
|
||||
if v, ok := r.(Virtual); ok {
|
||||
return v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type virtual struct {
|
||||
idx Index
|
||||
kind Kind
|
||||
Spec
|
||||
}
|
||||
|
||||
// NewVirtual builds a Virtual register.
|
||||
func NewVirtual(idx Index, k Kind, s Spec) Virtual {
|
||||
return virtual{
|
||||
idx: idx,
|
||||
kind: k,
|
||||
Spec: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (v virtual) ID() ID { return newid(1, v.kind, v.idx) }
|
||||
func (v virtual) VirtualIndex() Index { return v.idx }
|
||||
func (v virtual) Kind() Kind { return v.kind }
|
||||
|
||||
func (v virtual) Asm() string {
|
||||
// TODO(mbm): decide on virtual register syntax
|
||||
return fmt.Sprintf("<virtual:%v:%v:%v>", v.idx, v.Kind(), v.Size())
|
||||
}
|
||||
|
||||
func (v virtual) as(s Spec) Register {
|
||||
return virtual{
|
||||
idx: v.idx,
|
||||
kind: v.kind,
|
||||
Spec: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (v virtual) spec() Spec { return v.Spec }
|
||||
func (v virtual) register() {}
|
||||
|
||||
// Info is a bitmask of register properties.
|
||||
type Info uint8
|
||||
|
||||
// Defined register Info flags.
|
||||
const (
|
||||
None Info = 0
|
||||
Restricted Info = 1 << iota
|
||||
)
|
||||
|
||||
// Physical is a concrete register.
|
||||
type Physical interface {
|
||||
PhysicalIndex() Index
|
||||
Info() Info
|
||||
Register
|
||||
}
|
||||
|
||||
// ToPhysical converts r to Physical if possible, otherwise returns nil.
|
||||
func ToPhysical(r Register) Physical {
|
||||
if p, ok := r.(Physical); ok {
|
||||
return p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// register implements Physical.
|
||||
type register struct {
|
||||
family *Family
|
||||
idx Index
|
||||
name string
|
||||
info Info
|
||||
Spec
|
||||
}
|
||||
|
||||
func newregister(f *Family, s Spec, idx Index, name string, flags ...Info) register {
|
||||
r := register{
|
||||
family: f,
|
||||
idx: idx,
|
||||
name: name,
|
||||
info: None,
|
||||
Spec: s,
|
||||
}
|
||||
for _, flag := range flags {
|
||||
r.info |= flag
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r register) ID() ID { return newid(0, r.Kind(), r.idx) }
|
||||
func (r register) PhysicalIndex() Index { return r.idx }
|
||||
func (r register) Kind() Kind { return r.family.Kind }
|
||||
func (r register) Asm() string { return r.name }
|
||||
func (r register) Info() Info { return r.info }
|
||||
|
||||
func (r register) as(s Spec) Register {
|
||||
return r.family.Lookup(r.PhysicalIndex(), s)
|
||||
}
|
||||
|
||||
func (r register) spec() Spec { return r.Spec }
|
||||
func (r register) register() {}
|
||||
|
||||
// Spec defines the size of a register as well as the bit ranges it occupies in
|
||||
// an underlying physical register.
|
||||
type Spec uint16
|
||||
|
||||
// Spec values required for x86-64.
|
||||
const (
|
||||
S0 Spec = 0x0 // zero value reserved for pseudo registers
|
||||
S8L Spec = 0x1
|
||||
S8H Spec = 0x2
|
||||
S8 = S8L
|
||||
S16 Spec = 0x3
|
||||
S32 Spec = 0x7
|
||||
S64 Spec = 0xf
|
||||
S128 Spec = 0x1f
|
||||
S256 Spec = 0x3f
|
||||
S512 Spec = 0x7f
|
||||
)
|
||||
|
||||
// Mask returns a mask representing which bytes of an underlying register are
|
||||
// used by this register. This is almost always the low bytes, except for the
|
||||
// case of the high-byte registers. If bit n of the mask is set, this means
|
||||
// bytes 2^(n-1) to 2^n-1 are used.
|
||||
func (s Spec) Mask() uint16 {
|
||||
return uint16(s)
|
||||
}
|
||||
|
||||
// Size returns the register width in bytes.
|
||||
func (s Spec) Size() uint {
|
||||
x := uint(s)
|
||||
return (x >> 1) + (x & 1)
|
||||
}
|
||||
|
||||
// LookupPhysical returns the physical register with the given parameters, or nil if not found.
|
||||
func LookupPhysical(k Kind, idx Index, s Spec) Physical {
|
||||
f := FamilyOfKind(k)
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
return f.Lookup(idx, s)
|
||||
}
|
||||
|
||||
// LookupID returns the physical register with the given id and spec, or nil if not found.
|
||||
func LookupID(id ID, s Spec) Physical {
|
||||
if id.IsVirtual() {
|
||||
return nil
|
||||
}
|
||||
return LookupPhysical(id.Kind(), id.Index(), s)
|
||||
}
|
||||
|
||||
// Allocation records a register allocation.
|
||||
type Allocation map[ID]ID
|
||||
|
||||
// NewEmptyAllocation builds an empty register allocation.
|
||||
func NewEmptyAllocation() Allocation {
|
||||
return Allocation{}
|
||||
}
|
||||
|
||||
// Merge allocations from b into a. Errors if there is disagreement on a common
|
||||
// register.
|
||||
func (a Allocation) Merge(b Allocation) error {
|
||||
for id, p := range b {
|
||||
if alt, found := a[id]; found && alt != p {
|
||||
return errors.New("disagreement on overlapping register")
|
||||
}
|
||||
a[id] = p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookupDefault returns the register ID assigned by this allocation, returning
|
||||
// id if none is found.
|
||||
func (a Allocation) LookupDefault(id ID) ID {
|
||||
if _, found := a[id]; found {
|
||||
return a[id]
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// LookupRegister the allocation for register r, or return nil if there is none.
|
||||
func (a Allocation) LookupRegister(r Register) Physical {
|
||||
// Return immediately if it is already a physical register.
|
||||
if p := ToPhysical(r); p != nil {
|
||||
return p
|
||||
}
|
||||
|
||||
// Lookup an allocation for this virtual ID.
|
||||
id, found := a[r.ID()]
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
return LookupID(id, r.spec())
|
||||
}
|
||||
|
||||
// LookupRegisterDefault returns the register assigned to r, or r itself if there is none.
|
||||
func (a Allocation) LookupRegisterDefault(r Register) Register {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
if p := a.LookupRegister(r); p != nil {
|
||||
return p
|
||||
}
|
||||
return r
|
||||
}
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
package reg
|
||||
|
||||
// Register kinds.
|
||||
const (
|
||||
KindPseudo Kind = iota
|
||||
KindGP
|
||||
KindVector
|
||||
)
|
||||
|
||||
// Declare register families.
|
||||
var (
|
||||
Pseudo = &Family{Kind: KindPseudo}
|
||||
GeneralPurpose = &Family{Kind: KindGP}
|
||||
Vector = &Family{Kind: KindVector}
|
||||
|
||||
Families = []*Family{
|
||||
Pseudo,
|
||||
GeneralPurpose,
|
||||
Vector,
|
||||
}
|
||||
)
|
||||
|
||||
var familiesByKind = map[Kind]*Family{}
|
||||
|
||||
func init() {
|
||||
for _, f := range Families {
|
||||
familiesByKind[f.Kind] = f
|
||||
}
|
||||
}
|
||||
|
||||
// FamilyOfKind returns the Family of registers of the given kind, or nil if not found.
|
||||
func FamilyOfKind(k Kind) *Family {
|
||||
return familiesByKind[k]
|
||||
}
|
||||
|
||||
// Pseudo registers.
|
||||
var (
|
||||
FramePointer = Pseudo.define(S0, 0, "FP")
|
||||
ProgramCounter = Pseudo.define(S0, 0, "PC")
|
||||
StaticBase = Pseudo.define(S0, 0, "SB")
|
||||
StackPointer = Pseudo.define(S0, 0, "SP")
|
||||
)
|
||||
|
||||
// GP provides additional methods for general purpose registers.
|
||||
type GP interface {
|
||||
As8() Register
|
||||
As8L() Register
|
||||
As8H() Register
|
||||
As16() Register
|
||||
As32() Register
|
||||
As64() Register
|
||||
}
|
||||
|
||||
// GPPhysical is a general-purpose physical register.
|
||||
type GPPhysical interface {
|
||||
Physical
|
||||
GP
|
||||
}
|
||||
|
||||
type gpp struct {
|
||||
Physical
|
||||
}
|
||||
|
||||
func newgpp(r Physical) GPPhysical { return gpp{Physical: r} }
|
||||
|
||||
func (p gpp) As8() Register { return newgpp(p.as(S8).(Physical)) }
|
||||
func (p gpp) As8L() Register { return newgpp(p.as(S8L).(Physical)) }
|
||||
func (p gpp) As8H() Register { return newgpp(p.as(S8H).(Physical)) }
|
||||
func (p gpp) As16() Register { return newgpp(p.as(S16).(Physical)) }
|
||||
func (p gpp) As32() Register { return newgpp(p.as(S32).(Physical)) }
|
||||
func (p gpp) As64() Register { return newgpp(p.as(S64).(Physical)) }
|
||||
|
||||
// GPVirtual is a general-purpose virtual register.
|
||||
type GPVirtual interface {
|
||||
Virtual
|
||||
GP
|
||||
}
|
||||
|
||||
type gpv struct {
|
||||
Virtual
|
||||
}
|
||||
|
||||
func newgpv(v Virtual) GPVirtual { return gpv{Virtual: v} }
|
||||
|
||||
func (v gpv) As8() Register { return newgpv(v.as(S8).(Virtual)) }
|
||||
func (v gpv) As8L() Register { return newgpv(v.as(S8L).(Virtual)) }
|
||||
func (v gpv) As8H() Register { return newgpv(v.as(S8H).(Virtual)) }
|
||||
func (v gpv) As16() Register { return newgpv(v.as(S16).(Virtual)) }
|
||||
func (v gpv) As32() Register { return newgpv(v.as(S32).(Virtual)) }
|
||||
func (v gpv) As64() Register { return newgpv(v.as(S64).(Virtual)) }
|
||||
|
||||
func gp(s Spec, id Index, name string, flags ...Info) GPPhysical {
|
||||
r := newgpp(newregister(GeneralPurpose, s, id, name, flags...))
|
||||
GeneralPurpose.add(r)
|
||||
return r
|
||||
}
|
||||
|
||||
// General purpose registers.
|
||||
var (
|
||||
// Low byte
|
||||
AL = gp(S8L, 0, "AL")
|
||||
CL = gp(S8L, 1, "CL")
|
||||
DL = gp(S8L, 2, "DL")
|
||||
BL = gp(S8L, 3, "BL")
|
||||
|
||||
// High byte
|
||||
AH = gp(S8H, 0, "AH")
|
||||
CH = gp(S8H, 1, "CH")
|
||||
DH = gp(S8H, 2, "DH")
|
||||
BH = gp(S8H, 3, "BH")
|
||||
|
||||
// 8-bit
|
||||
SPB = gp(S8, 4, "SP", Restricted)
|
||||
BPB = gp(S8, 5, "BP")
|
||||
SIB = gp(S8, 6, "SI")
|
||||
DIB = gp(S8, 7, "DI")
|
||||
R8B = gp(S8, 8, "R8")
|
||||
R9B = gp(S8, 9, "R9")
|
||||
R10B = gp(S8, 10, "R10")
|
||||
R11B = gp(S8, 11, "R11")
|
||||
R12B = gp(S8, 12, "R12")
|
||||
R13B = gp(S8, 13, "R13")
|
||||
R14B = gp(S8, 14, "R14")
|
||||
R15B = gp(S8, 15, "R15")
|
||||
|
||||
// 16-bit
|
||||
AX = gp(S16, 0, "AX")
|
||||
CX = gp(S16, 1, "CX")
|
||||
DX = gp(S16, 2, "DX")
|
||||
BX = gp(S16, 3, "BX")
|
||||
SP = gp(S16, 4, "SP", Restricted)
|
||||
BP = gp(S16, 5, "BP")
|
||||
SI = gp(S16, 6, "SI")
|
||||
DI = gp(S16, 7, "DI")
|
||||
R8W = gp(S16, 8, "R8")
|
||||
R9W = gp(S16, 9, "R9")
|
||||
R10W = gp(S16, 10, "R10")
|
||||
R11W = gp(S16, 11, "R11")
|
||||
R12W = gp(S16, 12, "R12")
|
||||
R13W = gp(S16, 13, "R13")
|
||||
R14W = gp(S16, 14, "R14")
|
||||
R15W = gp(S16, 15, "R15")
|
||||
|
||||
// 32-bit
|
||||
EAX = gp(S32, 0, "AX")
|
||||
ECX = gp(S32, 1, "CX")
|
||||
EDX = gp(S32, 2, "DX")
|
||||
EBX = gp(S32, 3, "BX")
|
||||
ESP = gp(S32, 4, "SP", Restricted)
|
||||
EBP = gp(S32, 5, "BP")
|
||||
ESI = gp(S32, 6, "SI")
|
||||
EDI = gp(S32, 7, "DI")
|
||||
R8L = gp(S32, 8, "R8")
|
||||
R9L = gp(S32, 9, "R9")
|
||||
R10L = gp(S32, 10, "R10")
|
||||
R11L = gp(S32, 11, "R11")
|
||||
R12L = gp(S32, 12, "R12")
|
||||
R13L = gp(S32, 13, "R13")
|
||||
R14L = gp(S32, 14, "R14")
|
||||
R15L = gp(S32, 15, "R15")
|
||||
|
||||
// 64-bit
|
||||
RAX = gp(S64, 0, "AX")
|
||||
RCX = gp(S64, 1, "CX")
|
||||
RDX = gp(S64, 2, "DX")
|
||||
RBX = gp(S64, 3, "BX")
|
||||
RSP = gp(S64, 4, "SP", Restricted)
|
||||
RBP = gp(S64, 5, "BP")
|
||||
RSI = gp(S64, 6, "SI")
|
||||
RDI = gp(S64, 7, "DI")
|
||||
R8 = gp(S64, 8, "R8")
|
||||
R9 = gp(S64, 9, "R9")
|
||||
R10 = gp(S64, 10, "R10")
|
||||
R11 = gp(S64, 11, "R11")
|
||||
R12 = gp(S64, 12, "R12")
|
||||
R13 = gp(S64, 13, "R13")
|
||||
R14 = gp(S64, 14, "R14")
|
||||
R15 = gp(S64, 15, "R15")
|
||||
)
|
||||
|
||||
// Vec provides methods for vector registers.
|
||||
type Vec interface {
|
||||
AsX() Register
|
||||
AsY() Register
|
||||
AsZ() Register
|
||||
}
|
||||
|
||||
// VecPhysical is a physical vector register.
|
||||
type VecPhysical interface {
|
||||
Physical
|
||||
Vec
|
||||
}
|
||||
|
||||
type vecp struct {
|
||||
Physical
|
||||
Vec
|
||||
}
|
||||
|
||||
func newvecp(r Physical) VecPhysical { return vecp{Physical: r} }
|
||||
|
||||
func (p vecp) AsX() Register { return newvecp(p.as(S128).(Physical)) }
|
||||
func (p vecp) AsY() Register { return newvecp(p.as(S256).(Physical)) }
|
||||
func (p vecp) AsZ() Register { return newvecp(p.as(S512).(Physical)) }
|
||||
|
||||
// VecVirtual is a virtual vector register.
|
||||
type VecVirtual interface {
|
||||
Virtual
|
||||
Vec
|
||||
}
|
||||
|
||||
type vecv struct {
|
||||
Virtual
|
||||
Vec
|
||||
}
|
||||
|
||||
func newvecv(v Virtual) VecVirtual { return vecv{Virtual: v} }
|
||||
|
||||
func (v vecv) AsX() Register { return newvecv(v.as(S128).(Virtual)) }
|
||||
func (v vecv) AsY() Register { return newvecv(v.as(S256).(Virtual)) }
|
||||
func (v vecv) AsZ() Register { return newvecv(v.as(S512).(Virtual)) }
|
||||
|
||||
func vec(s Spec, id Index, name string, flags ...Info) VecPhysical {
|
||||
r := newvecp(newregister(Vector, s, id, name, flags...))
|
||||
Vector.add(r)
|
||||
return r
|
||||
}
|
||||
|
||||
// Vector registers.
|
||||
var (
|
||||
// 128-bit
|
||||
X0 = vec(S128, 0, "X0")
|
||||
X1 = vec(S128, 1, "X1")
|
||||
X2 = vec(S128, 2, "X2")
|
||||
X3 = vec(S128, 3, "X3")
|
||||
X4 = vec(S128, 4, "X4")
|
||||
X5 = vec(S128, 5, "X5")
|
||||
X6 = vec(S128, 6, "X6")
|
||||
X7 = vec(S128, 7, "X7")
|
||||
X8 = vec(S128, 8, "X8")
|
||||
X9 = vec(S128, 9, "X9")
|
||||
X10 = vec(S128, 10, "X10")
|
||||
X11 = vec(S128, 11, "X11")
|
||||
X12 = vec(S128, 12, "X12")
|
||||
X13 = vec(S128, 13, "X13")
|
||||
X14 = vec(S128, 14, "X14")
|
||||
X15 = vec(S128, 15, "X15")
|
||||
X16 = vec(S128, 16, "X16")
|
||||
X17 = vec(S128, 17, "X17")
|
||||
X18 = vec(S128, 18, "X18")
|
||||
X19 = vec(S128, 19, "X19")
|
||||
X20 = vec(S128, 20, "X20")
|
||||
X21 = vec(S128, 21, "X21")
|
||||
X22 = vec(S128, 22, "X22")
|
||||
X23 = vec(S128, 23, "X23")
|
||||
X24 = vec(S128, 24, "X24")
|
||||
X25 = vec(S128, 25, "X25")
|
||||
X26 = vec(S128, 26, "X26")
|
||||
X27 = vec(S128, 27, "X27")
|
||||
X28 = vec(S128, 28, "X28")
|
||||
X29 = vec(S128, 29, "X29")
|
||||
X30 = vec(S128, 30, "X30")
|
||||
X31 = vec(S128, 31, "X31")
|
||||
|
||||
// 256-bit
|
||||
Y0 = vec(S256, 0, "Y0")
|
||||
Y1 = vec(S256, 1, "Y1")
|
||||
Y2 = vec(S256, 2, "Y2")
|
||||
Y3 = vec(S256, 3, "Y3")
|
||||
Y4 = vec(S256, 4, "Y4")
|
||||
Y5 = vec(S256, 5, "Y5")
|
||||
Y6 = vec(S256, 6, "Y6")
|
||||
Y7 = vec(S256, 7, "Y7")
|
||||
Y8 = vec(S256, 8, "Y8")
|
||||
Y9 = vec(S256, 9, "Y9")
|
||||
Y10 = vec(S256, 10, "Y10")
|
||||
Y11 = vec(S256, 11, "Y11")
|
||||
Y12 = vec(S256, 12, "Y12")
|
||||
Y13 = vec(S256, 13, "Y13")
|
||||
Y14 = vec(S256, 14, "Y14")
|
||||
Y15 = vec(S256, 15, "Y15")
|
||||
Y16 = vec(S256, 16, "Y16")
|
||||
Y17 = vec(S256, 17, "Y17")
|
||||
Y18 = vec(S256, 18, "Y18")
|
||||
Y19 = vec(S256, 19, "Y19")
|
||||
Y20 = vec(S256, 20, "Y20")
|
||||
Y21 = vec(S256, 21, "Y21")
|
||||
Y22 = vec(S256, 22, "Y22")
|
||||
Y23 = vec(S256, 23, "Y23")
|
||||
Y24 = vec(S256, 24, "Y24")
|
||||
Y25 = vec(S256, 25, "Y25")
|
||||
Y26 = vec(S256, 26, "Y26")
|
||||
Y27 = vec(S256, 27, "Y27")
|
||||
Y28 = vec(S256, 28, "Y28")
|
||||
Y29 = vec(S256, 29, "Y29")
|
||||
Y30 = vec(S256, 30, "Y30")
|
||||
Y31 = vec(S256, 31, "Y31")
|
||||
|
||||
// 512-bit
|
||||
Z0 = vec(S512, 0, "Z0")
|
||||
Z1 = vec(S512, 1, "Z1")
|
||||
Z2 = vec(S512, 2, "Z2")
|
||||
Z3 = vec(S512, 3, "Z3")
|
||||
Z4 = vec(S512, 4, "Z4")
|
||||
Z5 = vec(S512, 5, "Z5")
|
||||
Z6 = vec(S512, 6, "Z6")
|
||||
Z7 = vec(S512, 7, "Z7")
|
||||
Z8 = vec(S512, 8, "Z8")
|
||||
Z9 = vec(S512, 9, "Z9")
|
||||
Z10 = vec(S512, 10, "Z10")
|
||||
Z11 = vec(S512, 11, "Z11")
|
||||
Z12 = vec(S512, 12, "Z12")
|
||||
Z13 = vec(S512, 13, "Z13")
|
||||
Z14 = vec(S512, 14, "Z14")
|
||||
Z15 = vec(S512, 15, "Z15")
|
||||
Z16 = vec(S512, 16, "Z16")
|
||||
Z17 = vec(S512, 17, "Z17")
|
||||
Z18 = vec(S512, 18, "Z18")
|
||||
Z19 = vec(S512, 19, "Z19")
|
||||
Z20 = vec(S512, 20, "Z20")
|
||||
Z21 = vec(S512, 21, "Z21")
|
||||
Z22 = vec(S512, 22, "Z22")
|
||||
Z23 = vec(S512, 23, "Z23")
|
||||
Z24 = vec(S512, 24, "Z24")
|
||||
Z25 = vec(S512, 25, "Z25")
|
||||
Z26 = vec(S512, 26, "Z26")
|
||||
Z27 = vec(S512, 27, "Z27")
|
||||
Z28 = vec(S512, 28, "Z28")
|
||||
Z29 = vec(S512, 29, "Z29")
|
||||
Z30 = vec(S512, 30, "Z30")
|
||||
Z31 = vec(S512, 31, "Z31")
|
||||
)
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
// Package src provides types for working with source files.
|
||||
package src
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Position represents a position in a source file.
|
||||
type Position struct {
|
||||
Filename string
|
||||
Line int // 1-up
|
||||
}
|
||||
|
||||
// FramePosition returns the Position of the given stack frame.
|
||||
func FramePosition(f runtime.Frame) Position {
|
||||
return Position{
|
||||
Filename: f.File,
|
||||
Line: f.Line,
|
||||
}
|
||||
}
|
||||
|
||||
// IsValid reports whether the position is valid: Line must be positive, but
|
||||
// Filename may be empty.
|
||||
func (p Position) IsValid() bool {
|
||||
return p.Line > 0
|
||||
}
|
||||
|
||||
// String represents Position as a string.
|
||||
func (p Position) String() string {
|
||||
if !p.IsValid() {
|
||||
return "-"
|
||||
}
|
||||
var s string
|
||||
if p.Filename != "" {
|
||||
s += p.Filename + ":"
|
||||
}
|
||||
s += strconv.Itoa(p.Line)
|
||||
return s
|
||||
}
|
||||
|
||||
// Rel returns Position relative to basepath. If the given filename cannot be
|
||||
// expressed relative to basepath the position will be returned unchanged.
|
||||
func (p Position) Rel(basepath string) Position {
|
||||
q := p
|
||||
if rel, err := filepath.Rel(basepath, q.Filename); err == nil {
|
||||
q.Filename = rel
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// Relwd returns Position relative to the current working directory. Returns p
|
||||
// unchanged if the working directory cannot be determined, or the filename
|
||||
// cannot be expressed relative to the working directory.
|
||||
func (p Position) Relwd() Position {
|
||||
if wd, err := os.Getwd(); err == nil {
|
||||
return p.Rel(wd)
|
||||
}
|
||||
return p
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package x86 provides constructors for all x86-64 instructions.
|
||||
package x86
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package x86
|
||||
|
||||
//go:generate avogen -output zctors.go ctors
|
||||
//go:generate avogen -output zctors_test.go ctorstest
|
||||
+34629
File diff suppressed because it is too large
Load Diff
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
||||
+388
@@ -0,0 +1,388 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package semver implements comparison of semantic version strings.
|
||||
// In this package, semantic version strings must begin with a leading "v",
|
||||
// as in "v1.0.0".
|
||||
//
|
||||
// The general form of a semantic version string accepted by this package is
|
||||
//
|
||||
// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]]
|
||||
//
|
||||
// where square brackets indicate optional parts of the syntax;
|
||||
// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros;
|
||||
// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers
|
||||
// using only alphanumeric characters and hyphens; and
|
||||
// all-numeric PRERELEASE identifiers must not have leading zeros.
|
||||
//
|
||||
// This package follows Semantic Versioning 2.0.0 (see semver.org)
|
||||
// with two exceptions. First, it requires the "v" prefix. Second, it recognizes
|
||||
// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes)
|
||||
// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
|
||||
package semver
|
||||
|
||||
// parsed returns the parsed form of a semantic version string.
|
||||
type parsed struct {
|
||||
major string
|
||||
minor string
|
||||
patch string
|
||||
short string
|
||||
prerelease string
|
||||
build string
|
||||
err string
|
||||
}
|
||||
|
||||
// IsValid reports whether v is a valid semantic version string.
|
||||
func IsValid(v string) bool {
|
||||
_, ok := parse(v)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Canonical returns the canonical formatting of the semantic version v.
|
||||
// It fills in any missing .MINOR or .PATCH and discards build metadata.
|
||||
// Two semantic versions compare equal only if their canonical formattings
|
||||
// are identical strings.
|
||||
// The canonical invalid semantic version is the empty string.
|
||||
func Canonical(v string) string {
|
||||
p, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
if p.build != "" {
|
||||
return v[:len(v)-len(p.build)]
|
||||
}
|
||||
if p.short != "" {
|
||||
return v + p.short
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Major returns the major version prefix of the semantic version v.
|
||||
// For example, Major("v2.1.0") == "v2".
|
||||
// If v is an invalid semantic version string, Major returns the empty string.
|
||||
func Major(v string) string {
|
||||
pv, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return v[:1+len(pv.major)]
|
||||
}
|
||||
|
||||
// MajorMinor returns the major.minor version prefix of the semantic version v.
|
||||
// For example, MajorMinor("v2.1.0") == "v2.1".
|
||||
// If v is an invalid semantic version string, MajorMinor returns the empty string.
|
||||
func MajorMinor(v string) string {
|
||||
pv, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
i := 1 + len(pv.major)
|
||||
if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
|
||||
return v[:j]
|
||||
}
|
||||
return v[:i] + "." + pv.minor
|
||||
}
|
||||
|
||||
// Prerelease returns the prerelease suffix of the semantic version v.
|
||||
// For example, Prerelease("v2.1.0-pre+meta") == "-pre".
|
||||
// If v is an invalid semantic version string, Prerelease returns the empty string.
|
||||
func Prerelease(v string) string {
|
||||
pv, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return pv.prerelease
|
||||
}
|
||||
|
||||
// Build returns the build suffix of the semantic version v.
|
||||
// For example, Build("v2.1.0+meta") == "+meta".
|
||||
// If v is an invalid semantic version string, Build returns the empty string.
|
||||
func Build(v string) string {
|
||||
pv, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return pv.build
|
||||
}
|
||||
|
||||
// Compare returns an integer comparing two versions according to
|
||||
// semantic version precedence.
|
||||
// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
|
||||
//
|
||||
// An invalid semantic version string is considered less than a valid one.
|
||||
// All invalid semantic version strings compare equal to each other.
|
||||
func Compare(v, w string) int {
|
||||
pv, ok1 := parse(v)
|
||||
pw, ok2 := parse(w)
|
||||
if !ok1 && !ok2 {
|
||||
return 0
|
||||
}
|
||||
if !ok1 {
|
||||
return -1
|
||||
}
|
||||
if !ok2 {
|
||||
return +1
|
||||
}
|
||||
if c := compareInt(pv.major, pw.major); c != 0 {
|
||||
return c
|
||||
}
|
||||
if c := compareInt(pv.minor, pw.minor); c != 0 {
|
||||
return c
|
||||
}
|
||||
if c := compareInt(pv.patch, pw.patch); c != 0 {
|
||||
return c
|
||||
}
|
||||
return comparePrerelease(pv.prerelease, pw.prerelease)
|
||||
}
|
||||
|
||||
// Max canonicalizes its arguments and then returns the version string
|
||||
// that compares greater.
|
||||
func Max(v, w string) string {
|
||||
v = Canonical(v)
|
||||
w = Canonical(w)
|
||||
if Compare(v, w) > 0 {
|
||||
return v
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func parse(v string) (p parsed, ok bool) {
|
||||
if v == "" || v[0] != 'v' {
|
||||
p.err = "missing v prefix"
|
||||
return
|
||||
}
|
||||
p.major, v, ok = parseInt(v[1:])
|
||||
if !ok {
|
||||
p.err = "bad major version"
|
||||
return
|
||||
}
|
||||
if v == "" {
|
||||
p.minor = "0"
|
||||
p.patch = "0"
|
||||
p.short = ".0.0"
|
||||
return
|
||||
}
|
||||
if v[0] != '.' {
|
||||
p.err = "bad minor prefix"
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
p.minor, v, ok = parseInt(v[1:])
|
||||
if !ok {
|
||||
p.err = "bad minor version"
|
||||
return
|
||||
}
|
||||
if v == "" {
|
||||
p.patch = "0"
|
||||
p.short = ".0"
|
||||
return
|
||||
}
|
||||
if v[0] != '.' {
|
||||
p.err = "bad patch prefix"
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
p.patch, v, ok = parseInt(v[1:])
|
||||
if !ok {
|
||||
p.err = "bad patch version"
|
||||
return
|
||||
}
|
||||
if len(v) > 0 && v[0] == '-' {
|
||||
p.prerelease, v, ok = parsePrerelease(v)
|
||||
if !ok {
|
||||
p.err = "bad prerelease"
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(v) > 0 && v[0] == '+' {
|
||||
p.build, v, ok = parseBuild(v)
|
||||
if !ok {
|
||||
p.err = "bad build"
|
||||
return
|
||||
}
|
||||
}
|
||||
if v != "" {
|
||||
p.err = "junk on end"
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
func parseInt(v string) (t, rest string, ok bool) {
|
||||
if v == "" {
|
||||
return
|
||||
}
|
||||
if v[0] < '0' || '9' < v[0] {
|
||||
return
|
||||
}
|
||||
i := 1
|
||||
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
if v[0] == '0' && i != 1 {
|
||||
return
|
||||
}
|
||||
return v[:i], v[i:], true
|
||||
}
|
||||
|
||||
func parsePrerelease(v string) (t, rest string, ok bool) {
|
||||
// "A pre-release version MAY be denoted by appending a hyphen and
|
||||
// a series of dot separated identifiers immediately following the patch version.
|
||||
// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
|
||||
// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
|
||||
if v == "" || v[0] != '-' {
|
||||
return
|
||||
}
|
||||
i := 1
|
||||
start := 1
|
||||
for i < len(v) && v[i] != '+' {
|
||||
if !isIdentChar(v[i]) && v[i] != '.' {
|
||||
return
|
||||
}
|
||||
if v[i] == '.' {
|
||||
if start == i || isBadNum(v[start:i]) {
|
||||
return
|
||||
}
|
||||
start = i + 1
|
||||
}
|
||||
i++
|
||||
}
|
||||
if start == i || isBadNum(v[start:i]) {
|
||||
return
|
||||
}
|
||||
return v[:i], v[i:], true
|
||||
}
|
||||
|
||||
func parseBuild(v string) (t, rest string, ok bool) {
|
||||
if v == "" || v[0] != '+' {
|
||||
return
|
||||
}
|
||||
i := 1
|
||||
start := 1
|
||||
for i < len(v) {
|
||||
if !isIdentChar(v[i]) && v[i] != '.' {
|
||||
return
|
||||
}
|
||||
if v[i] == '.' {
|
||||
if start == i {
|
||||
return
|
||||
}
|
||||
start = i + 1
|
||||
}
|
||||
i++
|
||||
}
|
||||
if start == i {
|
||||
return
|
||||
}
|
||||
return v[:i], v[i:], true
|
||||
}
|
||||
|
||||
func isIdentChar(c byte) bool {
|
||||
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
|
||||
}
|
||||
|
||||
func isBadNum(v string) bool {
|
||||
i := 0
|
||||
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
return i == len(v) && i > 1 && v[0] == '0'
|
||||
}
|
||||
|
||||
func isNum(v string) bool {
|
||||
i := 0
|
||||
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
return i == len(v)
|
||||
}
|
||||
|
||||
func compareInt(x, y string) int {
|
||||
if x == y {
|
||||
return 0
|
||||
}
|
||||
if len(x) < len(y) {
|
||||
return -1
|
||||
}
|
||||
if len(x) > len(y) {
|
||||
return +1
|
||||
}
|
||||
if x < y {
|
||||
return -1
|
||||
} else {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
|
||||
func comparePrerelease(x, y string) int {
|
||||
// "When major, minor, and patch are equal, a pre-release version has
|
||||
// lower precedence than a normal version.
|
||||
// Example: 1.0.0-alpha < 1.0.0.
|
||||
// Precedence for two pre-release versions with the same major, minor,
|
||||
// and patch version MUST be determined by comparing each dot separated
|
||||
// identifier from left to right until a difference is found as follows:
|
||||
// identifiers consisting of only digits are compared numerically and
|
||||
// identifiers with letters or hyphens are compared lexically in ASCII
|
||||
// sort order. Numeric identifiers always have lower precedence than
|
||||
// non-numeric identifiers. A larger set of pre-release fields has a
|
||||
// higher precedence than a smaller set, if all of the preceding
|
||||
// identifiers are equal.
|
||||
// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
|
||||
// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
|
||||
if x == y {
|
||||
return 0
|
||||
}
|
||||
if x == "" {
|
||||
return +1
|
||||
}
|
||||
if y == "" {
|
||||
return -1
|
||||
}
|
||||
for x != "" && y != "" {
|
||||
x = x[1:] // skip - or .
|
||||
y = y[1:] // skip - or .
|
||||
var dx, dy string
|
||||
dx, x = nextIdent(x)
|
||||
dy, y = nextIdent(y)
|
||||
if dx != dy {
|
||||
ix := isNum(dx)
|
||||
iy := isNum(dy)
|
||||
if ix != iy {
|
||||
if ix {
|
||||
return -1
|
||||
} else {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
if ix {
|
||||
if len(dx) < len(dy) {
|
||||
return -1
|
||||
}
|
||||
if len(dx) > len(dy) {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
if dx < dy {
|
||||
return -1
|
||||
} else {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
}
|
||||
if x == "" {
|
||||
return -1
|
||||
} else {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
|
||||
func nextIdent(x string) (dx, rest string) {
|
||||
i := 0
|
||||
for i < len(x) && x[i] != '.' {
|
||||
i++
|
||||
}
|
||||
return x[:i], x[i:]
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package unsafeheader contains header declarations for the Go runtime's
|
||||
// slice and string implementations.
|
||||
//
|
||||
// This package allows x/sys to use types equivalent to
|
||||
// reflect.SliceHeader and reflect.StringHeader without introducing
|
||||
// a dependency on the (relatively heavy) "reflect" package.
|
||||
package unsafeheader
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Slice is the runtime representation of a slice.
|
||||
// It cannot be used safely or portably and its representation may change in a later release.
|
||||
type Slice struct {
|
||||
Data unsafe.Pointer
|
||||
Len int
|
||||
Cap int
|
||||
}
|
||||
|
||||
// String is the runtime representation of a string.
|
||||
// It cannot be used safely or portably and its representation may change in a later release.
|
||||
type String struct {
|
||||
Data unsafe.Pointer
|
||||
Len int
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package gcexportdata provides functions for locating, reading, and
|
||||
// writing export data files containing type information produced by the
|
||||
// gc compiler. This package supports go1.7 export data format and all
|
||||
// later versions.
|
||||
//
|
||||
// Although it might seem convenient for this package to live alongside
|
||||
// go/types in the standard library, this would cause version skew
|
||||
// problems for developer tools that use it, since they must be able to
|
||||
// consume the outputs of the gc compiler both before and after a Go
|
||||
// update such as from Go 1.7 to Go 1.8. Because this package lives in
|
||||
// golang.org/x/tools, sites can update their version of this repo some
|
||||
// time before the Go 1.8 release and rebuild and redeploy their
|
||||
// developer tools, which will then be able to consume both Go 1.7 and
|
||||
// Go 1.8 export data files, so they will work before and after the
|
||||
// Go update. (See discussion at https://golang.org/issue/15651.)
|
||||
//
|
||||
package gcexportdata // import "golang.org/x/tools/go/gcexportdata"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"golang.org/x/tools/go/internal/gcimporter"
|
||||
)
|
||||
|
||||
// Find returns the name of an object (.o) or archive (.a) file
|
||||
// containing type information for the specified import path,
|
||||
// using the workspace layout conventions of go/build.
|
||||
// If no file was found, an empty filename is returned.
|
||||
//
|
||||
// A relative srcDir is interpreted relative to the current working directory.
|
||||
//
|
||||
// Find also returns the package's resolved (canonical) import path,
|
||||
// reflecting the effects of srcDir and vendoring on importPath.
|
||||
func Find(importPath, srcDir string) (filename, path string) {
|
||||
return gcimporter.FindPkg(importPath, srcDir)
|
||||
}
|
||||
|
||||
// NewReader returns a reader for the export data section of an object
|
||||
// (.o) or archive (.a) file read from r. The new reader may provide
|
||||
// additional trailing data beyond the end of the export data.
|
||||
func NewReader(r io.Reader) (io.Reader, error) {
|
||||
buf := bufio.NewReader(r)
|
||||
_, err := gcimporter.FindExportData(buf)
|
||||
// If we ever switch to a zip-like archive format with the ToC
|
||||
// at the end, we can return the correct portion of export data,
|
||||
// but for now we must return the entire rest of the file.
|
||||
return buf, err
|
||||
}
|
||||
|
||||
// Read reads export data from in, decodes it, and returns type
|
||||
// information for the package.
|
||||
// The package name is specified by path.
|
||||
// File position information is added to fset.
|
||||
//
|
||||
// Read may inspect and add to the imports map to ensure that references
|
||||
// within the export data to other packages are consistent. The caller
|
||||
// must ensure that imports[path] does not exist, or exists but is
|
||||
// incomplete (see types.Package.Complete), and Read inserts the
|
||||
// resulting package into this map entry.
|
||||
//
|
||||
// On return, the state of the reader is undefined.
|
||||
func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) {
|
||||
data, err := ioutil.ReadAll(in)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading export data for %q: %v", path, err)
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(data, []byte("!<arch>")) {
|
||||
return nil, fmt.Errorf("can't read export data for %q directly from an archive file (call gcexportdata.NewReader first to extract export data)", path)
|
||||
}
|
||||
|
||||
// The App Engine Go runtime v1.6 uses the old export data format.
|
||||
// TODO(adonovan): delete once v1.7 has been around for a while.
|
||||
if bytes.HasPrefix(data, []byte("package ")) {
|
||||
return gcimporter.ImportData(imports, path, path, bytes.NewReader(data))
|
||||
}
|
||||
|
||||
// The indexed export format starts with an 'i'; the older
|
||||
// binary export format starts with a 'c', 'd', or 'v'
|
||||
// (from "version"). Select appropriate importer.
|
||||
if len(data) > 0 && data[0] == 'i' {
|
||||
_, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path)
|
||||
return pkg, err
|
||||
}
|
||||
|
||||
_, pkg, err := gcimporter.BImportData(fset, imports, data, path)
|
||||
return pkg, err
|
||||
}
|
||||
|
||||
// Write writes encoded type information for the specified package to out.
|
||||
// The FileSet provides file position information for named objects.
|
||||
func Write(out io.Writer, fset *token.FileSet, pkg *types.Package) error {
|
||||
b, err := gcimporter.IExportData(fset, pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = out.Write(b)
|
||||
return err
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gcexportdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
)
|
||||
|
||||
// NewImporter returns a new instance of the types.Importer interface
|
||||
// that reads type information from export data files written by gc.
|
||||
// The Importer also satisfies types.ImporterFrom.
|
||||
//
|
||||
// Export data files are located using "go build" workspace conventions
|
||||
// and the build.Default context.
|
||||
//
|
||||
// Use this importer instead of go/importer.For("gc", ...) to avoid the
|
||||
// version-skew problems described in the documentation of this package,
|
||||
// or to control the FileSet or access the imports map populated during
|
||||
// package loading.
|
||||
//
|
||||
func NewImporter(fset *token.FileSet, imports map[string]*types.Package) types.ImporterFrom {
|
||||
return importer{fset, imports}
|
||||
}
|
||||
|
||||
type importer struct {
|
||||
fset *token.FileSet
|
||||
imports map[string]*types.Package
|
||||
}
|
||||
|
||||
func (imp importer) Import(importPath string) (*types.Package, error) {
|
||||
return imp.ImportFrom(importPath, "", 0)
|
||||
}
|
||||
|
||||
func (imp importer) ImportFrom(importPath, srcDir string, mode types.ImportMode) (_ *types.Package, err error) {
|
||||
filename, path := Find(importPath, srcDir)
|
||||
if filename == "" {
|
||||
if importPath == "unsafe" {
|
||||
// Even for unsafe, call Find first in case
|
||||
// the package was vendored.
|
||||
return types.Unsafe, nil
|
||||
}
|
||||
return nil, fmt.Errorf("can't find import: %s", importPath)
|
||||
}
|
||||
|
||||
if pkg, ok := imp.imports[path]; ok && pkg.Complete() {
|
||||
return pkg, nil // cache hit
|
||||
}
|
||||
|
||||
// open file
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
f.Close()
|
||||
if err != nil {
|
||||
// add file name to error
|
||||
err = fmt.Errorf("reading export data: %s: %v", filename, err)
|
||||
}
|
||||
}()
|
||||
|
||||
r, err := NewReader(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Read(r, imp.fset, imp.imports, path)
|
||||
}
|
||||
+852
@@ -0,0 +1,852 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Binary package export.
|
||||
// This file was derived from $GOROOT/src/cmd/compile/internal/gc/bexport.go;
|
||||
// see that file for specification of the format.
|
||||
|
||||
package gcimporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// If debugFormat is set, each integer and string value is preceded by a marker
|
||||
// and position information in the encoding. This mechanism permits an importer
|
||||
// to recognize immediately when it is out of sync. The importer recognizes this
|
||||
// mode automatically (i.e., it can import export data produced with debugging
|
||||
// support even if debugFormat is not set at the time of import). This mode will
|
||||
// lead to massively larger export data (by a factor of 2 to 3) and should only
|
||||
// be enabled during development and debugging.
|
||||
//
|
||||
// NOTE: This flag is the first flag to enable if importing dies because of
|
||||
// (suspected) format errors, and whenever a change is made to the format.
|
||||
const debugFormat = false // default: false
|
||||
|
||||
// If trace is set, debugging output is printed to std out.
|
||||
const trace = false // default: false
|
||||
|
||||
// Current export format version. Increase with each format change.
|
||||
// Note: The latest binary (non-indexed) export format is at version 6.
|
||||
// This exporter is still at level 4, but it doesn't matter since
|
||||
// the binary importer can handle older versions just fine.
|
||||
// 6: package height (CL 105038) -- NOT IMPLEMENTED HERE
|
||||
// 5: improved position encoding efficiency (issue 20080, CL 41619) -- NOT IMPLEMEMTED HERE
|
||||
// 4: type name objects support type aliases, uses aliasTag
|
||||
// 3: Go1.8 encoding (same as version 2, aliasTag defined but never used)
|
||||
// 2: removed unused bool in ODCL export (compiler only)
|
||||
// 1: header format change (more regular), export package for _ struct fields
|
||||
// 0: Go1.7 encoding
|
||||
const exportVersion = 4
|
||||
|
||||
// trackAllTypes enables cycle tracking for all types, not just named
|
||||
// types. The existing compiler invariants assume that unnamed types
|
||||
// that are not completely set up are not used, or else there are spurious
|
||||
// errors.
|
||||
// If disabled, only named types are tracked, possibly leading to slightly
|
||||
// less efficient encoding in rare cases. It also prevents the export of
|
||||
// some corner-case type declarations (but those are not handled correctly
|
||||
// with with the textual export format either).
|
||||
// TODO(gri) enable and remove once issues caused by it are fixed
|
||||
const trackAllTypes = false
|
||||
|
||||
type exporter struct {
|
||||
fset *token.FileSet
|
||||
out bytes.Buffer
|
||||
|
||||
// object -> index maps, indexed in order of serialization
|
||||
strIndex map[string]int
|
||||
pkgIndex map[*types.Package]int
|
||||
typIndex map[types.Type]int
|
||||
|
||||
// position encoding
|
||||
posInfoFormat bool
|
||||
prevFile string
|
||||
prevLine int
|
||||
|
||||
// debugging support
|
||||
written int // bytes written
|
||||
indent int // for trace
|
||||
}
|
||||
|
||||
// internalError represents an error generated inside this package.
|
||||
type internalError string
|
||||
|
||||
func (e internalError) Error() string { return "gcimporter: " + string(e) }
|
||||
|
||||
func internalErrorf(format string, args ...interface{}) error {
|
||||
return internalError(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// BExportData returns binary export data for pkg.
|
||||
// If no file set is provided, position info will be missing.
|
||||
func BExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
if ierr, ok := e.(internalError); ok {
|
||||
err = ierr
|
||||
return
|
||||
}
|
||||
// Not an internal error; panic again.
|
||||
panic(e)
|
||||
}
|
||||
}()
|
||||
|
||||
p := exporter{
|
||||
fset: fset,
|
||||
strIndex: map[string]int{"": 0}, // empty string is mapped to 0
|
||||
pkgIndex: make(map[*types.Package]int),
|
||||
typIndex: make(map[types.Type]int),
|
||||
posInfoFormat: true, // TODO(gri) might become a flag, eventually
|
||||
}
|
||||
|
||||
// write version info
|
||||
// The version string must start with "version %d" where %d is the version
|
||||
// number. Additional debugging information may follow after a blank; that
|
||||
// text is ignored by the importer.
|
||||
p.rawStringln(fmt.Sprintf("version %d", exportVersion))
|
||||
var debug string
|
||||
if debugFormat {
|
||||
debug = "debug"
|
||||
}
|
||||
p.rawStringln(debug) // cannot use p.bool since it's affected by debugFormat; also want to see this clearly
|
||||
p.bool(trackAllTypes)
|
||||
p.bool(p.posInfoFormat)
|
||||
|
||||
// --- generic export data ---
|
||||
|
||||
// populate type map with predeclared "known" types
|
||||
for index, typ := range predeclared() {
|
||||
p.typIndex[typ] = index
|
||||
}
|
||||
if len(p.typIndex) != len(predeclared()) {
|
||||
return nil, internalError("duplicate entries in type map?")
|
||||
}
|
||||
|
||||
// write package data
|
||||
p.pkg(pkg, true)
|
||||
if trace {
|
||||
p.tracef("\n")
|
||||
}
|
||||
|
||||
// write objects
|
||||
objcount := 0
|
||||
scope := pkg.Scope()
|
||||
for _, name := range scope.Names() {
|
||||
if !ast.IsExported(name) {
|
||||
continue
|
||||
}
|
||||
if trace {
|
||||
p.tracef("\n")
|
||||
}
|
||||
p.obj(scope.Lookup(name))
|
||||
objcount++
|
||||
}
|
||||
|
||||
// indicate end of list
|
||||
if trace {
|
||||
p.tracef("\n")
|
||||
}
|
||||
p.tag(endTag)
|
||||
|
||||
// for self-verification only (redundant)
|
||||
p.int(objcount)
|
||||
|
||||
if trace {
|
||||
p.tracef("\n")
|
||||
}
|
||||
|
||||
// --- end of export data ---
|
||||
|
||||
return p.out.Bytes(), nil
|
||||
}
|
||||
|
||||
func (p *exporter) pkg(pkg *types.Package, emptypath bool) {
|
||||
if pkg == nil {
|
||||
panic(internalError("unexpected nil pkg"))
|
||||
}
|
||||
|
||||
// if we saw the package before, write its index (>= 0)
|
||||
if i, ok := p.pkgIndex[pkg]; ok {
|
||||
p.index('P', i)
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise, remember the package, write the package tag (< 0) and package data
|
||||
if trace {
|
||||
p.tracef("P%d = { ", len(p.pkgIndex))
|
||||
defer p.tracef("} ")
|
||||
}
|
||||
p.pkgIndex[pkg] = len(p.pkgIndex)
|
||||
|
||||
p.tag(packageTag)
|
||||
p.string(pkg.Name())
|
||||
if emptypath {
|
||||
p.string("")
|
||||
} else {
|
||||
p.string(pkg.Path())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) obj(obj types.Object) {
|
||||
switch obj := obj.(type) {
|
||||
case *types.Const:
|
||||
p.tag(constTag)
|
||||
p.pos(obj)
|
||||
p.qualifiedName(obj)
|
||||
p.typ(obj.Type())
|
||||
p.value(obj.Val())
|
||||
|
||||
case *types.TypeName:
|
||||
if obj.IsAlias() {
|
||||
p.tag(aliasTag)
|
||||
p.pos(obj)
|
||||
p.qualifiedName(obj)
|
||||
} else {
|
||||
p.tag(typeTag)
|
||||
}
|
||||
p.typ(obj.Type())
|
||||
|
||||
case *types.Var:
|
||||
p.tag(varTag)
|
||||
p.pos(obj)
|
||||
p.qualifiedName(obj)
|
||||
p.typ(obj.Type())
|
||||
|
||||
case *types.Func:
|
||||
p.tag(funcTag)
|
||||
p.pos(obj)
|
||||
p.qualifiedName(obj)
|
||||
sig := obj.Type().(*types.Signature)
|
||||
p.paramList(sig.Params(), sig.Variadic())
|
||||
p.paramList(sig.Results(), false)
|
||||
|
||||
default:
|
||||
panic(internalErrorf("unexpected object %v (%T)", obj, obj))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) pos(obj types.Object) {
|
||||
if !p.posInfoFormat {
|
||||
return
|
||||
}
|
||||
|
||||
file, line := p.fileLine(obj)
|
||||
if file == p.prevFile {
|
||||
// common case: write line delta
|
||||
// delta == 0 means different file or no line change
|
||||
delta := line - p.prevLine
|
||||
p.int(delta)
|
||||
if delta == 0 {
|
||||
p.int(-1) // -1 means no file change
|
||||
}
|
||||
} else {
|
||||
// different file
|
||||
p.int(0)
|
||||
// Encode filename as length of common prefix with previous
|
||||
// filename, followed by (possibly empty) suffix. Filenames
|
||||
// frequently share path prefixes, so this can save a lot
|
||||
// of space and make export data size less dependent on file
|
||||
// path length. The suffix is unlikely to be empty because
|
||||
// file names tend to end in ".go".
|
||||
n := commonPrefixLen(p.prevFile, file)
|
||||
p.int(n) // n >= 0
|
||||
p.string(file[n:]) // write suffix only
|
||||
p.prevFile = file
|
||||
p.int(line)
|
||||
}
|
||||
p.prevLine = line
|
||||
}
|
||||
|
||||
func (p *exporter) fileLine(obj types.Object) (file string, line int) {
|
||||
if p.fset != nil {
|
||||
pos := p.fset.Position(obj.Pos())
|
||||
file = pos.Filename
|
||||
line = pos.Line
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func commonPrefixLen(a, b string) int {
|
||||
if len(a) > len(b) {
|
||||
a, b = b, a
|
||||
}
|
||||
// len(a) <= len(b)
|
||||
i := 0
|
||||
for i < len(a) && a[i] == b[i] {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (p *exporter) qualifiedName(obj types.Object) {
|
||||
p.string(obj.Name())
|
||||
p.pkg(obj.Pkg(), false)
|
||||
}
|
||||
|
||||
func (p *exporter) typ(t types.Type) {
|
||||
if t == nil {
|
||||
panic(internalError("nil type"))
|
||||
}
|
||||
|
||||
// Possible optimization: Anonymous pointer types *T where
|
||||
// T is a named type are common. We could canonicalize all
|
||||
// such types *T to a single type PT = *T. This would lead
|
||||
// to at most one *T entry in typIndex, and all future *T's
|
||||
// would be encoded as the respective index directly. Would
|
||||
// save 1 byte (pointerTag) per *T and reduce the typIndex
|
||||
// size (at the cost of a canonicalization map). We can do
|
||||
// this later, without encoding format change.
|
||||
|
||||
// if we saw the type before, write its index (>= 0)
|
||||
if i, ok := p.typIndex[t]; ok {
|
||||
p.index('T', i)
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise, remember the type, write the type tag (< 0) and type data
|
||||
if trackAllTypes {
|
||||
if trace {
|
||||
p.tracef("T%d = {>\n", len(p.typIndex))
|
||||
defer p.tracef("<\n} ")
|
||||
}
|
||||
p.typIndex[t] = len(p.typIndex)
|
||||
}
|
||||
|
||||
switch t := t.(type) {
|
||||
case *types.Named:
|
||||
if !trackAllTypes {
|
||||
// if we don't track all types, track named types now
|
||||
p.typIndex[t] = len(p.typIndex)
|
||||
}
|
||||
|
||||
p.tag(namedTag)
|
||||
p.pos(t.Obj())
|
||||
p.qualifiedName(t.Obj())
|
||||
p.typ(t.Underlying())
|
||||
if !types.IsInterface(t) {
|
||||
p.assocMethods(t)
|
||||
}
|
||||
|
||||
case *types.Array:
|
||||
p.tag(arrayTag)
|
||||
p.int64(t.Len())
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *types.Slice:
|
||||
p.tag(sliceTag)
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *dddSlice:
|
||||
p.tag(dddTag)
|
||||
p.typ(t.elem)
|
||||
|
||||
case *types.Struct:
|
||||
p.tag(structTag)
|
||||
p.fieldList(t)
|
||||
|
||||
case *types.Pointer:
|
||||
p.tag(pointerTag)
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *types.Signature:
|
||||
p.tag(signatureTag)
|
||||
p.paramList(t.Params(), t.Variadic())
|
||||
p.paramList(t.Results(), false)
|
||||
|
||||
case *types.Interface:
|
||||
p.tag(interfaceTag)
|
||||
p.iface(t)
|
||||
|
||||
case *types.Map:
|
||||
p.tag(mapTag)
|
||||
p.typ(t.Key())
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *types.Chan:
|
||||
p.tag(chanTag)
|
||||
p.int(int(3 - t.Dir())) // hack
|
||||
p.typ(t.Elem())
|
||||
|
||||
default:
|
||||
panic(internalErrorf("unexpected type %T: %s", t, t))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) assocMethods(named *types.Named) {
|
||||
// Sort methods (for determinism).
|
||||
var methods []*types.Func
|
||||
for i := 0; i < named.NumMethods(); i++ {
|
||||
methods = append(methods, named.Method(i))
|
||||
}
|
||||
sort.Sort(methodsByName(methods))
|
||||
|
||||
p.int(len(methods))
|
||||
|
||||
if trace && methods != nil {
|
||||
p.tracef("associated methods {>\n")
|
||||
}
|
||||
|
||||
for i, m := range methods {
|
||||
if trace && i > 0 {
|
||||
p.tracef("\n")
|
||||
}
|
||||
|
||||
p.pos(m)
|
||||
name := m.Name()
|
||||
p.string(name)
|
||||
if !exported(name) {
|
||||
p.pkg(m.Pkg(), false)
|
||||
}
|
||||
|
||||
sig := m.Type().(*types.Signature)
|
||||
p.paramList(types.NewTuple(sig.Recv()), false)
|
||||
p.paramList(sig.Params(), sig.Variadic())
|
||||
p.paramList(sig.Results(), false)
|
||||
p.int(0) // dummy value for go:nointerface pragma - ignored by importer
|
||||
}
|
||||
|
||||
if trace && methods != nil {
|
||||
p.tracef("<\n} ")
|
||||
}
|
||||
}
|
||||
|
||||
type methodsByName []*types.Func
|
||||
|
||||
func (x methodsByName) Len() int { return len(x) }
|
||||
func (x methodsByName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
func (x methodsByName) Less(i, j int) bool { return x[i].Name() < x[j].Name() }
|
||||
|
||||
func (p *exporter) fieldList(t *types.Struct) {
|
||||
if trace && t.NumFields() > 0 {
|
||||
p.tracef("fields {>\n")
|
||||
defer p.tracef("<\n} ")
|
||||
}
|
||||
|
||||
p.int(t.NumFields())
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
if trace && i > 0 {
|
||||
p.tracef("\n")
|
||||
}
|
||||
p.field(t.Field(i))
|
||||
p.string(t.Tag(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) field(f *types.Var) {
|
||||
if !f.IsField() {
|
||||
panic(internalError("field expected"))
|
||||
}
|
||||
|
||||
p.pos(f)
|
||||
p.fieldName(f)
|
||||
p.typ(f.Type())
|
||||
}
|
||||
|
||||
func (p *exporter) iface(t *types.Interface) {
|
||||
// TODO(gri): enable importer to load embedded interfaces,
|
||||
// then emit Embeddeds and ExplicitMethods separately here.
|
||||
p.int(0)
|
||||
|
||||
n := t.NumMethods()
|
||||
if trace && n > 0 {
|
||||
p.tracef("methods {>\n")
|
||||
defer p.tracef("<\n} ")
|
||||
}
|
||||
p.int(n)
|
||||
for i := 0; i < n; i++ {
|
||||
if trace && i > 0 {
|
||||
p.tracef("\n")
|
||||
}
|
||||
p.method(t.Method(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) method(m *types.Func) {
|
||||
sig := m.Type().(*types.Signature)
|
||||
if sig.Recv() == nil {
|
||||
panic(internalError("method expected"))
|
||||
}
|
||||
|
||||
p.pos(m)
|
||||
p.string(m.Name())
|
||||
if m.Name() != "_" && !ast.IsExported(m.Name()) {
|
||||
p.pkg(m.Pkg(), false)
|
||||
}
|
||||
|
||||
// interface method; no need to encode receiver.
|
||||
p.paramList(sig.Params(), sig.Variadic())
|
||||
p.paramList(sig.Results(), false)
|
||||
}
|
||||
|
||||
func (p *exporter) fieldName(f *types.Var) {
|
||||
name := f.Name()
|
||||
|
||||
if f.Anonymous() {
|
||||
// anonymous field - we distinguish between 3 cases:
|
||||
// 1) field name matches base type name and is exported
|
||||
// 2) field name matches base type name and is not exported
|
||||
// 3) field name doesn't match base type name (alias name)
|
||||
bname := basetypeName(f.Type())
|
||||
if name == bname {
|
||||
if ast.IsExported(name) {
|
||||
name = "" // 1) we don't need to know the field name or package
|
||||
} else {
|
||||
name = "?" // 2) use unexported name "?" to force package export
|
||||
}
|
||||
} else {
|
||||
// 3) indicate alias and export name as is
|
||||
// (this requires an extra "@" but this is a rare case)
|
||||
p.string("@")
|
||||
}
|
||||
}
|
||||
|
||||
p.string(name)
|
||||
if name != "" && !ast.IsExported(name) {
|
||||
p.pkg(f.Pkg(), false)
|
||||
}
|
||||
}
|
||||
|
||||
func basetypeName(typ types.Type) string {
|
||||
switch typ := deref(typ).(type) {
|
||||
case *types.Basic:
|
||||
return typ.Name()
|
||||
case *types.Named:
|
||||
return typ.Obj().Name()
|
||||
default:
|
||||
return "" // unnamed type
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) paramList(params *types.Tuple, variadic bool) {
|
||||
// use negative length to indicate unnamed parameters
|
||||
// (look at the first parameter only since either all
|
||||
// names are present or all are absent)
|
||||
n := params.Len()
|
||||
if n > 0 && params.At(0).Name() == "" {
|
||||
n = -n
|
||||
}
|
||||
p.int(n)
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
q := params.At(i)
|
||||
t := q.Type()
|
||||
if variadic && i == params.Len()-1 {
|
||||
t = &dddSlice{t.(*types.Slice).Elem()}
|
||||
}
|
||||
p.typ(t)
|
||||
if n > 0 {
|
||||
name := q.Name()
|
||||
p.string(name)
|
||||
if name != "_" {
|
||||
p.pkg(q.Pkg(), false)
|
||||
}
|
||||
}
|
||||
p.string("") // no compiler-specific info
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) value(x constant.Value) {
|
||||
if trace {
|
||||
p.tracef("= ")
|
||||
}
|
||||
|
||||
switch x.Kind() {
|
||||
case constant.Bool:
|
||||
tag := falseTag
|
||||
if constant.BoolVal(x) {
|
||||
tag = trueTag
|
||||
}
|
||||
p.tag(tag)
|
||||
|
||||
case constant.Int:
|
||||
if v, exact := constant.Int64Val(x); exact {
|
||||
// common case: x fits into an int64 - use compact encoding
|
||||
p.tag(int64Tag)
|
||||
p.int64(v)
|
||||
return
|
||||
}
|
||||
// uncommon case: large x - use float encoding
|
||||
// (powers of 2 will be encoded efficiently with exponent)
|
||||
p.tag(floatTag)
|
||||
p.float(constant.ToFloat(x))
|
||||
|
||||
case constant.Float:
|
||||
p.tag(floatTag)
|
||||
p.float(x)
|
||||
|
||||
case constant.Complex:
|
||||
p.tag(complexTag)
|
||||
p.float(constant.Real(x))
|
||||
p.float(constant.Imag(x))
|
||||
|
||||
case constant.String:
|
||||
p.tag(stringTag)
|
||||
p.string(constant.StringVal(x))
|
||||
|
||||
case constant.Unknown:
|
||||
// package contains type errors
|
||||
p.tag(unknownTag)
|
||||
|
||||
default:
|
||||
panic(internalErrorf("unexpected value %v (%T)", x, x))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) float(x constant.Value) {
|
||||
if x.Kind() != constant.Float {
|
||||
panic(internalErrorf("unexpected constant %v, want float", x))
|
||||
}
|
||||
// extract sign (there is no -0)
|
||||
sign := constant.Sign(x)
|
||||
if sign == 0 {
|
||||
// x == 0
|
||||
p.int(0)
|
||||
return
|
||||
}
|
||||
// x != 0
|
||||
|
||||
var f big.Float
|
||||
if v, exact := constant.Float64Val(x); exact {
|
||||
// float64
|
||||
f.SetFloat64(v)
|
||||
} else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int {
|
||||
// TODO(gri): add big.Rat accessor to constant.Value.
|
||||
r := valueToRat(num)
|
||||
f.SetRat(r.Quo(r, valueToRat(denom)))
|
||||
} else {
|
||||
// Value too large to represent as a fraction => inaccessible.
|
||||
// TODO(gri): add big.Float accessor to constant.Value.
|
||||
f.SetFloat64(math.MaxFloat64) // FIXME
|
||||
}
|
||||
|
||||
// extract exponent such that 0.5 <= m < 1.0
|
||||
var m big.Float
|
||||
exp := f.MantExp(&m)
|
||||
|
||||
// extract mantissa as *big.Int
|
||||
// - set exponent large enough so mant satisfies mant.IsInt()
|
||||
// - get *big.Int from mant
|
||||
m.SetMantExp(&m, int(m.MinPrec()))
|
||||
mant, acc := m.Int(nil)
|
||||
if acc != big.Exact {
|
||||
panic(internalError("internal error"))
|
||||
}
|
||||
|
||||
p.int(sign)
|
||||
p.int(exp)
|
||||
p.string(string(mant.Bytes()))
|
||||
}
|
||||
|
||||
func valueToRat(x constant.Value) *big.Rat {
|
||||
// Convert little-endian to big-endian.
|
||||
// I can't believe this is necessary.
|
||||
bytes := constant.Bytes(x)
|
||||
for i := 0; i < len(bytes)/2; i++ {
|
||||
bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i]
|
||||
}
|
||||
return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes))
|
||||
}
|
||||
|
||||
func (p *exporter) bool(b bool) bool {
|
||||
if trace {
|
||||
p.tracef("[")
|
||||
defer p.tracef("= %v] ", b)
|
||||
}
|
||||
|
||||
x := 0
|
||||
if b {
|
||||
x = 1
|
||||
}
|
||||
p.int(x)
|
||||
return b
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Low-level encoders
|
||||
|
||||
func (p *exporter) index(marker byte, index int) {
|
||||
if index < 0 {
|
||||
panic(internalError("invalid index < 0"))
|
||||
}
|
||||
if debugFormat {
|
||||
p.marker('t')
|
||||
}
|
||||
if trace {
|
||||
p.tracef("%c%d ", marker, index)
|
||||
}
|
||||
p.rawInt64(int64(index))
|
||||
}
|
||||
|
||||
func (p *exporter) tag(tag int) {
|
||||
if tag >= 0 {
|
||||
panic(internalError("invalid tag >= 0"))
|
||||
}
|
||||
if debugFormat {
|
||||
p.marker('t')
|
||||
}
|
||||
if trace {
|
||||
p.tracef("%s ", tagString[-tag])
|
||||
}
|
||||
p.rawInt64(int64(tag))
|
||||
}
|
||||
|
||||
func (p *exporter) int(x int) {
|
||||
p.int64(int64(x))
|
||||
}
|
||||
|
||||
func (p *exporter) int64(x int64) {
|
||||
if debugFormat {
|
||||
p.marker('i')
|
||||
}
|
||||
if trace {
|
||||
p.tracef("%d ", x)
|
||||
}
|
||||
p.rawInt64(x)
|
||||
}
|
||||
|
||||
func (p *exporter) string(s string) {
|
||||
if debugFormat {
|
||||
p.marker('s')
|
||||
}
|
||||
if trace {
|
||||
p.tracef("%q ", s)
|
||||
}
|
||||
// if we saw the string before, write its index (>= 0)
|
||||
// (the empty string is mapped to 0)
|
||||
if i, ok := p.strIndex[s]; ok {
|
||||
p.rawInt64(int64(i))
|
||||
return
|
||||
}
|
||||
// otherwise, remember string and write its negative length and bytes
|
||||
p.strIndex[s] = len(p.strIndex)
|
||||
p.rawInt64(-int64(len(s)))
|
||||
for i := 0; i < len(s); i++ {
|
||||
p.rawByte(s[i])
|
||||
}
|
||||
}
|
||||
|
||||
// marker emits a marker byte and position information which makes
|
||||
// it easy for a reader to detect if it is "out of sync". Used for
|
||||
// debugFormat format only.
|
||||
func (p *exporter) marker(m byte) {
|
||||
p.rawByte(m)
|
||||
// Enable this for help tracking down the location
|
||||
// of an incorrect marker when running in debugFormat.
|
||||
if false && trace {
|
||||
p.tracef("#%d ", p.written)
|
||||
}
|
||||
p.rawInt64(int64(p.written))
|
||||
}
|
||||
|
||||
// rawInt64 should only be used by low-level encoders.
|
||||
func (p *exporter) rawInt64(x int64) {
|
||||
var tmp [binary.MaxVarintLen64]byte
|
||||
n := binary.PutVarint(tmp[:], x)
|
||||
for i := 0; i < n; i++ {
|
||||
p.rawByte(tmp[i])
|
||||
}
|
||||
}
|
||||
|
||||
// rawStringln should only be used to emit the initial version string.
|
||||
func (p *exporter) rawStringln(s string) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
p.rawByte(s[i])
|
||||
}
|
||||
p.rawByte('\n')
|
||||
}
|
||||
|
||||
// rawByte is the bottleneck interface to write to p.out.
|
||||
// rawByte escapes b as follows (any encoding does that
|
||||
// hides '$'):
|
||||
//
|
||||
// '$' => '|' 'S'
|
||||
// '|' => '|' '|'
|
||||
//
|
||||
// Necessary so other tools can find the end of the
|
||||
// export data by searching for "$$".
|
||||
// rawByte should only be used by low-level encoders.
|
||||
func (p *exporter) rawByte(b byte) {
|
||||
switch b {
|
||||
case '$':
|
||||
// write '$' as '|' 'S'
|
||||
b = 'S'
|
||||
fallthrough
|
||||
case '|':
|
||||
// write '|' as '|' '|'
|
||||
p.out.WriteByte('|')
|
||||
p.written++
|
||||
}
|
||||
p.out.WriteByte(b)
|
||||
p.written++
|
||||
}
|
||||
|
||||
// tracef is like fmt.Printf but it rewrites the format string
|
||||
// to take care of indentation.
|
||||
func (p *exporter) tracef(format string, args ...interface{}) {
|
||||
if strings.ContainsAny(format, "<>\n") {
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(format); i++ {
|
||||
// no need to deal with runes
|
||||
ch := format[i]
|
||||
switch ch {
|
||||
case '>':
|
||||
p.indent++
|
||||
continue
|
||||
case '<':
|
||||
p.indent--
|
||||
continue
|
||||
}
|
||||
buf.WriteByte(ch)
|
||||
if ch == '\n' {
|
||||
for j := p.indent; j > 0; j-- {
|
||||
buf.WriteString(". ")
|
||||
}
|
||||
}
|
||||
}
|
||||
format = buf.String()
|
||||
}
|
||||
fmt.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Debugging support.
|
||||
// (tagString is only used when tracing is enabled)
|
||||
var tagString = [...]string{
|
||||
// Packages
|
||||
-packageTag: "package",
|
||||
|
||||
// Types
|
||||
-namedTag: "named type",
|
||||
-arrayTag: "array",
|
||||
-sliceTag: "slice",
|
||||
-dddTag: "ddd",
|
||||
-structTag: "struct",
|
||||
-pointerTag: "pointer",
|
||||
-signatureTag: "signature",
|
||||
-interfaceTag: "interface",
|
||||
-mapTag: "map",
|
||||
-chanTag: "chan",
|
||||
|
||||
// Values
|
||||
-falseTag: "false",
|
||||
-trueTag: "true",
|
||||
-int64Tag: "int64",
|
||||
-floatTag: "float",
|
||||
-fractionTag: "fraction",
|
||||
-complexTag: "complex",
|
||||
-stringTag: "string",
|
||||
-unknownTag: "unknown",
|
||||
|
||||
// Type aliases
|
||||
-aliasTag: "alias",
|
||||
}
|
||||
+1039
File diff suppressed because it is too large
Load Diff
+93
@@ -0,0 +1,93 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file is a copy of $GOROOT/src/go/internal/gcimporter/exportdata.go.
|
||||
|
||||
// This file implements FindExportData.
|
||||
|
||||
package gcimporter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func readGopackHeader(r *bufio.Reader) (name string, size int, err error) {
|
||||
// See $GOROOT/include/ar.h.
|
||||
hdr := make([]byte, 16+12+6+6+8+10+2)
|
||||
_, err = io.ReadFull(r, hdr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// leave for debugging
|
||||
if false {
|
||||
fmt.Printf("header: %s", hdr)
|
||||
}
|
||||
s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10]))
|
||||
size, err = strconv.Atoi(s)
|
||||
if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' {
|
||||
err = fmt.Errorf("invalid archive header")
|
||||
return
|
||||
}
|
||||
name = strings.TrimSpace(string(hdr[:16]))
|
||||
return
|
||||
}
|
||||
|
||||
// FindExportData positions the reader r at the beginning of the
|
||||
// export data section of an underlying GC-created object/archive
|
||||
// file by reading from it. The reader must be positioned at the
|
||||
// start of the file before calling this function. The hdr result
|
||||
// is the string before the export data, either "$$" or "$$B".
|
||||
//
|
||||
func FindExportData(r *bufio.Reader) (hdr string, err error) {
|
||||
// Read first line to make sure this is an object file.
|
||||
line, err := r.ReadSlice('\n')
|
||||
if err != nil {
|
||||
err = fmt.Errorf("can't find export data (%v)", err)
|
||||
return
|
||||
}
|
||||
|
||||
if string(line) == "!<arch>\n" {
|
||||
// Archive file. Scan to __.PKGDEF.
|
||||
var name string
|
||||
if name, _, err = readGopackHeader(r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// First entry should be __.PKGDEF.
|
||||
if name != "__.PKGDEF" {
|
||||
err = fmt.Errorf("go archive is missing __.PKGDEF")
|
||||
return
|
||||
}
|
||||
|
||||
// Read first line of __.PKGDEF data, so that line
|
||||
// is once again the first line of the input.
|
||||
if line, err = r.ReadSlice('\n'); err != nil {
|
||||
err = fmt.Errorf("can't find export data (%v)", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Now at __.PKGDEF in archive or still at beginning of file.
|
||||
// Either way, line should begin with "go object ".
|
||||
if !strings.HasPrefix(string(line), "go object ") {
|
||||
err = fmt.Errorf("not a Go object file")
|
||||
return
|
||||
}
|
||||
|
||||
// Skip over object header to export data.
|
||||
// Begins after first line starting with $$.
|
||||
for line[0] != '$' {
|
||||
if line, err = r.ReadSlice('\n'); err != nil {
|
||||
err = fmt.Errorf("can't find export data (%v)", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
hdr = string(line)
|
||||
|
||||
return
|
||||
}
|
||||
+1078
File diff suppressed because it is too large
Load Diff
+739
@@ -0,0 +1,739 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Indexed binary package export.
|
||||
// This file was derived from $GOROOT/src/cmd/compile/internal/gc/iexport.go;
|
||||
// see that file for specification of the format.
|
||||
|
||||
package gcimporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Current indexed export format version. Increase with each format change.
|
||||
// 0: Go1.11 encoding
|
||||
const iexportVersion = 0
|
||||
|
||||
// IExportData returns the binary export data for pkg.
|
||||
//
|
||||
// If no file set is provided, position info will be missing.
|
||||
// The package path of the top-level package will not be recorded,
|
||||
// so that calls to IImportData can override with a provided package path.
|
||||
func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
if ierr, ok := e.(internalError); ok {
|
||||
err = ierr
|
||||
return
|
||||
}
|
||||
// Not an internal error; panic again.
|
||||
panic(e)
|
||||
}
|
||||
}()
|
||||
|
||||
p := iexporter{
|
||||
out: bytes.NewBuffer(nil),
|
||||
fset: fset,
|
||||
allPkgs: map[*types.Package]bool{},
|
||||
stringIndex: map[string]uint64{},
|
||||
declIndex: map[types.Object]uint64{},
|
||||
typIndex: map[types.Type]uint64{},
|
||||
localpkg: pkg,
|
||||
}
|
||||
|
||||
for i, pt := range predeclared() {
|
||||
p.typIndex[pt] = uint64(i)
|
||||
}
|
||||
if len(p.typIndex) > predeclReserved {
|
||||
panic(internalErrorf("too many predeclared types: %d > %d", len(p.typIndex), predeclReserved))
|
||||
}
|
||||
|
||||
// Initialize work queue with exported declarations.
|
||||
scope := pkg.Scope()
|
||||
for _, name := range scope.Names() {
|
||||
if ast.IsExported(name) {
|
||||
p.pushDecl(scope.Lookup(name))
|
||||
}
|
||||
}
|
||||
|
||||
// Loop until no more work.
|
||||
for !p.declTodo.empty() {
|
||||
p.doDecl(p.declTodo.popHead())
|
||||
}
|
||||
|
||||
// Append indices to data0 section.
|
||||
dataLen := uint64(p.data0.Len())
|
||||
w := p.newWriter()
|
||||
w.writeIndex(p.declIndex)
|
||||
w.flush()
|
||||
|
||||
// Assemble header.
|
||||
var hdr intWriter
|
||||
hdr.WriteByte('i')
|
||||
hdr.uint64(iexportVersion)
|
||||
hdr.uint64(uint64(p.strings.Len()))
|
||||
hdr.uint64(dataLen)
|
||||
|
||||
// Flush output.
|
||||
io.Copy(p.out, &hdr)
|
||||
io.Copy(p.out, &p.strings)
|
||||
io.Copy(p.out, &p.data0)
|
||||
|
||||
return p.out.Bytes(), nil
|
||||
}
|
||||
|
||||
// writeIndex writes out an object index. mainIndex indicates whether
|
||||
// we're writing out the main index, which is also read by
|
||||
// non-compiler tools and includes a complete package description
|
||||
// (i.e., name and height).
|
||||
func (w *exportWriter) writeIndex(index map[types.Object]uint64) {
|
||||
// Build a map from packages to objects from that package.
|
||||
pkgObjs := map[*types.Package][]types.Object{}
|
||||
|
||||
// For the main index, make sure to include every package that
|
||||
// we reference, even if we're not exporting (or reexporting)
|
||||
// any symbols from it.
|
||||
pkgObjs[w.p.localpkg] = nil
|
||||
for pkg := range w.p.allPkgs {
|
||||
pkgObjs[pkg] = nil
|
||||
}
|
||||
|
||||
for obj := range index {
|
||||
pkgObjs[obj.Pkg()] = append(pkgObjs[obj.Pkg()], obj)
|
||||
}
|
||||
|
||||
var pkgs []*types.Package
|
||||
for pkg, objs := range pkgObjs {
|
||||
pkgs = append(pkgs, pkg)
|
||||
|
||||
sort.Slice(objs, func(i, j int) bool {
|
||||
return objs[i].Name() < objs[j].Name()
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(pkgs, func(i, j int) bool {
|
||||
return w.exportPath(pkgs[i]) < w.exportPath(pkgs[j])
|
||||
})
|
||||
|
||||
w.uint64(uint64(len(pkgs)))
|
||||
for _, pkg := range pkgs {
|
||||
w.string(w.exportPath(pkg))
|
||||
w.string(pkg.Name())
|
||||
w.uint64(uint64(0)) // package height is not needed for go/types
|
||||
|
||||
objs := pkgObjs[pkg]
|
||||
w.uint64(uint64(len(objs)))
|
||||
for _, obj := range objs {
|
||||
w.string(obj.Name())
|
||||
w.uint64(index[obj])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type iexporter struct {
|
||||
fset *token.FileSet
|
||||
out *bytes.Buffer
|
||||
|
||||
localpkg *types.Package
|
||||
|
||||
// allPkgs tracks all packages that have been referenced by
|
||||
// the export data, so we can ensure to include them in the
|
||||
// main index.
|
||||
allPkgs map[*types.Package]bool
|
||||
|
||||
declTodo objQueue
|
||||
|
||||
strings intWriter
|
||||
stringIndex map[string]uint64
|
||||
|
||||
data0 intWriter
|
||||
declIndex map[types.Object]uint64
|
||||
typIndex map[types.Type]uint64
|
||||
}
|
||||
|
||||
// stringOff returns the offset of s within the string section.
|
||||
// If not already present, it's added to the end.
|
||||
func (p *iexporter) stringOff(s string) uint64 {
|
||||
off, ok := p.stringIndex[s]
|
||||
if !ok {
|
||||
off = uint64(p.strings.Len())
|
||||
p.stringIndex[s] = off
|
||||
|
||||
p.strings.uint64(uint64(len(s)))
|
||||
p.strings.WriteString(s)
|
||||
}
|
||||
return off
|
||||
}
|
||||
|
||||
// pushDecl adds n to the declaration work queue, if not already present.
|
||||
func (p *iexporter) pushDecl(obj types.Object) {
|
||||
// Package unsafe is known to the compiler and predeclared.
|
||||
assert(obj.Pkg() != types.Unsafe)
|
||||
|
||||
if _, ok := p.declIndex[obj]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
p.declIndex[obj] = ^uint64(0) // mark n present in work queue
|
||||
p.declTodo.pushTail(obj)
|
||||
}
|
||||
|
||||
// exportWriter handles writing out individual data section chunks.
|
||||
type exportWriter struct {
|
||||
p *iexporter
|
||||
|
||||
data intWriter
|
||||
currPkg *types.Package
|
||||
prevFile string
|
||||
prevLine int64
|
||||
}
|
||||
|
||||
func (w *exportWriter) exportPath(pkg *types.Package) string {
|
||||
if pkg == w.p.localpkg {
|
||||
return ""
|
||||
}
|
||||
return pkg.Path()
|
||||
}
|
||||
|
||||
func (p *iexporter) doDecl(obj types.Object) {
|
||||
w := p.newWriter()
|
||||
w.setPkg(obj.Pkg(), false)
|
||||
|
||||
switch obj := obj.(type) {
|
||||
case *types.Var:
|
||||
w.tag('V')
|
||||
w.pos(obj.Pos())
|
||||
w.typ(obj.Type(), obj.Pkg())
|
||||
|
||||
case *types.Func:
|
||||
sig, _ := obj.Type().(*types.Signature)
|
||||
if sig.Recv() != nil {
|
||||
panic(internalErrorf("unexpected method: %v", sig))
|
||||
}
|
||||
w.tag('F')
|
||||
w.pos(obj.Pos())
|
||||
w.signature(sig)
|
||||
|
||||
case *types.Const:
|
||||
w.tag('C')
|
||||
w.pos(obj.Pos())
|
||||
w.value(obj.Type(), obj.Val())
|
||||
|
||||
case *types.TypeName:
|
||||
if obj.IsAlias() {
|
||||
w.tag('A')
|
||||
w.pos(obj.Pos())
|
||||
w.typ(obj.Type(), obj.Pkg())
|
||||
break
|
||||
}
|
||||
|
||||
// Defined type.
|
||||
w.tag('T')
|
||||
w.pos(obj.Pos())
|
||||
|
||||
underlying := obj.Type().Underlying()
|
||||
w.typ(underlying, obj.Pkg())
|
||||
|
||||
t := obj.Type()
|
||||
if types.IsInterface(t) {
|
||||
break
|
||||
}
|
||||
|
||||
named, ok := t.(*types.Named)
|
||||
if !ok {
|
||||
panic(internalErrorf("%s is not a defined type", t))
|
||||
}
|
||||
|
||||
n := named.NumMethods()
|
||||
w.uint64(uint64(n))
|
||||
for i := 0; i < n; i++ {
|
||||
m := named.Method(i)
|
||||
w.pos(m.Pos())
|
||||
w.string(m.Name())
|
||||
sig, _ := m.Type().(*types.Signature)
|
||||
w.param(sig.Recv())
|
||||
w.signature(sig)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(internalErrorf("unexpected object: %v", obj))
|
||||
}
|
||||
|
||||
p.declIndex[obj] = w.flush()
|
||||
}
|
||||
|
||||
func (w *exportWriter) tag(tag byte) {
|
||||
w.data.WriteByte(tag)
|
||||
}
|
||||
|
||||
func (w *exportWriter) pos(pos token.Pos) {
|
||||
if w.p.fset == nil {
|
||||
w.int64(0)
|
||||
return
|
||||
}
|
||||
|
||||
p := w.p.fset.Position(pos)
|
||||
file := p.Filename
|
||||
line := int64(p.Line)
|
||||
|
||||
// When file is the same as the last position (common case),
|
||||
// we can save a few bytes by delta encoding just the line
|
||||
// number.
|
||||
//
|
||||
// Note: Because data objects may be read out of order (or not
|
||||
// at all), we can only apply delta encoding within a single
|
||||
// object. This is handled implicitly by tracking prevFile and
|
||||
// prevLine as fields of exportWriter.
|
||||
|
||||
if file == w.prevFile {
|
||||
delta := line - w.prevLine
|
||||
w.int64(delta)
|
||||
if delta == deltaNewFile {
|
||||
w.int64(-1)
|
||||
}
|
||||
} else {
|
||||
w.int64(deltaNewFile)
|
||||
w.int64(line) // line >= 0
|
||||
w.string(file)
|
||||
w.prevFile = file
|
||||
}
|
||||
w.prevLine = line
|
||||
}
|
||||
|
||||
func (w *exportWriter) pkg(pkg *types.Package) {
|
||||
// Ensure any referenced packages are declared in the main index.
|
||||
w.p.allPkgs[pkg] = true
|
||||
|
||||
w.string(w.exportPath(pkg))
|
||||
}
|
||||
|
||||
func (w *exportWriter) qualifiedIdent(obj types.Object) {
|
||||
// Ensure any referenced declarations are written out too.
|
||||
w.p.pushDecl(obj)
|
||||
|
||||
w.string(obj.Name())
|
||||
w.pkg(obj.Pkg())
|
||||
}
|
||||
|
||||
func (w *exportWriter) typ(t types.Type, pkg *types.Package) {
|
||||
w.data.uint64(w.p.typOff(t, pkg))
|
||||
}
|
||||
|
||||
func (p *iexporter) newWriter() *exportWriter {
|
||||
return &exportWriter{p: p}
|
||||
}
|
||||
|
||||
func (w *exportWriter) flush() uint64 {
|
||||
off := uint64(w.p.data0.Len())
|
||||
io.Copy(&w.p.data0, &w.data)
|
||||
return off
|
||||
}
|
||||
|
||||
func (p *iexporter) typOff(t types.Type, pkg *types.Package) uint64 {
|
||||
off, ok := p.typIndex[t]
|
||||
if !ok {
|
||||
w := p.newWriter()
|
||||
w.doTyp(t, pkg)
|
||||
off = predeclReserved + w.flush()
|
||||
p.typIndex[t] = off
|
||||
}
|
||||
return off
|
||||
}
|
||||
|
||||
func (w *exportWriter) startType(k itag) {
|
||||
w.data.uint64(uint64(k))
|
||||
}
|
||||
|
||||
func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
|
||||
switch t := t.(type) {
|
||||
case *types.Named:
|
||||
w.startType(definedType)
|
||||
w.qualifiedIdent(t.Obj())
|
||||
|
||||
case *types.Pointer:
|
||||
w.startType(pointerType)
|
||||
w.typ(t.Elem(), pkg)
|
||||
|
||||
case *types.Slice:
|
||||
w.startType(sliceType)
|
||||
w.typ(t.Elem(), pkg)
|
||||
|
||||
case *types.Array:
|
||||
w.startType(arrayType)
|
||||
w.uint64(uint64(t.Len()))
|
||||
w.typ(t.Elem(), pkg)
|
||||
|
||||
case *types.Chan:
|
||||
w.startType(chanType)
|
||||
// 1 RecvOnly; 2 SendOnly; 3 SendRecv
|
||||
var dir uint64
|
||||
switch t.Dir() {
|
||||
case types.RecvOnly:
|
||||
dir = 1
|
||||
case types.SendOnly:
|
||||
dir = 2
|
||||
case types.SendRecv:
|
||||
dir = 3
|
||||
}
|
||||
w.uint64(dir)
|
||||
w.typ(t.Elem(), pkg)
|
||||
|
||||
case *types.Map:
|
||||
w.startType(mapType)
|
||||
w.typ(t.Key(), pkg)
|
||||
w.typ(t.Elem(), pkg)
|
||||
|
||||
case *types.Signature:
|
||||
w.startType(signatureType)
|
||||
w.setPkg(pkg, true)
|
||||
w.signature(t)
|
||||
|
||||
case *types.Struct:
|
||||
w.startType(structType)
|
||||
w.setPkg(pkg, true)
|
||||
|
||||
n := t.NumFields()
|
||||
w.uint64(uint64(n))
|
||||
for i := 0; i < n; i++ {
|
||||
f := t.Field(i)
|
||||
w.pos(f.Pos())
|
||||
w.string(f.Name())
|
||||
w.typ(f.Type(), pkg)
|
||||
w.bool(f.Anonymous())
|
||||
w.string(t.Tag(i)) // note (or tag)
|
||||
}
|
||||
|
||||
case *types.Interface:
|
||||
w.startType(interfaceType)
|
||||
w.setPkg(pkg, true)
|
||||
|
||||
n := t.NumEmbeddeds()
|
||||
w.uint64(uint64(n))
|
||||
for i := 0; i < n; i++ {
|
||||
f := t.Embedded(i)
|
||||
w.pos(f.Obj().Pos())
|
||||
w.typ(f.Obj().Type(), f.Obj().Pkg())
|
||||
}
|
||||
|
||||
n = t.NumExplicitMethods()
|
||||
w.uint64(uint64(n))
|
||||
for i := 0; i < n; i++ {
|
||||
m := t.ExplicitMethod(i)
|
||||
w.pos(m.Pos())
|
||||
w.string(m.Name())
|
||||
sig, _ := m.Type().(*types.Signature)
|
||||
w.signature(sig)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(internalErrorf("unexpected type: %v, %v", t, reflect.TypeOf(t)))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *exportWriter) setPkg(pkg *types.Package, write bool) {
|
||||
if write {
|
||||
w.pkg(pkg)
|
||||
}
|
||||
|
||||
w.currPkg = pkg
|
||||
}
|
||||
|
||||
func (w *exportWriter) signature(sig *types.Signature) {
|
||||
w.paramList(sig.Params())
|
||||
w.paramList(sig.Results())
|
||||
if sig.Params().Len() > 0 {
|
||||
w.bool(sig.Variadic())
|
||||
}
|
||||
}
|
||||
|
||||
func (w *exportWriter) paramList(tup *types.Tuple) {
|
||||
n := tup.Len()
|
||||
w.uint64(uint64(n))
|
||||
for i := 0; i < n; i++ {
|
||||
w.param(tup.At(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *exportWriter) param(obj types.Object) {
|
||||
w.pos(obj.Pos())
|
||||
w.localIdent(obj)
|
||||
w.typ(obj.Type(), obj.Pkg())
|
||||
}
|
||||
|
||||
func (w *exportWriter) value(typ types.Type, v constant.Value) {
|
||||
w.typ(typ, nil)
|
||||
|
||||
switch v.Kind() {
|
||||
case constant.Bool:
|
||||
w.bool(constant.BoolVal(v))
|
||||
case constant.Int:
|
||||
var i big.Int
|
||||
if i64, exact := constant.Int64Val(v); exact {
|
||||
i.SetInt64(i64)
|
||||
} else if ui64, exact := constant.Uint64Val(v); exact {
|
||||
i.SetUint64(ui64)
|
||||
} else {
|
||||
i.SetString(v.ExactString(), 10)
|
||||
}
|
||||
w.mpint(&i, typ)
|
||||
case constant.Float:
|
||||
f := constantToFloat(v)
|
||||
w.mpfloat(f, typ)
|
||||
case constant.Complex:
|
||||
w.mpfloat(constantToFloat(constant.Real(v)), typ)
|
||||
w.mpfloat(constantToFloat(constant.Imag(v)), typ)
|
||||
case constant.String:
|
||||
w.string(constant.StringVal(v))
|
||||
case constant.Unknown:
|
||||
// package contains type errors
|
||||
default:
|
||||
panic(internalErrorf("unexpected value %v (%T)", v, v))
|
||||
}
|
||||
}
|
||||
|
||||
// constantToFloat converts a constant.Value with kind constant.Float to a
|
||||
// big.Float.
|
||||
func constantToFloat(x constant.Value) *big.Float {
|
||||
assert(x.Kind() == constant.Float)
|
||||
// Use the same floating-point precision (512) as cmd/compile
|
||||
// (see Mpprec in cmd/compile/internal/gc/mpfloat.go).
|
||||
const mpprec = 512
|
||||
var f big.Float
|
||||
f.SetPrec(mpprec)
|
||||
if v, exact := constant.Float64Val(x); exact {
|
||||
// float64
|
||||
f.SetFloat64(v)
|
||||
} else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int {
|
||||
// TODO(gri): add big.Rat accessor to constant.Value.
|
||||
n := valueToRat(num)
|
||||
d := valueToRat(denom)
|
||||
f.SetRat(n.Quo(n, d))
|
||||
} else {
|
||||
// Value too large to represent as a fraction => inaccessible.
|
||||
// TODO(gri): add big.Float accessor to constant.Value.
|
||||
_, ok := f.SetString(x.ExactString())
|
||||
assert(ok)
|
||||
}
|
||||
return &f
|
||||
}
|
||||
|
||||
// mpint exports a multi-precision integer.
|
||||
//
|
||||
// For unsigned types, small values are written out as a single
|
||||
// byte. Larger values are written out as a length-prefixed big-endian
|
||||
// byte string, where the length prefix is encoded as its complement.
|
||||
// For example, bytes 0, 1, and 2 directly represent the integer
|
||||
// values 0, 1, and 2; while bytes 255, 254, and 253 indicate a 1-,
|
||||
// 2-, and 3-byte big-endian string follow.
|
||||
//
|
||||
// Encoding for signed types use the same general approach as for
|
||||
// unsigned types, except small values use zig-zag encoding and the
|
||||
// bottom bit of length prefix byte for large values is reserved as a
|
||||
// sign bit.
|
||||
//
|
||||
// The exact boundary between small and large encodings varies
|
||||
// according to the maximum number of bytes needed to encode a value
|
||||
// of type typ. As a special case, 8-bit types are always encoded as a
|
||||
// single byte.
|
||||
//
|
||||
// TODO(mdempsky): Is this level of complexity really worthwhile?
|
||||
func (w *exportWriter) mpint(x *big.Int, typ types.Type) {
|
||||
basic, ok := typ.Underlying().(*types.Basic)
|
||||
if !ok {
|
||||
panic(internalErrorf("unexpected type %v (%T)", typ.Underlying(), typ.Underlying()))
|
||||
}
|
||||
|
||||
signed, maxBytes := intSize(basic)
|
||||
|
||||
negative := x.Sign() < 0
|
||||
if !signed && negative {
|
||||
panic(internalErrorf("negative unsigned integer; type %v, value %v", typ, x))
|
||||
}
|
||||
|
||||
b := x.Bytes()
|
||||
if len(b) > 0 && b[0] == 0 {
|
||||
panic(internalErrorf("leading zeros"))
|
||||
}
|
||||
if uint(len(b)) > maxBytes {
|
||||
panic(internalErrorf("bad mpint length: %d > %d (type %v, value %v)", len(b), maxBytes, typ, x))
|
||||
}
|
||||
|
||||
maxSmall := 256 - maxBytes
|
||||
if signed {
|
||||
maxSmall = 256 - 2*maxBytes
|
||||
}
|
||||
if maxBytes == 1 {
|
||||
maxSmall = 256
|
||||
}
|
||||
|
||||
// Check if x can use small value encoding.
|
||||
if len(b) <= 1 {
|
||||
var ux uint
|
||||
if len(b) == 1 {
|
||||
ux = uint(b[0])
|
||||
}
|
||||
if signed {
|
||||
ux <<= 1
|
||||
if negative {
|
||||
ux--
|
||||
}
|
||||
}
|
||||
if ux < maxSmall {
|
||||
w.data.WriteByte(byte(ux))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
n := 256 - uint(len(b))
|
||||
if signed {
|
||||
n = 256 - 2*uint(len(b))
|
||||
if negative {
|
||||
n |= 1
|
||||
}
|
||||
}
|
||||
if n < maxSmall || n >= 256 {
|
||||
panic(internalErrorf("encoding mistake: %d, %v, %v => %d", len(b), signed, negative, n))
|
||||
}
|
||||
|
||||
w.data.WriteByte(byte(n))
|
||||
w.data.Write(b)
|
||||
}
|
||||
|
||||
// mpfloat exports a multi-precision floating point number.
|
||||
//
|
||||
// The number's value is decomposed into mantissa × 2**exponent, where
|
||||
// mantissa is an integer. The value is written out as mantissa (as a
|
||||
// multi-precision integer) and then the exponent, except exponent is
|
||||
// omitted if mantissa is zero.
|
||||
func (w *exportWriter) mpfloat(f *big.Float, typ types.Type) {
|
||||
if f.IsInf() {
|
||||
panic("infinite constant")
|
||||
}
|
||||
|
||||
// Break into f = mant × 2**exp, with 0.5 <= mant < 1.
|
||||
var mant big.Float
|
||||
exp := int64(f.MantExp(&mant))
|
||||
|
||||
// Scale so that mant is an integer.
|
||||
prec := mant.MinPrec()
|
||||
mant.SetMantExp(&mant, int(prec))
|
||||
exp -= int64(prec)
|
||||
|
||||
manti, acc := mant.Int(nil)
|
||||
if acc != big.Exact {
|
||||
panic(internalErrorf("mantissa scaling failed for %f (%s)", f, acc))
|
||||
}
|
||||
w.mpint(manti, typ)
|
||||
if manti.Sign() != 0 {
|
||||
w.int64(exp)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *exportWriter) bool(b bool) bool {
|
||||
var x uint64
|
||||
if b {
|
||||
x = 1
|
||||
}
|
||||
w.uint64(x)
|
||||
return b
|
||||
}
|
||||
|
||||
func (w *exportWriter) int64(x int64) { w.data.int64(x) }
|
||||
func (w *exportWriter) uint64(x uint64) { w.data.uint64(x) }
|
||||
func (w *exportWriter) string(s string) { w.uint64(w.p.stringOff(s)) }
|
||||
|
||||
func (w *exportWriter) localIdent(obj types.Object) {
|
||||
// Anonymous parameters.
|
||||
if obj == nil {
|
||||
w.string("")
|
||||
return
|
||||
}
|
||||
|
||||
name := obj.Name()
|
||||
if name == "_" {
|
||||
w.string("_")
|
||||
return
|
||||
}
|
||||
|
||||
w.string(name)
|
||||
}
|
||||
|
||||
type intWriter struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (w *intWriter) int64(x int64) {
|
||||
var buf [binary.MaxVarintLen64]byte
|
||||
n := binary.PutVarint(buf[:], x)
|
||||
w.Write(buf[:n])
|
||||
}
|
||||
|
||||
func (w *intWriter) uint64(x uint64) {
|
||||
var buf [binary.MaxVarintLen64]byte
|
||||
n := binary.PutUvarint(buf[:], x)
|
||||
w.Write(buf[:n])
|
||||
}
|
||||
|
||||
func assert(cond bool) {
|
||||
if !cond {
|
||||
panic("internal error: assertion failed")
|
||||
}
|
||||
}
|
||||
|
||||
// The below is copied from go/src/cmd/compile/internal/gc/syntax.go.
|
||||
|
||||
// objQueue is a FIFO queue of types.Object. The zero value of objQueue is
|
||||
// a ready-to-use empty queue.
|
||||
type objQueue struct {
|
||||
ring []types.Object
|
||||
head, tail int
|
||||
}
|
||||
|
||||
// empty returns true if q contains no Nodes.
|
||||
func (q *objQueue) empty() bool {
|
||||
return q.head == q.tail
|
||||
}
|
||||
|
||||
// pushTail appends n to the tail of the queue.
|
||||
func (q *objQueue) pushTail(obj types.Object) {
|
||||
if len(q.ring) == 0 {
|
||||
q.ring = make([]types.Object, 16)
|
||||
} else if q.head+len(q.ring) == q.tail {
|
||||
// Grow the ring.
|
||||
nring := make([]types.Object, len(q.ring)*2)
|
||||
// Copy the old elements.
|
||||
part := q.ring[q.head%len(q.ring):]
|
||||
if q.tail-q.head <= len(part) {
|
||||
part = part[:q.tail-q.head]
|
||||
copy(nring, part)
|
||||
} else {
|
||||
pos := copy(nring, part)
|
||||
copy(nring[pos:], q.ring[:q.tail%len(q.ring)])
|
||||
}
|
||||
q.ring, q.head, q.tail = nring, 0, q.tail-q.head
|
||||
}
|
||||
|
||||
q.ring[q.tail%len(q.ring)] = obj
|
||||
q.tail++
|
||||
}
|
||||
|
||||
// popHead pops a node from the head of the queue. It panics if q is empty.
|
||||
func (q *objQueue) popHead() types.Object {
|
||||
if q.empty() {
|
||||
panic("dequeue empty")
|
||||
}
|
||||
obj := q.ring[q.head%len(q.ring)]
|
||||
q.head++
|
||||
return obj
|
||||
}
|
||||
+630
@@ -0,0 +1,630 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Indexed package import.
|
||||
// See cmd/compile/internal/gc/iexport.go for the export data format.
|
||||
|
||||
// This file is a copy of $GOROOT/src/go/internal/gcimporter/iimport.go.
|
||||
|
||||
package gcimporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type intReader struct {
|
||||
*bytes.Reader
|
||||
path string
|
||||
}
|
||||
|
||||
func (r *intReader) int64() int64 {
|
||||
i, err := binary.ReadVarint(r.Reader)
|
||||
if err != nil {
|
||||
errorf("import %q: read varint error: %v", r.path, err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (r *intReader) uint64() uint64 {
|
||||
i, err := binary.ReadUvarint(r.Reader)
|
||||
if err != nil {
|
||||
errorf("import %q: read varint error: %v", r.path, err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
const predeclReserved = 32
|
||||
|
||||
type itag uint64
|
||||
|
||||
const (
|
||||
// Types
|
||||
definedType itag = iota
|
||||
pointerType
|
||||
sliceType
|
||||
arrayType
|
||||
chanType
|
||||
mapType
|
||||
signatureType
|
||||
structType
|
||||
interfaceType
|
||||
)
|
||||
|
||||
// IImportData imports a package from the serialized package data
|
||||
// and returns the number of bytes consumed and a reference to the package.
|
||||
// If the export data version is not recognized or the format is otherwise
|
||||
// compromised, an error is returned.
|
||||
func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) {
|
||||
const currentVersion = 1
|
||||
version := int64(-1)
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
if version > currentVersion {
|
||||
err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e)
|
||||
} else {
|
||||
err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
r := &intReader{bytes.NewReader(data), path}
|
||||
|
||||
version = int64(r.uint64())
|
||||
switch version {
|
||||
case currentVersion, 0:
|
||||
default:
|
||||
errorf("unknown iexport format version %d", version)
|
||||
}
|
||||
|
||||
sLen := int64(r.uint64())
|
||||
dLen := int64(r.uint64())
|
||||
|
||||
whence, _ := r.Seek(0, io.SeekCurrent)
|
||||
stringData := data[whence : whence+sLen]
|
||||
declData := data[whence+sLen : whence+sLen+dLen]
|
||||
r.Seek(sLen+dLen, io.SeekCurrent)
|
||||
|
||||
p := iimporter{
|
||||
ipath: path,
|
||||
version: int(version),
|
||||
|
||||
stringData: stringData,
|
||||
stringCache: make(map[uint64]string),
|
||||
pkgCache: make(map[uint64]*types.Package),
|
||||
|
||||
declData: declData,
|
||||
pkgIndex: make(map[*types.Package]map[string]uint64),
|
||||
typCache: make(map[uint64]types.Type),
|
||||
|
||||
fake: fakeFileSet{
|
||||
fset: fset,
|
||||
files: make(map[string]*token.File),
|
||||
},
|
||||
}
|
||||
|
||||
for i, pt := range predeclared() {
|
||||
p.typCache[uint64(i)] = pt
|
||||
}
|
||||
|
||||
pkgList := make([]*types.Package, r.uint64())
|
||||
for i := range pkgList {
|
||||
pkgPathOff := r.uint64()
|
||||
pkgPath := p.stringAt(pkgPathOff)
|
||||
pkgName := p.stringAt(r.uint64())
|
||||
_ = r.uint64() // package height; unused by go/types
|
||||
|
||||
if pkgPath == "" {
|
||||
pkgPath = path
|
||||
}
|
||||
pkg := imports[pkgPath]
|
||||
if pkg == nil {
|
||||
pkg = types.NewPackage(pkgPath, pkgName)
|
||||
imports[pkgPath] = pkg
|
||||
} else if pkg.Name() != pkgName {
|
||||
errorf("conflicting names %s and %s for package %q", pkg.Name(), pkgName, path)
|
||||
}
|
||||
|
||||
p.pkgCache[pkgPathOff] = pkg
|
||||
|
||||
nameIndex := make(map[string]uint64)
|
||||
for nSyms := r.uint64(); nSyms > 0; nSyms-- {
|
||||
name := p.stringAt(r.uint64())
|
||||
nameIndex[name] = r.uint64()
|
||||
}
|
||||
|
||||
p.pkgIndex[pkg] = nameIndex
|
||||
pkgList[i] = pkg
|
||||
}
|
||||
if len(pkgList) == 0 {
|
||||
errorf("no packages found for %s", path)
|
||||
panic("unreachable")
|
||||
}
|
||||
p.ipkg = pkgList[0]
|
||||
names := make([]string, 0, len(p.pkgIndex[p.ipkg]))
|
||||
for name := range p.pkgIndex[p.ipkg] {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
p.doDecl(p.ipkg, name)
|
||||
}
|
||||
|
||||
for _, typ := range p.interfaceList {
|
||||
typ.Complete()
|
||||
}
|
||||
|
||||
// record all referenced packages as imports
|
||||
list := append(([]*types.Package)(nil), pkgList[1:]...)
|
||||
sort.Sort(byPath(list))
|
||||
p.ipkg.SetImports(list)
|
||||
|
||||
// package was imported completely and without errors
|
||||
p.ipkg.MarkComplete()
|
||||
|
||||
consumed, _ := r.Seek(0, io.SeekCurrent)
|
||||
return int(consumed), p.ipkg, nil
|
||||
}
|
||||
|
||||
type iimporter struct {
|
||||
ipath string
|
||||
ipkg *types.Package
|
||||
version int
|
||||
|
||||
stringData []byte
|
||||
stringCache map[uint64]string
|
||||
pkgCache map[uint64]*types.Package
|
||||
|
||||
declData []byte
|
||||
pkgIndex map[*types.Package]map[string]uint64
|
||||
typCache map[uint64]types.Type
|
||||
|
||||
fake fakeFileSet
|
||||
interfaceList []*types.Interface
|
||||
}
|
||||
|
||||
func (p *iimporter) doDecl(pkg *types.Package, name string) {
|
||||
// See if we've already imported this declaration.
|
||||
if obj := pkg.Scope().Lookup(name); obj != nil {
|
||||
return
|
||||
}
|
||||
|
||||
off, ok := p.pkgIndex[pkg][name]
|
||||
if !ok {
|
||||
errorf("%v.%v not in index", pkg, name)
|
||||
}
|
||||
|
||||
r := &importReader{p: p, currPkg: pkg}
|
||||
r.declReader.Reset(p.declData[off:])
|
||||
|
||||
r.obj(name)
|
||||
}
|
||||
|
||||
func (p *iimporter) stringAt(off uint64) string {
|
||||
if s, ok := p.stringCache[off]; ok {
|
||||
return s
|
||||
}
|
||||
|
||||
slen, n := binary.Uvarint(p.stringData[off:])
|
||||
if n <= 0 {
|
||||
errorf("varint failed")
|
||||
}
|
||||
spos := off + uint64(n)
|
||||
s := string(p.stringData[spos : spos+slen])
|
||||
p.stringCache[off] = s
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *iimporter) pkgAt(off uint64) *types.Package {
|
||||
if pkg, ok := p.pkgCache[off]; ok {
|
||||
return pkg
|
||||
}
|
||||
path := p.stringAt(off)
|
||||
if path == p.ipath {
|
||||
return p.ipkg
|
||||
}
|
||||
errorf("missing package %q in %q", path, p.ipath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *iimporter) typAt(off uint64, base *types.Named) types.Type {
|
||||
if t, ok := p.typCache[off]; ok && (base == nil || !isInterface(t)) {
|
||||
return t
|
||||
}
|
||||
|
||||
if off < predeclReserved {
|
||||
errorf("predeclared type missing from cache: %v", off)
|
||||
}
|
||||
|
||||
r := &importReader{p: p}
|
||||
r.declReader.Reset(p.declData[off-predeclReserved:])
|
||||
t := r.doType(base)
|
||||
|
||||
if base == nil || !isInterface(t) {
|
||||
p.typCache[off] = t
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
type importReader struct {
|
||||
p *iimporter
|
||||
declReader bytes.Reader
|
||||
currPkg *types.Package
|
||||
prevFile string
|
||||
prevLine int64
|
||||
prevColumn int64
|
||||
}
|
||||
|
||||
func (r *importReader) obj(name string) {
|
||||
tag := r.byte()
|
||||
pos := r.pos()
|
||||
|
||||
switch tag {
|
||||
case 'A':
|
||||
typ := r.typ()
|
||||
|
||||
r.declare(types.NewTypeName(pos, r.currPkg, name, typ))
|
||||
|
||||
case 'C':
|
||||
typ, val := r.value()
|
||||
|
||||
r.declare(types.NewConst(pos, r.currPkg, name, typ, val))
|
||||
|
||||
case 'F':
|
||||
sig := r.signature(nil)
|
||||
|
||||
r.declare(types.NewFunc(pos, r.currPkg, name, sig))
|
||||
|
||||
case 'T':
|
||||
// Types can be recursive. We need to setup a stub
|
||||
// declaration before recursing.
|
||||
obj := types.NewTypeName(pos, r.currPkg, name, nil)
|
||||
named := types.NewNamed(obj, nil, nil)
|
||||
r.declare(obj)
|
||||
|
||||
underlying := r.p.typAt(r.uint64(), named).Underlying()
|
||||
named.SetUnderlying(underlying)
|
||||
|
||||
if !isInterface(underlying) {
|
||||
for n := r.uint64(); n > 0; n-- {
|
||||
mpos := r.pos()
|
||||
mname := r.ident()
|
||||
recv := r.param()
|
||||
msig := r.signature(recv)
|
||||
|
||||
named.AddMethod(types.NewFunc(mpos, r.currPkg, mname, msig))
|
||||
}
|
||||
}
|
||||
|
||||
case 'V':
|
||||
typ := r.typ()
|
||||
|
||||
r.declare(types.NewVar(pos, r.currPkg, name, typ))
|
||||
|
||||
default:
|
||||
errorf("unexpected tag: %v", tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *importReader) declare(obj types.Object) {
|
||||
obj.Pkg().Scope().Insert(obj)
|
||||
}
|
||||
|
||||
func (r *importReader) value() (typ types.Type, val constant.Value) {
|
||||
typ = r.typ()
|
||||
|
||||
switch b := typ.Underlying().(*types.Basic); b.Info() & types.IsConstType {
|
||||
case types.IsBoolean:
|
||||
val = constant.MakeBool(r.bool())
|
||||
|
||||
case types.IsString:
|
||||
val = constant.MakeString(r.string())
|
||||
|
||||
case types.IsInteger:
|
||||
val = r.mpint(b)
|
||||
|
||||
case types.IsFloat:
|
||||
val = r.mpfloat(b)
|
||||
|
||||
case types.IsComplex:
|
||||
re := r.mpfloat(b)
|
||||
im := r.mpfloat(b)
|
||||
val = constant.BinaryOp(re, token.ADD, constant.MakeImag(im))
|
||||
|
||||
default:
|
||||
if b.Kind() == types.Invalid {
|
||||
val = constant.MakeUnknown()
|
||||
return
|
||||
}
|
||||
errorf("unexpected type %v", typ) // panics
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func intSize(b *types.Basic) (signed bool, maxBytes uint) {
|
||||
if (b.Info() & types.IsUntyped) != 0 {
|
||||
return true, 64
|
||||
}
|
||||
|
||||
switch b.Kind() {
|
||||
case types.Float32, types.Complex64:
|
||||
return true, 3
|
||||
case types.Float64, types.Complex128:
|
||||
return true, 7
|
||||
}
|
||||
|
||||
signed = (b.Info() & types.IsUnsigned) == 0
|
||||
switch b.Kind() {
|
||||
case types.Int8, types.Uint8:
|
||||
maxBytes = 1
|
||||
case types.Int16, types.Uint16:
|
||||
maxBytes = 2
|
||||
case types.Int32, types.Uint32:
|
||||
maxBytes = 4
|
||||
default:
|
||||
maxBytes = 8
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *importReader) mpint(b *types.Basic) constant.Value {
|
||||
signed, maxBytes := intSize(b)
|
||||
|
||||
maxSmall := 256 - maxBytes
|
||||
if signed {
|
||||
maxSmall = 256 - 2*maxBytes
|
||||
}
|
||||
if maxBytes == 1 {
|
||||
maxSmall = 256
|
||||
}
|
||||
|
||||
n, _ := r.declReader.ReadByte()
|
||||
if uint(n) < maxSmall {
|
||||
v := int64(n)
|
||||
if signed {
|
||||
v >>= 1
|
||||
if n&1 != 0 {
|
||||
v = ^v
|
||||
}
|
||||
}
|
||||
return constant.MakeInt64(v)
|
||||
}
|
||||
|
||||
v := -n
|
||||
if signed {
|
||||
v = -(n &^ 1) >> 1
|
||||
}
|
||||
if v < 1 || uint(v) > maxBytes {
|
||||
errorf("weird decoding: %v, %v => %v", n, signed, v)
|
||||
}
|
||||
|
||||
buf := make([]byte, v)
|
||||
io.ReadFull(&r.declReader, buf)
|
||||
|
||||
// convert to little endian
|
||||
// TODO(gri) go/constant should have a more direct conversion function
|
||||
// (e.g., once it supports a big.Float based implementation)
|
||||
for i, j := 0, len(buf)-1; i < j; i, j = i+1, j-1 {
|
||||
buf[i], buf[j] = buf[j], buf[i]
|
||||
}
|
||||
|
||||
x := constant.MakeFromBytes(buf)
|
||||
if signed && n&1 != 0 {
|
||||
x = constant.UnaryOp(token.SUB, x, 0)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func (r *importReader) mpfloat(b *types.Basic) constant.Value {
|
||||
x := r.mpint(b)
|
||||
if constant.Sign(x) == 0 {
|
||||
return x
|
||||
}
|
||||
|
||||
exp := r.int64()
|
||||
switch {
|
||||
case exp > 0:
|
||||
x = constant.Shift(x, token.SHL, uint(exp))
|
||||
case exp < 0:
|
||||
d := constant.Shift(constant.MakeInt64(1), token.SHL, uint(-exp))
|
||||
x = constant.BinaryOp(x, token.QUO, d)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func (r *importReader) ident() string {
|
||||
return r.string()
|
||||
}
|
||||
|
||||
func (r *importReader) qualifiedIdent() (*types.Package, string) {
|
||||
name := r.string()
|
||||
pkg := r.pkg()
|
||||
return pkg, name
|
||||
}
|
||||
|
||||
func (r *importReader) pos() token.Pos {
|
||||
if r.p.version >= 1 {
|
||||
r.posv1()
|
||||
} else {
|
||||
r.posv0()
|
||||
}
|
||||
|
||||
if r.prevFile == "" && r.prevLine == 0 && r.prevColumn == 0 {
|
||||
return token.NoPos
|
||||
}
|
||||
return r.p.fake.pos(r.prevFile, int(r.prevLine), int(r.prevColumn))
|
||||
}
|
||||
|
||||
func (r *importReader) posv0() {
|
||||
delta := r.int64()
|
||||
if delta != deltaNewFile {
|
||||
r.prevLine += delta
|
||||
} else if l := r.int64(); l == -1 {
|
||||
r.prevLine += deltaNewFile
|
||||
} else {
|
||||
r.prevFile = r.string()
|
||||
r.prevLine = l
|
||||
}
|
||||
}
|
||||
|
||||
func (r *importReader) posv1() {
|
||||
delta := r.int64()
|
||||
r.prevColumn += delta >> 1
|
||||
if delta&1 != 0 {
|
||||
delta = r.int64()
|
||||
r.prevLine += delta >> 1
|
||||
if delta&1 != 0 {
|
||||
r.prevFile = r.string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *importReader) typ() types.Type {
|
||||
return r.p.typAt(r.uint64(), nil)
|
||||
}
|
||||
|
||||
func isInterface(t types.Type) bool {
|
||||
_, ok := t.(*types.Interface)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (r *importReader) pkg() *types.Package { return r.p.pkgAt(r.uint64()) }
|
||||
func (r *importReader) string() string { return r.p.stringAt(r.uint64()) }
|
||||
|
||||
func (r *importReader) doType(base *types.Named) types.Type {
|
||||
switch k := r.kind(); k {
|
||||
default:
|
||||
errorf("unexpected kind tag in %q: %v", r.p.ipath, k)
|
||||
return nil
|
||||
|
||||
case definedType:
|
||||
pkg, name := r.qualifiedIdent()
|
||||
r.p.doDecl(pkg, name)
|
||||
return pkg.Scope().Lookup(name).(*types.TypeName).Type()
|
||||
case pointerType:
|
||||
return types.NewPointer(r.typ())
|
||||
case sliceType:
|
||||
return types.NewSlice(r.typ())
|
||||
case arrayType:
|
||||
n := r.uint64()
|
||||
return types.NewArray(r.typ(), int64(n))
|
||||
case chanType:
|
||||
dir := chanDir(int(r.uint64()))
|
||||
return types.NewChan(dir, r.typ())
|
||||
case mapType:
|
||||
return types.NewMap(r.typ(), r.typ())
|
||||
case signatureType:
|
||||
r.currPkg = r.pkg()
|
||||
return r.signature(nil)
|
||||
|
||||
case structType:
|
||||
r.currPkg = r.pkg()
|
||||
|
||||
fields := make([]*types.Var, r.uint64())
|
||||
tags := make([]string, len(fields))
|
||||
for i := range fields {
|
||||
fpos := r.pos()
|
||||
fname := r.ident()
|
||||
ftyp := r.typ()
|
||||
emb := r.bool()
|
||||
tag := r.string()
|
||||
|
||||
fields[i] = types.NewField(fpos, r.currPkg, fname, ftyp, emb)
|
||||
tags[i] = tag
|
||||
}
|
||||
return types.NewStruct(fields, tags)
|
||||
|
||||
case interfaceType:
|
||||
r.currPkg = r.pkg()
|
||||
|
||||
embeddeds := make([]types.Type, r.uint64())
|
||||
for i := range embeddeds {
|
||||
_ = r.pos()
|
||||
embeddeds[i] = r.typ()
|
||||
}
|
||||
|
||||
methods := make([]*types.Func, r.uint64())
|
||||
for i := range methods {
|
||||
mpos := r.pos()
|
||||
mname := r.ident()
|
||||
|
||||
// TODO(mdempsky): Matches bimport.go, but I
|
||||
// don't agree with this.
|
||||
var recv *types.Var
|
||||
if base != nil {
|
||||
recv = types.NewVar(token.NoPos, r.currPkg, "", base)
|
||||
}
|
||||
|
||||
msig := r.signature(recv)
|
||||
methods[i] = types.NewFunc(mpos, r.currPkg, mname, msig)
|
||||
}
|
||||
|
||||
typ := newInterface(methods, embeddeds)
|
||||
r.p.interfaceList = append(r.p.interfaceList, typ)
|
||||
return typ
|
||||
}
|
||||
}
|
||||
|
||||
func (r *importReader) kind() itag {
|
||||
return itag(r.uint64())
|
||||
}
|
||||
|
||||
func (r *importReader) signature(recv *types.Var) *types.Signature {
|
||||
params := r.paramList()
|
||||
results := r.paramList()
|
||||
variadic := params.Len() > 0 && r.bool()
|
||||
return types.NewSignature(recv, params, results, variadic)
|
||||
}
|
||||
|
||||
func (r *importReader) paramList() *types.Tuple {
|
||||
xs := make([]*types.Var, r.uint64())
|
||||
for i := range xs {
|
||||
xs[i] = r.param()
|
||||
}
|
||||
return types.NewTuple(xs...)
|
||||
}
|
||||
|
||||
func (r *importReader) param() *types.Var {
|
||||
pos := r.pos()
|
||||
name := r.ident()
|
||||
typ := r.typ()
|
||||
return types.NewParam(pos, r.currPkg, name, typ)
|
||||
}
|
||||
|
||||
func (r *importReader) bool() bool {
|
||||
return r.uint64() != 0
|
||||
}
|
||||
|
||||
func (r *importReader) int64() int64 {
|
||||
n, err := binary.ReadVarint(&r.declReader)
|
||||
if err != nil {
|
||||
errorf("readVarint: %v", err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (r *importReader) uint64() uint64 {
|
||||
n, err := binary.ReadUvarint(&r.declReader)
|
||||
if err != nil {
|
||||
errorf("readUvarint: %v", err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (r *importReader) byte() byte {
|
||||
x, err := r.declReader.ReadByte()
|
||||
if err != nil {
|
||||
errorf("declReader.ReadByte: %v", err)
|
||||
}
|
||||
return x
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.11
|
||||
|
||||
package gcimporter
|
||||
|
||||
import "go/types"
|
||||
|
||||
func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface {
|
||||
named := make([]*types.Named, len(embeddeds))
|
||||
for i, e := range embeddeds {
|
||||
var ok bool
|
||||
named[i], ok = e.(*types.Named)
|
||||
if !ok {
|
||||
panic("embedding of non-defined interfaces in interfaces is not supported before Go 1.11")
|
||||
}
|
||||
}
|
||||
return types.NewInterface(methods, named)
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.11
|
||||
|
||||
package gcimporter
|
||||
|
||||
import "go/types"
|
||||
|
||||
func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface {
|
||||
return types.NewInterfaceType(methods, embeddeds)
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package packagesdriver fetches type sizes for go/packages and go/analysis.
|
||||
package packagesdriver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/types"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/internal/gocommand"
|
||||
)
|
||||
|
||||
var debug = false
|
||||
|
||||
func GetSizes(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) {
|
||||
// TODO(matloob): Clean this up. This code is mostly a copy of packages.findExternalDriver.
|
||||
const toolPrefix = "GOPACKAGESDRIVER="
|
||||
tool := ""
|
||||
for _, env := range env {
|
||||
if val := strings.TrimPrefix(env, toolPrefix); val != env {
|
||||
tool = val
|
||||
}
|
||||
}
|
||||
|
||||
if tool == "" {
|
||||
var err error
|
||||
tool, err = exec.LookPath("gopackagesdriver")
|
||||
if err != nil {
|
||||
// We did not find the driver, so use "go list".
|
||||
tool = "off"
|
||||
}
|
||||
}
|
||||
|
||||
if tool == "off" {
|
||||
return GetSizesGolist(ctx, buildFlags, env, gocmdRunner, dir)
|
||||
}
|
||||
|
||||
req, err := json.Marshal(struct {
|
||||
Command string `json:"command"`
|
||||
Env []string `json:"env"`
|
||||
BuildFlags []string `json:"build_flags"`
|
||||
}{
|
||||
Command: "sizes",
|
||||
Env: env,
|
||||
BuildFlags: buildFlags,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode message to driver tool: %v", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := exec.CommandContext(ctx, tool)
|
||||
cmd.Dir = dir
|
||||
cmd.Env = env
|
||||
cmd.Stdin = bytes.NewReader(req)
|
||||
cmd.Stdout = buf
|
||||
cmd.Stderr = new(bytes.Buffer)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr)
|
||||
}
|
||||
var response struct {
|
||||
// Sizes, if not nil, is the types.Sizes to use when type checking.
|
||||
Sizes *types.StdSizes
|
||||
}
|
||||
if err := json.Unmarshal(buf.Bytes(), &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.Sizes, nil
|
||||
}
|
||||
|
||||
func GetSizesGolist(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) {
|
||||
inv := gocommand.Invocation{
|
||||
Verb: "list",
|
||||
Args: []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"},
|
||||
Env: env,
|
||||
BuildFlags: buildFlags,
|
||||
WorkingDir: dir,
|
||||
}
|
||||
stdout, stderr, friendlyErr, rawErr := gocmdRunner.RunRaw(ctx, inv)
|
||||
var goarch, compiler string
|
||||
if rawErr != nil {
|
||||
if strings.Contains(rawErr.Error(), "cannot find main module") {
|
||||
// User's running outside of a module. All bets are off. Get GOARCH and guess compiler is gc.
|
||||
// TODO(matloob): Is this a problem in practice?
|
||||
inv := gocommand.Invocation{
|
||||
Verb: "env",
|
||||
Args: []string{"GOARCH"},
|
||||
Env: env,
|
||||
WorkingDir: dir,
|
||||
}
|
||||
envout, enverr := gocmdRunner.Run(ctx, inv)
|
||||
if enverr != nil {
|
||||
return nil, enverr
|
||||
}
|
||||
goarch = strings.TrimSpace(envout.String())
|
||||
compiler = "gc"
|
||||
} else {
|
||||
return nil, friendlyErr
|
||||
}
|
||||
} else {
|
||||
fields := strings.Fields(stdout.String())
|
||||
if len(fields) < 2 {
|
||||
return nil, fmt.Errorf("could not parse GOARCH and Go compiler in format \"<GOARCH> <compiler>\":\nstdout: <<%s>>\nstderr: <<%s>>",
|
||||
stdout.String(), stderr.String())
|
||||
}
|
||||
goarch = fields[0]
|
||||
compiler = fields[1]
|
||||
}
|
||||
return types.SizesFor(compiler, goarch), nil
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package packages loads Go packages for inspection and analysis.
|
||||
|
||||
The Load function takes as input a list of patterns and return a list of Package
|
||||
structs describing individual packages matched by those patterns.
|
||||
The LoadMode controls the amount of detail in the loaded packages.
|
||||
|
||||
Load passes most patterns directly to the underlying build tool,
|
||||
but all patterns with the prefix "query=", where query is a
|
||||
non-empty string of letters from [a-z], are reserved and may be
|
||||
interpreted as query operators.
|
||||
|
||||
Two query operators are currently supported: "file" and "pattern".
|
||||
|
||||
The query "file=path/to/file.go" matches the package or packages enclosing
|
||||
the Go source file path/to/file.go. For example "file=~/go/src/fmt/print.go"
|
||||
might return the packages "fmt" and "fmt [fmt.test]".
|
||||
|
||||
The query "pattern=string" causes "string" to be passed directly to
|
||||
the underlying build tool. In most cases this is unnecessary,
|
||||
but an application can use Load("pattern=" + x) as an escaping mechanism
|
||||
to ensure that x is not interpreted as a query operator if it contains '='.
|
||||
|
||||
All other query operators are reserved for future use and currently
|
||||
cause Load to report an error.
|
||||
|
||||
The Package struct provides basic information about the package, including
|
||||
|
||||
- ID, a unique identifier for the package in the returned set;
|
||||
- GoFiles, the names of the package's Go source files;
|
||||
- Imports, a map from source import strings to the Packages they name;
|
||||
- Types, the type information for the package's exported symbols;
|
||||
- Syntax, the parsed syntax trees for the package's source code; and
|
||||
- TypeInfo, the result of a complete type-check of the package syntax trees.
|
||||
|
||||
(See the documentation for type Package for the complete list of fields
|
||||
and more detailed descriptions.)
|
||||
|
||||
For example,
|
||||
|
||||
Load(nil, "bytes", "unicode...")
|
||||
|
||||
returns four Package structs describing the standard library packages
|
||||
bytes, unicode, unicode/utf16, and unicode/utf8. Note that one pattern
|
||||
can match multiple packages and that a package might be matched by
|
||||
multiple patterns: in general it is not possible to determine which
|
||||
packages correspond to which patterns.
|
||||
|
||||
Note that the list returned by Load contains only the packages matched
|
||||
by the patterns. Their dependencies can be found by walking the import
|
||||
graph using the Imports fields.
|
||||
|
||||
The Load function can be configured by passing a pointer to a Config as
|
||||
the first argument. A nil Config is equivalent to the zero Config, which
|
||||
causes Load to run in LoadFiles mode, collecting minimal information.
|
||||
See the documentation for type Config for details.
|
||||
|
||||
As noted earlier, the Config.Mode controls the amount of detail
|
||||
reported about the loaded packages. See the documentation for type LoadMode
|
||||
for details.
|
||||
|
||||
Most tools should pass their command-line arguments (after any flags)
|
||||
uninterpreted to the loader, so that the loader can interpret them
|
||||
according to the conventions of the underlying build system.
|
||||
See the Example function for typical usage.
|
||||
|
||||
*/
|
||||
package packages // import "golang.org/x/tools/go/packages"
|
||||
|
||||
/*
|
||||
|
||||
Motivation and design considerations
|
||||
|
||||
The new package's design solves problems addressed by two existing
|
||||
packages: go/build, which locates and describes packages, and
|
||||
golang.org/x/tools/go/loader, which loads, parses and type-checks them.
|
||||
The go/build.Package structure encodes too much of the 'go build' way
|
||||
of organizing projects, leaving us in need of a data type that describes a
|
||||
package of Go source code independent of the underlying build system.
|
||||
We wanted something that works equally well with go build and vgo, and
|
||||
also other build systems such as Bazel and Blaze, making it possible to
|
||||
construct analysis tools that work in all these environments.
|
||||
Tools such as errcheck and staticcheck were essentially unavailable to
|
||||
the Go community at Google, and some of Google's internal tools for Go
|
||||
are unavailable externally.
|
||||
This new package provides a uniform way to obtain package metadata by
|
||||
querying each of these build systems, optionally supporting their
|
||||
preferred command-line notations for packages, so that tools integrate
|
||||
neatly with users' build environments. The Metadata query function
|
||||
executes an external query tool appropriate to the current workspace.
|
||||
|
||||
Loading packages always returns the complete import graph "all the way down",
|
||||
even if all you want is information about a single package, because the query
|
||||
mechanisms of all the build systems we currently support ({go,vgo} list, and
|
||||
blaze/bazel aspect-based query) cannot provide detailed information
|
||||
about one package without visiting all its dependencies too, so there is
|
||||
no additional asymptotic cost to providing transitive information.
|
||||
(This property might not be true of a hypothetical 5th build system.)
|
||||
|
||||
In calls to TypeCheck, all initial packages, and any package that
|
||||
transitively depends on one of them, must be loaded from source.
|
||||
Consider A->B->C->D->E: if A,C are initial, A,B,C must be loaded from
|
||||
source; D may be loaded from export data, and E may not be loaded at all
|
||||
(though it's possible that D's export data mentions it, so a
|
||||
types.Package may be created for it and exposed.)
|
||||
|
||||
The old loader had a feature to suppress type-checking of function
|
||||
bodies on a per-package basis, primarily intended to reduce the work of
|
||||
obtaining type information for imported packages. Now that imports are
|
||||
satisfied by export data, the optimization no longer seems necessary.
|
||||
|
||||
Despite some early attempts, the old loader did not exploit export data,
|
||||
instead always using the equivalent of WholeProgram mode. This was due
|
||||
to the complexity of mixing source and export data packages (now
|
||||
resolved by the upward traversal mentioned above), and because export data
|
||||
files were nearly always missing or stale. Now that 'go build' supports
|
||||
caching, all the underlying build systems can guarantee to produce
|
||||
export data in a reasonable (amortized) time.
|
||||
|
||||
Test "main" packages synthesized by the build system are now reported as
|
||||
first-class packages, avoiding the need for clients (such as go/ssa) to
|
||||
reinvent this generation logic.
|
||||
|
||||
One way in which go/packages is simpler than the old loader is in its
|
||||
treatment of in-package tests. In-package tests are packages that
|
||||
consist of all the files of the library under test, plus the test files.
|
||||
The old loader constructed in-package tests by a two-phase process of
|
||||
mutation called "augmentation": first it would construct and type check
|
||||
all the ordinary library packages and type-check the packages that
|
||||
depend on them; then it would add more (test) files to the package and
|
||||
type-check again. This two-phase approach had four major problems:
|
||||
1) in processing the tests, the loader modified the library package,
|
||||
leaving no way for a client application to see both the test
|
||||
package and the library package; one would mutate into the other.
|
||||
2) because test files can declare additional methods on types defined in
|
||||
the library portion of the package, the dispatch of method calls in
|
||||
the library portion was affected by the presence of the test files.
|
||||
This should have been a clue that the packages were logically
|
||||
different.
|
||||
3) this model of "augmentation" assumed at most one in-package test
|
||||
per library package, which is true of projects using 'go build',
|
||||
but not other build systems.
|
||||
4) because of the two-phase nature of test processing, all packages that
|
||||
import the library package had to be processed before augmentation,
|
||||
forcing a "one-shot" API and preventing the client from calling Load
|
||||
in several times in sequence as is now possible in WholeProgram mode.
|
||||
(TypeCheck mode has a similar one-shot restriction for a different reason.)
|
||||
|
||||
Early drafts of this package supported "multi-shot" operation.
|
||||
Although it allowed clients to make a sequence of calls (or concurrent
|
||||
calls) to Load, building up the graph of Packages incrementally,
|
||||
it was of marginal value: it complicated the API
|
||||
(since it allowed some options to vary across calls but not others),
|
||||
it complicated the implementation,
|
||||
it cannot be made to work in Types mode, as explained above,
|
||||
and it was less efficient than making one combined call (when this is possible).
|
||||
Among the clients we have inspected, none made multiple calls to load
|
||||
but could not be easily and satisfactorily modified to make only a single call.
|
||||
However, applications changes may be required.
|
||||
For example, the ssadump command loads the user-specified packages
|
||||
and in addition the runtime package. It is tempting to simply append
|
||||
"runtime" to the user-provided list, but that does not work if the user
|
||||
specified an ad-hoc package such as [a.go b.go].
|
||||
Instead, ssadump no longer requests the runtime package,
|
||||
but seeks it among the dependencies of the user-specified packages,
|
||||
and emits an error if it is not found.
|
||||
|
||||
Overlays: The Overlay field in the Config allows providing alternate contents
|
||||
for Go source files, by providing a mapping from file path to contents.
|
||||
go/packages will pull in new imports added in overlay files when go/packages
|
||||
is run in LoadImports mode or greater.
|
||||
Overlay support for the go list driver isn't complete yet: if the file doesn't
|
||||
exist on disk, it will only be recognized in an overlay if it is a non-test file
|
||||
and the package would be reported even without the overlay.
|
||||
|
||||
Questions & Tasks
|
||||
|
||||
- Add GOARCH/GOOS?
|
||||
They are not portable concepts, but could be made portable.
|
||||
Our goal has been to allow users to express themselves using the conventions
|
||||
of the underlying build system: if the build system honors GOARCH
|
||||
during a build and during a metadata query, then so should
|
||||
applications built atop that query mechanism.
|
||||
Conversely, if the target architecture of the build is determined by
|
||||
command-line flags, the application can pass the relevant
|
||||
flags through to the build system using a command such as:
|
||||
myapp -query_flag="--cpu=amd64" -query_flag="--os=darwin"
|
||||
However, this approach is low-level, unwieldy, and non-portable.
|
||||
GOOS and GOARCH seem important enough to warrant a dedicated option.
|
||||
|
||||
- How should we handle partial failures such as a mixture of good and
|
||||
malformed patterns, existing and non-existent packages, successful and
|
||||
failed builds, import failures, import cycles, and so on, in a call to
|
||||
Load?
|
||||
|
||||
- Support bazel, blaze, and go1.10 list, not just go1.11 list.
|
||||
|
||||
- Handle (and test) various partial success cases, e.g.
|
||||
a mixture of good packages and:
|
||||
invalid patterns
|
||||
nonexistent packages
|
||||
empty packages
|
||||
packages with malformed package or import declarations
|
||||
unreadable files
|
||||
import cycles
|
||||
other parse errors
|
||||
type errors
|
||||
Make sure we record errors at the correct place in the graph.
|
||||
|
||||
- Missing packages among initial arguments are not reported.
|
||||
Return bogus packages for them, like golist does.
|
||||
|
||||
- "undeclared name" errors (for example) are reported out of source file
|
||||
order. I suspect this is due to the breadth-first resolution now used
|
||||
by go/types. Is that a bug? Discuss with gri.
|
||||
|
||||
*/
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file enables an external tool to intercept package requests.
|
||||
// If the tool is present then its results are used in preference to
|
||||
// the go list command.
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The Driver Protocol
|
||||
//
|
||||
// The driver, given the inputs to a call to Load, returns metadata about the packages specified.
|
||||
// This allows for different build systems to support go/packages by telling go/packages how the
|
||||
// packages' source is organized.
|
||||
// The driver is a binary, either specified by the GOPACKAGESDRIVER environment variable or in
|
||||
// the path as gopackagesdriver. It's given the inputs to load in its argv. See the package
|
||||
// documentation in doc.go for the full description of the patterns that need to be supported.
|
||||
// A driver receives as a JSON-serialized driverRequest struct in standard input and will
|
||||
// produce a JSON-serialized driverResponse (see definition in packages.go) in its standard output.
|
||||
|
||||
// driverRequest is used to provide the portion of Load's Config that is needed by a driver.
|
||||
type driverRequest struct {
|
||||
Mode LoadMode `json:"mode"`
|
||||
// Env specifies the environment the underlying build system should be run in.
|
||||
Env []string `json:"env"`
|
||||
// BuildFlags are flags that should be passed to the underlying build system.
|
||||
BuildFlags []string `json:"build_flags"`
|
||||
// Tests specifies whether the patterns should also return test packages.
|
||||
Tests bool `json:"tests"`
|
||||
// Overlay maps file paths (relative to the driver's working directory) to the byte contents
|
||||
// of overlay files.
|
||||
Overlay map[string][]byte `json:"overlay"`
|
||||
}
|
||||
|
||||
// findExternalDriver returns the file path of a tool that supplies
|
||||
// the build system package structure, or "" if not found."
|
||||
// If GOPACKAGESDRIVER is set in the environment findExternalTool returns its
|
||||
// value, otherwise it searches for a binary named gopackagesdriver on the PATH.
|
||||
func findExternalDriver(cfg *Config) driver {
|
||||
const toolPrefix = "GOPACKAGESDRIVER="
|
||||
tool := ""
|
||||
for _, env := range cfg.Env {
|
||||
if val := strings.TrimPrefix(env, toolPrefix); val != env {
|
||||
tool = val
|
||||
}
|
||||
}
|
||||
if tool != "" && tool == "off" {
|
||||
return nil
|
||||
}
|
||||
if tool == "" {
|
||||
var err error
|
||||
tool, err = exec.LookPath("gopackagesdriver")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return func(cfg *Config, words ...string) (*driverResponse, error) {
|
||||
req, err := json.Marshal(driverRequest{
|
||||
Mode: cfg.Mode,
|
||||
Env: cfg.Env,
|
||||
BuildFlags: cfg.BuildFlags,
|
||||
Tests: cfg.Tests,
|
||||
Overlay: cfg.Overlay,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode message to driver tool: %v", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
cmd := exec.CommandContext(cfg.Context, tool, words...)
|
||||
cmd.Dir = cfg.Dir
|
||||
cmd.Env = cfg.Env
|
||||
cmd.Stdin = bytes.NewReader(req)
|
||||
cmd.Stdout = buf
|
||||
cmd.Stderr = stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr)
|
||||
}
|
||||
if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTDRIVERERRORS") != "" {
|
||||
fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, words...), stderr)
|
||||
}
|
||||
|
||||
var response driverResponse
|
||||
if err := json.Unmarshal(buf.Bytes(), &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
}
|
||||
+996
@@ -0,0 +1,996 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/types"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/tools/go/internal/packagesdriver"
|
||||
"golang.org/x/tools/internal/gocommand"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// debug controls verbose logging.
|
||||
var debug, _ = strconv.ParseBool(os.Getenv("GOPACKAGESDEBUG"))
|
||||
|
||||
// A goTooOldError reports that the go command
|
||||
// found by exec.LookPath is too old to use the new go list behavior.
|
||||
type goTooOldError struct {
|
||||
error
|
||||
}
|
||||
|
||||
// responseDeduper wraps a driverResponse, deduplicating its contents.
|
||||
type responseDeduper struct {
|
||||
seenRoots map[string]bool
|
||||
seenPackages map[string]*Package
|
||||
dr *driverResponse
|
||||
}
|
||||
|
||||
func newDeduper() *responseDeduper {
|
||||
return &responseDeduper{
|
||||
dr: &driverResponse{},
|
||||
seenRoots: map[string]bool{},
|
||||
seenPackages: map[string]*Package{},
|
||||
}
|
||||
}
|
||||
|
||||
// addAll fills in r with a driverResponse.
|
||||
func (r *responseDeduper) addAll(dr *driverResponse) {
|
||||
for _, pkg := range dr.Packages {
|
||||
r.addPackage(pkg)
|
||||
}
|
||||
for _, root := range dr.Roots {
|
||||
r.addRoot(root)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *responseDeduper) addPackage(p *Package) {
|
||||
if r.seenPackages[p.ID] != nil {
|
||||
return
|
||||
}
|
||||
r.seenPackages[p.ID] = p
|
||||
r.dr.Packages = append(r.dr.Packages, p)
|
||||
}
|
||||
|
||||
func (r *responseDeduper) addRoot(id string) {
|
||||
if r.seenRoots[id] {
|
||||
return
|
||||
}
|
||||
r.seenRoots[id] = true
|
||||
r.dr.Roots = append(r.dr.Roots, id)
|
||||
}
|
||||
|
||||
type golistState struct {
|
||||
cfg *Config
|
||||
ctx context.Context
|
||||
|
||||
envOnce sync.Once
|
||||
goEnvError error
|
||||
goEnv map[string]string
|
||||
|
||||
rootsOnce sync.Once
|
||||
rootDirsError error
|
||||
rootDirs map[string]string
|
||||
|
||||
goVersionOnce sync.Once
|
||||
goVersionError error
|
||||
goVersion string // third field of 'go version'
|
||||
|
||||
// vendorDirs caches the (non)existence of vendor directories.
|
||||
vendorDirs map[string]bool
|
||||
}
|
||||
|
||||
// getEnv returns Go environment variables. Only specific variables are
|
||||
// populated -- computing all of them is slow.
|
||||
func (state *golistState) getEnv() (map[string]string, error) {
|
||||
state.envOnce.Do(func() {
|
||||
var b *bytes.Buffer
|
||||
b, state.goEnvError = state.invokeGo("env", "-json", "GOMOD", "GOPATH")
|
||||
if state.goEnvError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
state.goEnv = make(map[string]string)
|
||||
decoder := json.NewDecoder(b)
|
||||
if state.goEnvError = decoder.Decode(&state.goEnv); state.goEnvError != nil {
|
||||
return
|
||||
}
|
||||
})
|
||||
return state.goEnv, state.goEnvError
|
||||
}
|
||||
|
||||
// mustGetEnv is a convenience function that can be used if getEnv has already succeeded.
|
||||
func (state *golistState) mustGetEnv() map[string]string {
|
||||
env, err := state.getEnv()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("mustGetEnv: %v", err))
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
// goListDriver uses the go list command to interpret the patterns and produce
|
||||
// the build system package structure.
|
||||
// See driver for more details.
|
||||
func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
|
||||
// Make sure that any asynchronous go commands are killed when we return.
|
||||
parentCtx := cfg.Context
|
||||
if parentCtx == nil {
|
||||
parentCtx = context.Background()
|
||||
}
|
||||
ctx, cancel := context.WithCancel(parentCtx)
|
||||
defer cancel()
|
||||
|
||||
response := newDeduper()
|
||||
|
||||
// Fill in response.Sizes asynchronously if necessary.
|
||||
var sizeserr error
|
||||
var sizeswg sync.WaitGroup
|
||||
if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 {
|
||||
sizeswg.Add(1)
|
||||
go func() {
|
||||
var sizes types.Sizes
|
||||
sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, cfg.BuildFlags, cfg.Env, cfg.gocmdRunner, cfg.Dir)
|
||||
// types.SizesFor always returns nil or a *types.StdSizes.
|
||||
response.dr.Sizes, _ = sizes.(*types.StdSizes)
|
||||
sizeswg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
state := &golistState{
|
||||
cfg: cfg,
|
||||
ctx: ctx,
|
||||
vendorDirs: map[string]bool{},
|
||||
}
|
||||
|
||||
// Determine files requested in contains patterns
|
||||
var containFiles []string
|
||||
restPatterns := make([]string, 0, len(patterns))
|
||||
// Extract file= and other [querytype]= patterns. Report an error if querytype
|
||||
// doesn't exist.
|
||||
extractQueries:
|
||||
for _, pattern := range patterns {
|
||||
eqidx := strings.Index(pattern, "=")
|
||||
if eqidx < 0 {
|
||||
restPatterns = append(restPatterns, pattern)
|
||||
} else {
|
||||
query, value := pattern[:eqidx], pattern[eqidx+len("="):]
|
||||
switch query {
|
||||
case "file":
|
||||
containFiles = append(containFiles, value)
|
||||
case "pattern":
|
||||
restPatterns = append(restPatterns, value)
|
||||
case "": // not a reserved query
|
||||
restPatterns = append(restPatterns, pattern)
|
||||
default:
|
||||
for _, rune := range query {
|
||||
if rune < 'a' || rune > 'z' { // not a reserved query
|
||||
restPatterns = append(restPatterns, pattern)
|
||||
continue extractQueries
|
||||
}
|
||||
}
|
||||
// Reject all other patterns containing "="
|
||||
return nil, fmt.Errorf("invalid query type %q in query pattern %q", query, pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See if we have any patterns to pass through to go list. Zero initial
|
||||
// patterns also requires a go list call, since it's the equivalent of
|
||||
// ".".
|
||||
if len(restPatterns) > 0 || len(patterns) == 0 {
|
||||
dr, err := state.createDriverResponse(restPatterns...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.addAll(dr)
|
||||
}
|
||||
|
||||
if len(containFiles) != 0 {
|
||||
if err := state.runContainsQueries(response, containFiles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
modifiedPkgs, needPkgs, err := state.processGolistOverlay(response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var containsCandidates []string
|
||||
if len(containFiles) > 0 {
|
||||
containsCandidates = append(containsCandidates, modifiedPkgs...)
|
||||
containsCandidates = append(containsCandidates, needPkgs...)
|
||||
}
|
||||
if err := state.addNeededOverlayPackages(response, needPkgs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Check candidate packages for containFiles.
|
||||
if len(containFiles) > 0 {
|
||||
for _, id := range containsCandidates {
|
||||
pkg, ok := response.seenPackages[id]
|
||||
if !ok {
|
||||
response.addPackage(&Package{
|
||||
ID: id,
|
||||
Errors: []Error{
|
||||
{
|
||||
Kind: ListError,
|
||||
Msg: fmt.Sprintf("package %s expected but not seen", id),
|
||||
},
|
||||
},
|
||||
})
|
||||
continue
|
||||
}
|
||||
for _, f := range containFiles {
|
||||
for _, g := range pkg.GoFiles {
|
||||
if sameFile(f, g) {
|
||||
response.addRoot(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sizeswg.Wait()
|
||||
if sizeserr != nil {
|
||||
return nil, sizeserr
|
||||
}
|
||||
return response.dr, nil
|
||||
}
|
||||
|
||||
func (state *golistState) addNeededOverlayPackages(response *responseDeduper, pkgs []string) error {
|
||||
if len(pkgs) == 0 {
|
||||
return nil
|
||||
}
|
||||
dr, err := state.createDriverResponse(pkgs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, pkg := range dr.Packages {
|
||||
response.addPackage(pkg)
|
||||
}
|
||||
_, needPkgs, err := state.processGolistOverlay(response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return state.addNeededOverlayPackages(response, needPkgs)
|
||||
}
|
||||
|
||||
func (state *golistState) runContainsQueries(response *responseDeduper, queries []string) error {
|
||||
for _, query := range queries {
|
||||
// TODO(matloob): Do only one query per directory.
|
||||
fdir := filepath.Dir(query)
|
||||
// Pass absolute path of directory to go list so that it knows to treat it as a directory,
|
||||
// not a package path.
|
||||
pattern, err := filepath.Abs(fdir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err)
|
||||
}
|
||||
dirResponse, err := state.createDriverResponse(pattern)
|
||||
|
||||
// If there was an error loading the package, or the package is returned
|
||||
// with errors, try to load the file as an ad-hoc package.
|
||||
// Usually the error will appear in a returned package, but may not if we're
|
||||
// in module mode and the ad-hoc is located outside a module.
|
||||
if err != nil || len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].GoFiles) == 0 &&
|
||||
len(dirResponse.Packages[0].Errors) == 1 {
|
||||
var queryErr error
|
||||
if dirResponse, queryErr = state.adhocPackage(pattern, query); queryErr != nil {
|
||||
return err // return the original error
|
||||
}
|
||||
}
|
||||
isRoot := make(map[string]bool, len(dirResponse.Roots))
|
||||
for _, root := range dirResponse.Roots {
|
||||
isRoot[root] = true
|
||||
}
|
||||
for _, pkg := range dirResponse.Packages {
|
||||
// Add any new packages to the main set
|
||||
// We don't bother to filter packages that will be dropped by the changes of roots,
|
||||
// that will happen anyway during graph construction outside this function.
|
||||
// Over-reporting packages is not a problem.
|
||||
response.addPackage(pkg)
|
||||
// if the package was not a root one, it cannot have the file
|
||||
if !isRoot[pkg.ID] {
|
||||
continue
|
||||
}
|
||||
for _, pkgFile := range pkg.GoFiles {
|
||||
if filepath.Base(query) == filepath.Base(pkgFile) {
|
||||
response.addRoot(pkg.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// adhocPackage attempts to load or construct an ad-hoc package for a given
|
||||
// query, if the original call to the driver produced inadequate results.
|
||||
func (state *golistState) adhocPackage(pattern, query string) (*driverResponse, error) {
|
||||
response, err := state.createDriverResponse(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If we get nothing back from `go list`,
|
||||
// try to make this file into its own ad-hoc package.
|
||||
// TODO(rstambler): Should this check against the original response?
|
||||
if len(response.Packages) == 0 {
|
||||
response.Packages = append(response.Packages, &Package{
|
||||
ID: "command-line-arguments",
|
||||
PkgPath: query,
|
||||
GoFiles: []string{query},
|
||||
CompiledGoFiles: []string{query},
|
||||
Imports: make(map[string]*Package),
|
||||
})
|
||||
response.Roots = append(response.Roots, "command-line-arguments")
|
||||
}
|
||||
// Handle special cases.
|
||||
if len(response.Packages) == 1 {
|
||||
// golang/go#33482: If this is a file= query for ad-hoc packages where
|
||||
// the file only exists on an overlay, and exists outside of a module,
|
||||
// add the file to the package and remove the errors.
|
||||
if response.Packages[0].ID == "command-line-arguments" ||
|
||||
filepath.ToSlash(response.Packages[0].PkgPath) == filepath.ToSlash(query) {
|
||||
if len(response.Packages[0].GoFiles) == 0 {
|
||||
filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath
|
||||
// TODO(matloob): check if the file is outside of a root dir?
|
||||
for path := range state.cfg.Overlay {
|
||||
if path == filename {
|
||||
response.Packages[0].Errors = nil
|
||||
response.Packages[0].GoFiles = []string{path}
|
||||
response.Packages[0].CompiledGoFiles = []string{path}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// Fields must match go list;
|
||||
// see $GOROOT/src/cmd/go/internal/load/pkg.go.
|
||||
type jsonPackage struct {
|
||||
ImportPath string
|
||||
Dir string
|
||||
Name string
|
||||
Export string
|
||||
GoFiles []string
|
||||
CompiledGoFiles []string
|
||||
CFiles []string
|
||||
CgoFiles []string
|
||||
CXXFiles []string
|
||||
MFiles []string
|
||||
HFiles []string
|
||||
FFiles []string
|
||||
SFiles []string
|
||||
SwigFiles []string
|
||||
SwigCXXFiles []string
|
||||
SysoFiles []string
|
||||
Imports []string
|
||||
ImportMap map[string]string
|
||||
Deps []string
|
||||
Module *Module
|
||||
TestGoFiles []string
|
||||
TestImports []string
|
||||
XTestGoFiles []string
|
||||
XTestImports []string
|
||||
ForTest string // q in a "p [q.test]" package, else ""
|
||||
DepOnly bool
|
||||
|
||||
Error *jsonPackageError
|
||||
}
|
||||
|
||||
type jsonPackageError struct {
|
||||
ImportStack []string
|
||||
Pos string
|
||||
Err string
|
||||
}
|
||||
|
||||
func otherFiles(p *jsonPackage) [][]string {
|
||||
return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles}
|
||||
}
|
||||
|
||||
// createDriverResponse uses the "go list" command to expand the pattern
|
||||
// words and return a response for the specified packages.
|
||||
func (state *golistState) createDriverResponse(words ...string) (*driverResponse, error) {
|
||||
// go list uses the following identifiers in ImportPath and Imports:
|
||||
//
|
||||
// "p" -- importable package or main (command)
|
||||
// "q.test" -- q's test executable
|
||||
// "p [q.test]" -- variant of p as built for q's test executable
|
||||
// "q_test [q.test]" -- q's external test package
|
||||
//
|
||||
// The packages p that are built differently for a test q.test
|
||||
// are q itself, plus any helpers used by the external test q_test,
|
||||
// typically including "testing" and all its dependencies.
|
||||
|
||||
// Run "go list" for complete
|
||||
// information on the specified packages.
|
||||
buf, err := state.invokeGo("list", golistargs(state.cfg, words)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seen := make(map[string]*jsonPackage)
|
||||
pkgs := make(map[string]*Package)
|
||||
additionalErrors := make(map[string][]Error)
|
||||
// Decode the JSON and convert it to Package form.
|
||||
var response driverResponse
|
||||
for dec := json.NewDecoder(buf); dec.More(); {
|
||||
p := new(jsonPackage)
|
||||
if err := dec.Decode(p); err != nil {
|
||||
return nil, fmt.Errorf("JSON decoding failed: %v", err)
|
||||
}
|
||||
|
||||
if p.ImportPath == "" {
|
||||
// The documentation for go list says that “[e]rroneous packages will have
|
||||
// a non-empty ImportPath”. If for some reason it comes back empty, we
|
||||
// prefer to error out rather than silently discarding data or handing
|
||||
// back a package without any way to refer to it.
|
||||
if p.Error != nil {
|
||||
return nil, Error{
|
||||
Pos: p.Error.Pos,
|
||||
Msg: p.Error.Err,
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("package missing import path: %+v", p)
|
||||
}
|
||||
|
||||
// Work around https://golang.org/issue/33157:
|
||||
// go list -e, when given an absolute path, will find the package contained at
|
||||
// that directory. But when no package exists there, it will return a fake package
|
||||
// with an error and the ImportPath set to the absolute path provided to go list.
|
||||
// Try to convert that absolute path to what its package path would be if it's
|
||||
// contained in a known module or GOPATH entry. This will allow the package to be
|
||||
// properly "reclaimed" when overlays are processed.
|
||||
if filepath.IsAbs(p.ImportPath) && p.Error != nil {
|
||||
pkgPath, ok, err := state.getPkgPath(p.ImportPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok {
|
||||
p.ImportPath = pkgPath
|
||||
}
|
||||
}
|
||||
|
||||
if old, found := seen[p.ImportPath]; found {
|
||||
// If one version of the package has an error, and the other doesn't, assume
|
||||
// that this is a case where go list is reporting a fake dependency variant
|
||||
// of the imported package: When a package tries to invalidly import another
|
||||
// package, go list emits a variant of the imported package (with the same
|
||||
// import path, but with an error on it, and the package will have a
|
||||
// DepError set on it). An example of when this can happen is for imports of
|
||||
// main packages: main packages can not be imported, but they may be
|
||||
// separately matched and listed by another pattern.
|
||||
// See golang.org/issue/36188 for more details.
|
||||
|
||||
// The plan is that eventually, hopefully in Go 1.15, the error will be
|
||||
// reported on the importing package rather than the duplicate "fake"
|
||||
// version of the imported package. Once all supported versions of Go
|
||||
// have the new behavior this logic can be deleted.
|
||||
// TODO(matloob): delete the workaround logic once all supported versions of
|
||||
// Go return the errors on the proper package.
|
||||
|
||||
// There should be exactly one version of a package that doesn't have an
|
||||
// error.
|
||||
if old.Error == nil && p.Error == nil {
|
||||
if !reflect.DeepEqual(p, old) {
|
||||
return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine if this package's error needs to be bubbled up.
|
||||
// This is a hack, and we expect for go list to eventually set the error
|
||||
// on the package.
|
||||
if old.Error != nil {
|
||||
var errkind string
|
||||
if strings.Contains(old.Error.Err, "not an importable package") {
|
||||
errkind = "not an importable package"
|
||||
} else if strings.Contains(old.Error.Err, "use of internal package") && strings.Contains(old.Error.Err, "not allowed") {
|
||||
errkind = "use of internal package not allowed"
|
||||
}
|
||||
if errkind != "" {
|
||||
if len(old.Error.ImportStack) < 1 {
|
||||
return nil, fmt.Errorf(`internal error: go list gave a %q error with empty import stack`, errkind)
|
||||
}
|
||||
importingPkg := old.Error.ImportStack[len(old.Error.ImportStack)-1]
|
||||
if importingPkg == old.ImportPath {
|
||||
// Using an older version of Go which put this package itself on top of import
|
||||
// stack, instead of the importer. Look for importer in second from top
|
||||
// position.
|
||||
if len(old.Error.ImportStack) < 2 {
|
||||
return nil, fmt.Errorf(`internal error: go list gave a %q error with an import stack without importing package`, errkind)
|
||||
}
|
||||
importingPkg = old.Error.ImportStack[len(old.Error.ImportStack)-2]
|
||||
}
|
||||
additionalErrors[importingPkg] = append(additionalErrors[importingPkg], Error{
|
||||
Pos: old.Error.Pos,
|
||||
Msg: old.Error.Err,
|
||||
Kind: ListError,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that if there's a version of the package without an error,
|
||||
// that's the one reported to the user.
|
||||
if old.Error == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// This package will replace the old one at the end of the loop.
|
||||
}
|
||||
seen[p.ImportPath] = p
|
||||
|
||||
pkg := &Package{
|
||||
Name: p.Name,
|
||||
ID: p.ImportPath,
|
||||
GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
|
||||
CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles),
|
||||
OtherFiles: absJoin(p.Dir, otherFiles(p)...),
|
||||
forTest: p.ForTest,
|
||||
Module: p.Module,
|
||||
}
|
||||
|
||||
if (state.cfg.Mode&typecheckCgo) != 0 && len(p.CgoFiles) != 0 {
|
||||
if len(p.CompiledGoFiles) > len(p.GoFiles) {
|
||||
// We need the cgo definitions, which are in the first
|
||||
// CompiledGoFile after the non-cgo ones. This is a hack but there
|
||||
// isn't currently a better way to find it. We also need the pure
|
||||
// Go files and unprocessed cgo files, all of which are already
|
||||
// in pkg.GoFiles.
|
||||
cgoTypes := p.CompiledGoFiles[len(p.GoFiles)]
|
||||
pkg.CompiledGoFiles = append([]string{cgoTypes}, pkg.GoFiles...)
|
||||
} else {
|
||||
// golang/go#38990: go list silently fails to do cgo processing
|
||||
pkg.CompiledGoFiles = nil
|
||||
pkg.Errors = append(pkg.Errors, Error{
|
||||
Msg: "go list failed to return CompiledGoFiles; https://golang.org/issue/38990?",
|
||||
Kind: ListError,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Work around https://golang.org/issue/28749:
|
||||
// cmd/go puts assembly, C, and C++ files in CompiledGoFiles.
|
||||
// Filter out any elements of CompiledGoFiles that are also in OtherFiles.
|
||||
// We have to keep this workaround in place until go1.12 is a distant memory.
|
||||
if len(pkg.OtherFiles) > 0 {
|
||||
other := make(map[string]bool, len(pkg.OtherFiles))
|
||||
for _, f := range pkg.OtherFiles {
|
||||
other[f] = true
|
||||
}
|
||||
|
||||
out := pkg.CompiledGoFiles[:0]
|
||||
for _, f := range pkg.CompiledGoFiles {
|
||||
if other[f] {
|
||||
continue
|
||||
}
|
||||
out = append(out, f)
|
||||
}
|
||||
pkg.CompiledGoFiles = out
|
||||
}
|
||||
|
||||
// Extract the PkgPath from the package's ID.
|
||||
if i := strings.IndexByte(pkg.ID, ' '); i >= 0 {
|
||||
pkg.PkgPath = pkg.ID[:i]
|
||||
} else {
|
||||
pkg.PkgPath = pkg.ID
|
||||
}
|
||||
|
||||
if pkg.PkgPath == "unsafe" {
|
||||
pkg.GoFiles = nil // ignore fake unsafe.go file
|
||||
}
|
||||
|
||||
// Assume go list emits only absolute paths for Dir.
|
||||
if p.Dir != "" && !filepath.IsAbs(p.Dir) {
|
||||
log.Fatalf("internal error: go list returned non-absolute Package.Dir: %s", p.Dir)
|
||||
}
|
||||
|
||||
if p.Export != "" && !filepath.IsAbs(p.Export) {
|
||||
pkg.ExportFile = filepath.Join(p.Dir, p.Export)
|
||||
} else {
|
||||
pkg.ExportFile = p.Export
|
||||
}
|
||||
|
||||
// imports
|
||||
//
|
||||
// Imports contains the IDs of all imported packages.
|
||||
// ImportsMap records (path, ID) only where they differ.
|
||||
ids := make(map[string]bool)
|
||||
for _, id := range p.Imports {
|
||||
ids[id] = true
|
||||
}
|
||||
pkg.Imports = make(map[string]*Package)
|
||||
for path, id := range p.ImportMap {
|
||||
pkg.Imports[path] = &Package{ID: id} // non-identity import
|
||||
delete(ids, id)
|
||||
}
|
||||
for id := range ids {
|
||||
if id == "C" {
|
||||
continue
|
||||
}
|
||||
|
||||
pkg.Imports[id] = &Package{ID: id} // identity import
|
||||
}
|
||||
if !p.DepOnly {
|
||||
response.Roots = append(response.Roots, pkg.ID)
|
||||
}
|
||||
|
||||
// Work around for pre-go.1.11 versions of go list.
|
||||
// TODO(matloob): they should be handled by the fallback.
|
||||
// Can we delete this?
|
||||
if len(pkg.CompiledGoFiles) == 0 {
|
||||
pkg.CompiledGoFiles = pkg.GoFiles
|
||||
}
|
||||
|
||||
// Temporary work-around for golang/go#39986. Parse filenames out of
|
||||
// error messages. This happens if there are unrecoverable syntax
|
||||
// errors in the source, so we can't match on a specific error message.
|
||||
if err := p.Error; err != nil && state.shouldAddFilenameFromError(p) {
|
||||
addFilenameFromPos := func(pos string) bool {
|
||||
split := strings.Split(pos, ":")
|
||||
if len(split) < 1 {
|
||||
return false
|
||||
}
|
||||
filename := strings.TrimSpace(split[0])
|
||||
if filename == "" {
|
||||
return false
|
||||
}
|
||||
if !filepath.IsAbs(filename) {
|
||||
filename = filepath.Join(state.cfg.Dir, filename)
|
||||
}
|
||||
info, _ := os.Stat(filename)
|
||||
if info == nil {
|
||||
return false
|
||||
}
|
||||
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, filename)
|
||||
pkg.GoFiles = append(pkg.GoFiles, filename)
|
||||
return true
|
||||
}
|
||||
found := addFilenameFromPos(err.Pos)
|
||||
// In some cases, go list only reports the error position in the
|
||||
// error text, not the error position. One such case is when the
|
||||
// file's package name is a keyword (see golang.org/issue/39763).
|
||||
if !found {
|
||||
addFilenameFromPos(err.Err)
|
||||
}
|
||||
}
|
||||
|
||||
if p.Error != nil {
|
||||
msg := strings.TrimSpace(p.Error.Err) // Trim to work around golang.org/issue/32363.
|
||||
// Address golang.org/issue/35964 by appending import stack to error message.
|
||||
if msg == "import cycle not allowed" && len(p.Error.ImportStack) != 0 {
|
||||
msg += fmt.Sprintf(": import stack: %v", p.Error.ImportStack)
|
||||
}
|
||||
pkg.Errors = append(pkg.Errors, Error{
|
||||
Pos: p.Error.Pos,
|
||||
Msg: msg,
|
||||
Kind: ListError,
|
||||
})
|
||||
}
|
||||
|
||||
pkgs[pkg.ID] = pkg
|
||||
}
|
||||
|
||||
for id, errs := range additionalErrors {
|
||||
if p, ok := pkgs[id]; ok {
|
||||
p.Errors = append(p.Errors, errs...)
|
||||
}
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
response.Packages = append(response.Packages, pkg)
|
||||
}
|
||||
sort.Slice(response.Packages, func(i, j int) bool { return response.Packages[i].ID < response.Packages[j].ID })
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (state *golistState) shouldAddFilenameFromError(p *jsonPackage) bool {
|
||||
if len(p.GoFiles) > 0 || len(p.CompiledGoFiles) > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
goV, err := state.getGoVersion()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// On Go 1.14 and earlier, only add filenames from errors if the import stack is empty.
|
||||
// The import stack behaves differently for these versions than newer Go versions.
|
||||
if strings.HasPrefix(goV, "go1.13") || strings.HasPrefix(goV, "go1.14") {
|
||||
return len(p.Error.ImportStack) == 0
|
||||
}
|
||||
|
||||
// On Go 1.15 and later, only parse filenames out of error if there's no import stack,
|
||||
// or the current package is at the top of the import stack. This is not guaranteed
|
||||
// to work perfectly, but should avoid some cases where files in errors don't belong to this
|
||||
// package.
|
||||
return len(p.Error.ImportStack) == 0 || p.Error.ImportStack[len(p.Error.ImportStack)-1] == p.ImportPath
|
||||
}
|
||||
|
||||
func (state *golistState) getGoVersion() (string, error) {
|
||||
state.goVersionOnce.Do(func() {
|
||||
var b *bytes.Buffer
|
||||
// Invoke go version. Don't use invokeGo because it will supply build flags, and
|
||||
// go version doesn't expect build flags.
|
||||
inv := gocommand.Invocation{
|
||||
Verb: "version",
|
||||
Env: state.cfg.Env,
|
||||
Logf: state.cfg.Logf,
|
||||
}
|
||||
gocmdRunner := state.cfg.gocmdRunner
|
||||
if gocmdRunner == nil {
|
||||
gocmdRunner = &gocommand.Runner{}
|
||||
}
|
||||
b, _, _, state.goVersionError = gocmdRunner.RunRaw(state.cfg.Context, inv)
|
||||
if state.goVersionError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sp := strings.Split(b.String(), " ")
|
||||
if len(sp) < 3 {
|
||||
state.goVersionError = fmt.Errorf("go version output: expected 'go version <version>', got '%s'", b.String())
|
||||
return
|
||||
}
|
||||
state.goVersion = sp[2]
|
||||
})
|
||||
return state.goVersion, state.goVersionError
|
||||
}
|
||||
|
||||
// getPkgPath finds the package path of a directory if it's relative to a root directory.
|
||||
func (state *golistState) getPkgPath(dir string) (string, bool, error) {
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
roots, err := state.determineRootDirs()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
for rdir, rpath := range roots {
|
||||
// Make sure that the directory is in the module,
|
||||
// to avoid creating a path relative to another module.
|
||||
if !strings.HasPrefix(absDir, rdir) {
|
||||
continue
|
||||
}
|
||||
// TODO(matloob): This doesn't properly handle symlinks.
|
||||
r, err := filepath.Rel(rdir, dir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if rpath != "" {
|
||||
// We choose only one root even though the directory even it can belong in multiple modules
|
||||
// or GOPATH entries. This is okay because we only need to work with absolute dirs when a
|
||||
// file is missing from disk, for instance when gopls calls go/packages in an overlay.
|
||||
// Once the file is saved, gopls, or the next invocation of the tool will get the correct
|
||||
// result straight from golist.
|
||||
// TODO(matloob): Implement module tiebreaking?
|
||||
return path.Join(rpath, filepath.ToSlash(r)), true, nil
|
||||
}
|
||||
return filepath.ToSlash(r), true, nil
|
||||
}
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
// absJoin absolutizes and flattens the lists of files.
|
||||
func absJoin(dir string, fileses ...[]string) (res []string) {
|
||||
for _, files := range fileses {
|
||||
for _, file := range files {
|
||||
if !filepath.IsAbs(file) {
|
||||
file = filepath.Join(dir, file)
|
||||
}
|
||||
res = append(res, file)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func golistargs(cfg *Config, words []string) []string {
|
||||
const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo
|
||||
fullargs := []string{
|
||||
"-e", "-json",
|
||||
fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypes|NeedTypesInfo|NeedTypesSizes) != 0),
|
||||
fmt.Sprintf("-test=%t", cfg.Tests),
|
||||
fmt.Sprintf("-export=%t", usesExportData(cfg)),
|
||||
fmt.Sprintf("-deps=%t", cfg.Mode&NeedImports != 0),
|
||||
// go list doesn't let you pass -test and -find together,
|
||||
// probably because you'd just get the TestMain.
|
||||
fmt.Sprintf("-find=%t", !cfg.Tests && cfg.Mode&findFlags == 0),
|
||||
}
|
||||
fullargs = append(fullargs, cfg.BuildFlags...)
|
||||
fullargs = append(fullargs, "--")
|
||||
fullargs = append(fullargs, words...)
|
||||
return fullargs
|
||||
}
|
||||
|
||||
// invokeGo returns the stdout of a go command invocation.
|
||||
func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) {
|
||||
cfg := state.cfg
|
||||
|
||||
inv := gocommand.Invocation{
|
||||
Verb: verb,
|
||||
Args: args,
|
||||
BuildFlags: cfg.BuildFlags,
|
||||
Env: cfg.Env,
|
||||
Logf: cfg.Logf,
|
||||
WorkingDir: cfg.Dir,
|
||||
}
|
||||
gocmdRunner := cfg.gocmdRunner
|
||||
if gocmdRunner == nil {
|
||||
gocmdRunner = &gocommand.Runner{}
|
||||
}
|
||||
stdout, stderr, _, err := gocmdRunner.RunRaw(cfg.Context, inv)
|
||||
if err != nil {
|
||||
// Check for 'go' executable not being found.
|
||||
if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
|
||||
return nil, fmt.Errorf("'go list' driver requires 'go', but %s", exec.ErrNotFound)
|
||||
}
|
||||
|
||||
exitErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
// Catastrophic error:
|
||||
// - context cancellation
|
||||
return nil, xerrors.Errorf("couldn't run 'go': %w", err)
|
||||
}
|
||||
|
||||
// Old go version?
|
||||
if strings.Contains(stderr.String(), "flag provided but not defined") {
|
||||
return nil, goTooOldError{fmt.Errorf("unsupported version of go: %s: %s", exitErr, stderr)}
|
||||
}
|
||||
|
||||
// Related to #24854
|
||||
if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "unexpected directory layout") {
|
||||
return nil, fmt.Errorf("%s", stderr.String())
|
||||
}
|
||||
|
||||
// Is there an error running the C compiler in cgo? This will be reported in the "Error" field
|
||||
// and should be suppressed by go list -e.
|
||||
//
|
||||
// This condition is not perfect yet because the error message can include other error messages than runtime/cgo.
|
||||
isPkgPathRune := func(r rune) bool {
|
||||
// From https://golang.org/ref/spec#Import_declarations:
|
||||
// Implementation restriction: A compiler may restrict ImportPaths to non-empty strings
|
||||
// using only characters belonging to Unicode's L, M, N, P, and S general categories
|
||||
// (the Graphic characters without spaces) and may also exclude the
|
||||
// characters !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character U+FFFD.
|
||||
return unicode.IsOneOf([]*unicode.RangeTable{unicode.L, unicode.M, unicode.N, unicode.P, unicode.S}, r) &&
|
||||
!strings.ContainsRune("!\"#$%&'()*,:;<=>?[\\]^`{|}\uFFFD", r)
|
||||
}
|
||||
if len(stderr.String()) > 0 && strings.HasPrefix(stderr.String(), "# ") {
|
||||
msg := stderr.String()[len("# "):]
|
||||
if strings.HasPrefix(strings.TrimLeftFunc(msg, isPkgPathRune), "\n") {
|
||||
return stdout, nil
|
||||
}
|
||||
// Treat pkg-config errors as a special case (golang.org/issue/36770).
|
||||
if strings.HasPrefix(msg, "pkg-config") {
|
||||
return stdout, nil
|
||||
}
|
||||
}
|
||||
|
||||
// This error only appears in stderr. See golang.org/cl/166398 for a fix in go list to show
|
||||
// the error in the Err section of stdout in case -e option is provided.
|
||||
// This fix is provided for backwards compatibility.
|
||||
if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must be .go files") {
|
||||
output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
|
||||
strings.Trim(stderr.String(), "\n"))
|
||||
return bytes.NewBufferString(output), nil
|
||||
}
|
||||
|
||||
// Similar to the previous error, but currently lacks a fix in Go.
|
||||
if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must all be in one directory") {
|
||||
output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
|
||||
strings.Trim(stderr.String(), "\n"))
|
||||
return bytes.NewBufferString(output), nil
|
||||
}
|
||||
|
||||
// Backwards compatibility for Go 1.11 because 1.12 and 1.13 put the directory in the ImportPath.
|
||||
// If the package doesn't exist, put the absolute path of the directory into the error message,
|
||||
// as Go 1.13 list does.
|
||||
const noSuchDirectory = "no such directory"
|
||||
if len(stderr.String()) > 0 && strings.Contains(stderr.String(), noSuchDirectory) {
|
||||
errstr := stderr.String()
|
||||
abspath := strings.TrimSpace(errstr[strings.Index(errstr, noSuchDirectory)+len(noSuchDirectory):])
|
||||
output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
|
||||
abspath, strings.Trim(stderr.String(), "\n"))
|
||||
return bytes.NewBufferString(output), nil
|
||||
}
|
||||
|
||||
// Workaround for #29280: go list -e has incorrect behavior when an ad-hoc package doesn't exist.
|
||||
// Note that the error message we look for in this case is different that the one looked for above.
|
||||
if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no such file or directory") {
|
||||
output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
|
||||
strings.Trim(stderr.String(), "\n"))
|
||||
return bytes.NewBufferString(output), nil
|
||||
}
|
||||
|
||||
// Workaround for #34273. go list -e with GO111MODULE=on has incorrect behavior when listing a
|
||||
// directory outside any module.
|
||||
if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "outside available modules") {
|
||||
output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
|
||||
// TODO(matloob): command-line-arguments isn't correct here.
|
||||
"command-line-arguments", strings.Trim(stderr.String(), "\n"))
|
||||
return bytes.NewBufferString(output), nil
|
||||
}
|
||||
|
||||
// Another variation of the previous error
|
||||
if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "outside module root") {
|
||||
output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
|
||||
// TODO(matloob): command-line-arguments isn't correct here.
|
||||
"command-line-arguments", strings.Trim(stderr.String(), "\n"))
|
||||
return bytes.NewBufferString(output), nil
|
||||
}
|
||||
|
||||
// Workaround for an instance of golang.org/issue/26755: go list -e will return a non-zero exit
|
||||
// status if there's a dependency on a package that doesn't exist. But it should return
|
||||
// a zero exit status and set an error on that package.
|
||||
if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no Go files in") {
|
||||
// Don't clobber stdout if `go list` actually returned something.
|
||||
if len(stdout.String()) > 0 {
|
||||
return stdout, nil
|
||||
}
|
||||
// try to extract package name from string
|
||||
stderrStr := stderr.String()
|
||||
var importPath string
|
||||
colon := strings.Index(stderrStr, ":")
|
||||
if colon > 0 && strings.HasPrefix(stderrStr, "go build ") {
|
||||
importPath = stderrStr[len("go build "):colon]
|
||||
}
|
||||
output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
|
||||
importPath, strings.Trim(stderrStr, "\n"))
|
||||
return bytes.NewBufferString(output), nil
|
||||
}
|
||||
|
||||
// Export mode entails a build.
|
||||
// If that build fails, errors appear on stderr
|
||||
// (despite the -e flag) and the Export field is blank.
|
||||
// Do not fail in that case.
|
||||
// The same is true if an ad-hoc package given to go list doesn't exist.
|
||||
// TODO(matloob): Remove these once we can depend on go list to exit with a zero status with -e even when
|
||||
// packages don't exist or a build fails.
|
||||
if !usesExportData(cfg) && !containsGoFile(args) {
|
||||
return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr)
|
||||
}
|
||||
}
|
||||
return stdout, nil
|
||||
}
|
||||
|
||||
func containsGoFile(s []string) bool {
|
||||
for _, f := range s {
|
||||
if strings.HasSuffix(f, ".go") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cmdDebugStr(cmd *exec.Cmd, args ...string) string {
|
||||
env := make(map[string]string)
|
||||
for _, kv := range cmd.Env {
|
||||
split := strings.Split(kv, "=")
|
||||
k, v := split[0], split[1]
|
||||
env[k] = v
|
||||
}
|
||||
var quotedArgs []string
|
||||
for _, arg := range args {
|
||||
quotedArgs = append(quotedArgs, strconv.Quote(arg))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %s", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], strings.Join(quotedArgs, " "))
|
||||
}
|
||||
+473
@@ -0,0 +1,473 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// processGolistOverlay provides rudimentary support for adding
|
||||
// files that don't exist on disk to an overlay. The results can be
|
||||
// sometimes incorrect.
|
||||
// TODO(matloob): Handle unsupported cases, including the following:
|
||||
// - determining the correct package to add given a new import path
|
||||
func (state *golistState) processGolistOverlay(response *responseDeduper) (modifiedPkgs, needPkgs []string, err error) {
|
||||
havePkgs := make(map[string]string) // importPath -> non-test package ID
|
||||
needPkgsSet := make(map[string]bool)
|
||||
modifiedPkgsSet := make(map[string]bool)
|
||||
|
||||
pkgOfDir := make(map[string][]*Package)
|
||||
for _, pkg := range response.dr.Packages {
|
||||
// This is an approximation of import path to id. This can be
|
||||
// wrong for tests, vendored packages, and a number of other cases.
|
||||
havePkgs[pkg.PkgPath] = pkg.ID
|
||||
x := commonDir(pkg.GoFiles)
|
||||
if x != "" {
|
||||
pkgOfDir[x] = append(pkgOfDir[x], pkg)
|
||||
}
|
||||
}
|
||||
|
||||
// If no new imports are added, it is safe to avoid loading any needPkgs.
|
||||
// Otherwise, it's hard to tell which package is actually being loaded
|
||||
// (due to vendoring) and whether any modified package will show up
|
||||
// in the transitive set of dependencies (because new imports are added,
|
||||
// potentially modifying the transitive set of dependencies).
|
||||
var overlayAddsImports bool
|
||||
|
||||
// If both a package and its test package are created by the overlay, we
|
||||
// need the real package first. Process all non-test files before test
|
||||
// files, and make the whole process deterministic while we're at it.
|
||||
var overlayFiles []string
|
||||
for opath := range state.cfg.Overlay {
|
||||
overlayFiles = append(overlayFiles, opath)
|
||||
}
|
||||
sort.Slice(overlayFiles, func(i, j int) bool {
|
||||
iTest := strings.HasSuffix(overlayFiles[i], "_test.go")
|
||||
jTest := strings.HasSuffix(overlayFiles[j], "_test.go")
|
||||
if iTest != jTest {
|
||||
return !iTest // non-tests are before tests.
|
||||
}
|
||||
return overlayFiles[i] < overlayFiles[j]
|
||||
})
|
||||
for _, opath := range overlayFiles {
|
||||
contents := state.cfg.Overlay[opath]
|
||||
base := filepath.Base(opath)
|
||||
dir := filepath.Dir(opath)
|
||||
var pkg *Package // if opath belongs to both a package and its test variant, this will be the test variant
|
||||
var testVariantOf *Package // if opath is a test file, this is the package it is testing
|
||||
var fileExists bool
|
||||
isTestFile := strings.HasSuffix(opath, "_test.go")
|
||||
pkgName, ok := extractPackageName(opath, contents)
|
||||
if !ok {
|
||||
// Don't bother adding a file that doesn't even have a parsable package statement
|
||||
// to the overlay.
|
||||
continue
|
||||
}
|
||||
// If all the overlay files belong to a different package, change the
|
||||
// package name to that package.
|
||||
maybeFixPackageName(pkgName, isTestFile, pkgOfDir[dir])
|
||||
nextPackage:
|
||||
for _, p := range response.dr.Packages {
|
||||
if pkgName != p.Name && p.ID != "command-line-arguments" {
|
||||
continue
|
||||
}
|
||||
for _, f := range p.GoFiles {
|
||||
if !sameFile(filepath.Dir(f), dir) {
|
||||
continue
|
||||
}
|
||||
// Make sure to capture information on the package's test variant, if needed.
|
||||
if isTestFile && !hasTestFiles(p) {
|
||||
// TODO(matloob): Are there packages other than the 'production' variant
|
||||
// of a package that this can match? This shouldn't match the test main package
|
||||
// because the file is generated in another directory.
|
||||
testVariantOf = p
|
||||
continue nextPackage
|
||||
}
|
||||
// We must have already seen the package of which this is a test variant.
|
||||
if pkg != nil && p != pkg && pkg.PkgPath == p.PkgPath {
|
||||
if hasTestFiles(p) {
|
||||
testVariantOf = pkg
|
||||
}
|
||||
}
|
||||
pkg = p
|
||||
if filepath.Base(f) == base {
|
||||
fileExists = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// The overlay could have included an entirely new package or an
|
||||
// ad-hoc package. An ad-hoc package is one that we have manually
|
||||
// constructed from inadequate `go list` results for a file= query.
|
||||
// It will have the ID command-line-arguments.
|
||||
if pkg == nil || pkg.ID == "command-line-arguments" {
|
||||
// Try to find the module or gopath dir the file is contained in.
|
||||
// Then for modules, add the module opath to the beginning.
|
||||
pkgPath, ok, err := state.getPkgPath(dir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
var forTest string // only set for x tests
|
||||
isXTest := strings.HasSuffix(pkgName, "_test")
|
||||
if isXTest {
|
||||
forTest = pkgPath
|
||||
pkgPath += "_test"
|
||||
}
|
||||
id := pkgPath
|
||||
if isTestFile {
|
||||
if isXTest {
|
||||
id = fmt.Sprintf("%s [%s.test]", pkgPath, forTest)
|
||||
} else {
|
||||
id = fmt.Sprintf("%s [%s.test]", pkgPath, pkgPath)
|
||||
}
|
||||
}
|
||||
if pkg != nil {
|
||||
// TODO(rstambler): We should change the package's path and ID
|
||||
// here. The only issue is that this messes with the roots.
|
||||
} else {
|
||||
// Try to reclaim a package with the same ID, if it exists in the response.
|
||||
for _, p := range response.dr.Packages {
|
||||
if reclaimPackage(p, id, opath, contents) {
|
||||
pkg = p
|
||||
break
|
||||
}
|
||||
}
|
||||
// Otherwise, create a new package.
|
||||
if pkg == nil {
|
||||
pkg = &Package{
|
||||
PkgPath: pkgPath,
|
||||
ID: id,
|
||||
Name: pkgName,
|
||||
Imports: make(map[string]*Package),
|
||||
}
|
||||
response.addPackage(pkg)
|
||||
havePkgs[pkg.PkgPath] = id
|
||||
// Add the production package's sources for a test variant.
|
||||
if isTestFile && !isXTest && testVariantOf != nil {
|
||||
pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...)
|
||||
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...)
|
||||
// Add the package under test and its imports to the test variant.
|
||||
pkg.forTest = testVariantOf.PkgPath
|
||||
for k, v := range testVariantOf.Imports {
|
||||
pkg.Imports[k] = &Package{ID: v.ID}
|
||||
}
|
||||
}
|
||||
if isXTest {
|
||||
pkg.forTest = forTest
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !fileExists {
|
||||
pkg.GoFiles = append(pkg.GoFiles, opath)
|
||||
// TODO(matloob): Adding the file to CompiledGoFiles can exhibit the wrong behavior
|
||||
// if the file will be ignored due to its build tags.
|
||||
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, opath)
|
||||
modifiedPkgsSet[pkg.ID] = true
|
||||
}
|
||||
imports, err := extractImports(opath, contents)
|
||||
if err != nil {
|
||||
// Let the parser or type checker report errors later.
|
||||
continue
|
||||
}
|
||||
for _, imp := range imports {
|
||||
// TODO(rstambler): If the package is an x test and the import has
|
||||
// a test variant, make sure to replace it.
|
||||
if _, found := pkg.Imports[imp]; found {
|
||||
continue
|
||||
}
|
||||
overlayAddsImports = true
|
||||
id, ok := havePkgs[imp]
|
||||
if !ok {
|
||||
var err error
|
||||
id, err = state.resolveImport(dir, imp)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
pkg.Imports[imp] = &Package{ID: id}
|
||||
// Add dependencies to the non-test variant version of this package as well.
|
||||
if testVariantOf != nil {
|
||||
testVariantOf.Imports[imp] = &Package{ID: id}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// toPkgPath guesses the package path given the id.
|
||||
toPkgPath := func(sourceDir, id string) (string, error) {
|
||||
if i := strings.IndexByte(id, ' '); i >= 0 {
|
||||
return state.resolveImport(sourceDir, id[:i])
|
||||
}
|
||||
return state.resolveImport(sourceDir, id)
|
||||
}
|
||||
|
||||
// Now that new packages have been created, do another pass to determine
|
||||
// the new set of missing packages.
|
||||
for _, pkg := range response.dr.Packages {
|
||||
for _, imp := range pkg.Imports {
|
||||
if len(pkg.GoFiles) == 0 {
|
||||
return nil, nil, fmt.Errorf("cannot resolve imports for package %q with no Go files", pkg.PkgPath)
|
||||
}
|
||||
pkgPath, err := toPkgPath(filepath.Dir(pkg.GoFiles[0]), imp.ID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, ok := havePkgs[pkgPath]; !ok {
|
||||
needPkgsSet[pkgPath] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if overlayAddsImports {
|
||||
needPkgs = make([]string, 0, len(needPkgsSet))
|
||||
for pkg := range needPkgsSet {
|
||||
needPkgs = append(needPkgs, pkg)
|
||||
}
|
||||
}
|
||||
modifiedPkgs = make([]string, 0, len(modifiedPkgsSet))
|
||||
for pkg := range modifiedPkgsSet {
|
||||
modifiedPkgs = append(modifiedPkgs, pkg)
|
||||
}
|
||||
return modifiedPkgs, needPkgs, err
|
||||
}
|
||||
|
||||
// resolveImport finds the the ID of a package given its import path.
|
||||
// In particular, it will find the right vendored copy when in GOPATH mode.
|
||||
func (state *golistState) resolveImport(sourceDir, importPath string) (string, error) {
|
||||
env, err := state.getEnv()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if env["GOMOD"] != "" {
|
||||
return importPath, nil
|
||||
}
|
||||
|
||||
searchDir := sourceDir
|
||||
for {
|
||||
vendorDir := filepath.Join(searchDir, "vendor")
|
||||
exists, ok := state.vendorDirs[vendorDir]
|
||||
if !ok {
|
||||
info, err := os.Stat(vendorDir)
|
||||
exists = err == nil && info.IsDir()
|
||||
state.vendorDirs[vendorDir] = exists
|
||||
}
|
||||
|
||||
if exists {
|
||||
vendoredPath := filepath.Join(vendorDir, importPath)
|
||||
if info, err := os.Stat(vendoredPath); err == nil && info.IsDir() {
|
||||
// We should probably check for .go files here, but shame on anyone who fools us.
|
||||
path, ok, err := state.getPkgPath(vendoredPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if ok {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We know we've hit the top of the filesystem when we Dir / and get /,
|
||||
// or C:\ and get C:\, etc.
|
||||
next := filepath.Dir(searchDir)
|
||||
if next == searchDir {
|
||||
break
|
||||
}
|
||||
searchDir = next
|
||||
}
|
||||
return importPath, nil
|
||||
}
|
||||
|
||||
func hasTestFiles(p *Package) bool {
|
||||
for _, f := range p.GoFiles {
|
||||
if strings.HasSuffix(f, "_test.go") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// determineRootDirs returns a mapping from absolute directories that could
|
||||
// contain code to their corresponding import path prefixes.
|
||||
func (state *golistState) determineRootDirs() (map[string]string, error) {
|
||||
env, err := state.getEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if env["GOMOD"] != "" {
|
||||
state.rootsOnce.Do(func() {
|
||||
state.rootDirs, state.rootDirsError = state.determineRootDirsModules()
|
||||
})
|
||||
} else {
|
||||
state.rootsOnce.Do(func() {
|
||||
state.rootDirs, state.rootDirsError = state.determineRootDirsGOPATH()
|
||||
})
|
||||
}
|
||||
return state.rootDirs, state.rootDirsError
|
||||
}
|
||||
|
||||
func (state *golistState) determineRootDirsModules() (map[string]string, error) {
|
||||
// This will only return the root directory for the main module.
|
||||
// For now we only support overlays in main modules.
|
||||
// Editing files in the module cache isn't a great idea, so we don't
|
||||
// plan to ever support that, but editing files in replaced modules
|
||||
// is something we may want to support. To do that, we'll want to
|
||||
// do a go list -m to determine the replaced module's module path and
|
||||
// directory, and then a go list -m {{with .Replace}}{{.Dir}}{{end}} <replaced module's path>
|
||||
// from the main module to determine if that module is actually a replacement.
|
||||
// See bcmills's comment here: https://github.com/golang/go/issues/37629#issuecomment-594179751
|
||||
// for more information.
|
||||
out, err := state.invokeGo("list", "-m", "-json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := map[string]string{}
|
||||
type jsonMod struct{ Path, Dir string }
|
||||
for dec := json.NewDecoder(out); dec.More(); {
|
||||
mod := new(jsonMod)
|
||||
if err := dec.Decode(mod); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mod.Dir != "" && mod.Path != "" {
|
||||
// This is a valid module; add it to the map.
|
||||
absDir, err := filepath.Abs(mod.Dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[absDir] = mod.Path
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (state *golistState) determineRootDirsGOPATH() (map[string]string, error) {
|
||||
m := map[string]string{}
|
||||
for _, dir := range filepath.SplitList(state.mustGetEnv()["GOPATH"]) {
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[filepath.Join(absDir, "src")] = ""
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func extractImports(filename string, contents []byte) ([]string, error) {
|
||||
f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.ImportsOnly) // TODO(matloob): reuse fileset?
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []string
|
||||
for _, imp := range f.Imports {
|
||||
quotedPath := imp.Path.Value
|
||||
path, err := strconv.Unquote(quotedPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, path)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// reclaimPackage attempts to reuse a package that failed to load in an overlay.
|
||||
//
|
||||
// If the package has errors and has no Name, GoFiles, or Imports,
|
||||
// then it's possible that it doesn't yet exist on disk.
|
||||
func reclaimPackage(pkg *Package, id string, filename string, contents []byte) bool {
|
||||
// TODO(rstambler): Check the message of the actual error?
|
||||
// It differs between $GOPATH and module mode.
|
||||
if pkg.ID != id {
|
||||
return false
|
||||
}
|
||||
if len(pkg.Errors) != 1 {
|
||||
return false
|
||||
}
|
||||
if pkg.Name != "" || pkg.ExportFile != "" {
|
||||
return false
|
||||
}
|
||||
if len(pkg.GoFiles) > 0 || len(pkg.CompiledGoFiles) > 0 || len(pkg.OtherFiles) > 0 {
|
||||
return false
|
||||
}
|
||||
if len(pkg.Imports) > 0 {
|
||||
return false
|
||||
}
|
||||
pkgName, ok := extractPackageName(filename, contents)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
pkg.Name = pkgName
|
||||
pkg.Errors = nil
|
||||
return true
|
||||
}
|
||||
|
||||
func extractPackageName(filename string, contents []byte) (string, bool) {
|
||||
// TODO(rstambler): Check the message of the actual error?
|
||||
// It differs between $GOPATH and module mode.
|
||||
f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.PackageClauseOnly) // TODO(matloob): reuse fileset?
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return f.Name.Name, true
|
||||
}
|
||||
|
||||
func commonDir(a []string) string {
|
||||
seen := make(map[string]bool)
|
||||
x := append([]string{}, a...)
|
||||
for _, f := range x {
|
||||
seen[filepath.Dir(f)] = true
|
||||
}
|
||||
if len(seen) > 1 {
|
||||
log.Fatalf("commonDir saw %v for %v", seen, x)
|
||||
}
|
||||
for k := range seen {
|
||||
// len(seen) == 1
|
||||
return k
|
||||
}
|
||||
return "" // no files
|
||||
}
|
||||
|
||||
// It is possible that the files in the disk directory dir have a different package
|
||||
// name from newName, which is deduced from the overlays. If they all have a different
|
||||
// package name, and they all have the same package name, then that name becomes
|
||||
// the package name.
|
||||
// It returns true if it changes the package name, false otherwise.
|
||||
func maybeFixPackageName(newName string, isTestFile bool, pkgsOfDir []*Package) {
|
||||
names := make(map[string]int)
|
||||
for _, p := range pkgsOfDir {
|
||||
names[p.Name]++
|
||||
}
|
||||
if len(names) != 1 {
|
||||
// some files are in different packages
|
||||
return
|
||||
}
|
||||
var oldName string
|
||||
for k := range names {
|
||||
oldName = k
|
||||
}
|
||||
if newName == oldName {
|
||||
return
|
||||
}
|
||||
// We might have a case where all of the package names in the directory are
|
||||
// the same, but the overlay file is for an x test, which belongs to its
|
||||
// own package. If the x test does not yet exist on disk, we may not yet
|
||||
// have its package name on disk, but we should not rename the packages.
|
||||
//
|
||||
// We use a heuristic to determine if this file belongs to an x test:
|
||||
// The test file should have a package name whose package name has a _test
|
||||
// suffix or looks like "newName_test".
|
||||
maybeXTest := strings.HasPrefix(oldName+"_test", newName) || strings.HasSuffix(newName, "_test")
|
||||
if isTestFile && maybeXTest {
|
||||
return
|
||||
}
|
||||
for _, p := range pkgsOfDir {
|
||||
p.Name = newName
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var allModes = []LoadMode{
|
||||
NeedName,
|
||||
NeedFiles,
|
||||
NeedCompiledGoFiles,
|
||||
NeedImports,
|
||||
NeedDeps,
|
||||
NeedExportsFile,
|
||||
NeedTypes,
|
||||
NeedSyntax,
|
||||
NeedTypesInfo,
|
||||
NeedTypesSizes,
|
||||
}
|
||||
|
||||
var modeStrings = []string{
|
||||
"NeedName",
|
||||
"NeedFiles",
|
||||
"NeedCompiledGoFiles",
|
||||
"NeedImports",
|
||||
"NeedDeps",
|
||||
"NeedExportsFile",
|
||||
"NeedTypes",
|
||||
"NeedSyntax",
|
||||
"NeedTypesInfo",
|
||||
"NeedTypesSizes",
|
||||
}
|
||||
|
||||
func (mod LoadMode) String() string {
|
||||
m := mod
|
||||
if m == 0 {
|
||||
return "LoadMode(0)"
|
||||
}
|
||||
var out []string
|
||||
for i, x := range allModes {
|
||||
if x > m {
|
||||
break
|
||||
}
|
||||
if (m & x) != 0 {
|
||||
out = append(out, modeStrings[i])
|
||||
m = m ^ x
|
||||
}
|
||||
}
|
||||
if m != 0 {
|
||||
out = append(out, "Unknown")
|
||||
}
|
||||
return fmt.Sprintf("LoadMode(%s)", strings.Join(out, "|"))
|
||||
}
|
||||
+1212
File diff suppressed because it is too large
Load Diff
+55
@@ -0,0 +1,55 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Visit visits all the packages in the import graph whose roots are
|
||||
// pkgs, calling the optional pre function the first time each package
|
||||
// is encountered (preorder), and the optional post function after a
|
||||
// package's dependencies have been visited (postorder).
|
||||
// The boolean result of pre(pkg) determines whether
|
||||
// the imports of package pkg are visited.
|
||||
func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) {
|
||||
seen := make(map[*Package]bool)
|
||||
var visit func(*Package)
|
||||
visit = func(pkg *Package) {
|
||||
if !seen[pkg] {
|
||||
seen[pkg] = true
|
||||
|
||||
if pre == nil || pre(pkg) {
|
||||
paths := make([]string, 0, len(pkg.Imports))
|
||||
for path := range pkg.Imports {
|
||||
paths = append(paths, path)
|
||||
}
|
||||
sort.Strings(paths) // Imports is a map, this makes visit stable
|
||||
for _, path := range paths {
|
||||
visit(pkg.Imports[path])
|
||||
}
|
||||
}
|
||||
|
||||
if post != nil {
|
||||
post(pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
visit(pkg)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintErrors prints to os.Stderr the accumulated errors of all
|
||||
// packages in the import graph rooted at pkgs, dependencies first.
|
||||
// PrintErrors returns the number of errors printed.
|
||||
func PrintErrors(pkgs []*Package) int {
|
||||
var n int
|
||||
Visit(pkgs, nil, func(pkg *Package) {
|
||||
for _, err := range pkg.Errors {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
n++
|
||||
}
|
||||
})
|
||||
return n
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package core provides support for event based telemetry.
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/internal/event/label"
|
||||
)
|
||||
|
||||
// Event holds the information about an event of note that ocurred.
|
||||
type Event struct {
|
||||
at time.Time
|
||||
|
||||
// As events are often on the stack, storing the first few labels directly
|
||||
// in the event can avoid an allocation at all for the very common cases of
|
||||
// simple events.
|
||||
// The length needs to be large enough to cope with the majority of events
|
||||
// but no so large as to cause undue stack pressure.
|
||||
// A log message with two values will use 3 labels (one for each value and
|
||||
// one for the message itself).
|
||||
|
||||
static [3]label.Label // inline storage for the first few labels
|
||||
dynamic []label.Label // dynamically sized storage for remaining labels
|
||||
}
|
||||
|
||||
// eventLabelMap implements label.Map for a the labels of an Event.
|
||||
type eventLabelMap struct {
|
||||
event Event
|
||||
}
|
||||
|
||||
func (ev Event) At() time.Time { return ev.at }
|
||||
|
||||
func (ev Event) Format(f fmt.State, r rune) {
|
||||
if !ev.at.IsZero() {
|
||||
fmt.Fprint(f, ev.at.Format("2006/01/02 15:04:05 "))
|
||||
}
|
||||
for index := 0; ev.Valid(index); index++ {
|
||||
if l := ev.Label(index); l.Valid() {
|
||||
fmt.Fprintf(f, "\n\t%v", l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ev Event) Valid(index int) bool {
|
||||
return index >= 0 && index < len(ev.static)+len(ev.dynamic)
|
||||
}
|
||||
|
||||
func (ev Event) Label(index int) label.Label {
|
||||
if index < len(ev.static) {
|
||||
return ev.static[index]
|
||||
}
|
||||
return ev.dynamic[index-len(ev.static)]
|
||||
}
|
||||
|
||||
func (ev Event) Find(key label.Key) label.Label {
|
||||
for _, l := range ev.static {
|
||||
if l.Key() == key {
|
||||
return l
|
||||
}
|
||||
}
|
||||
for _, l := range ev.dynamic {
|
||||
if l.Key() == key {
|
||||
return l
|
||||
}
|
||||
}
|
||||
return label.Label{}
|
||||
}
|
||||
|
||||
func MakeEvent(static [3]label.Label, labels []label.Label) Event {
|
||||
return Event{
|
||||
static: static,
|
||||
dynamic: labels,
|
||||
}
|
||||
}
|
||||
|
||||
// CloneEvent event returns a copy of the event with the time adjusted to at.
|
||||
func CloneEvent(ev Event, at time.Time) Event {
|
||||
ev.at = at
|
||||
return ev
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/tools/internal/event/label"
|
||||
)
|
||||
|
||||
// Exporter is a function that handles events.
|
||||
// It may return a modified context and event.
|
||||
type Exporter func(context.Context, Event, label.Map) context.Context
|
||||
|
||||
var (
|
||||
exporter unsafe.Pointer
|
||||
)
|
||||
|
||||
// SetExporter sets the global exporter function that handles all events.
|
||||
// The exporter is called synchronously from the event call site, so it should
|
||||
// return quickly so as not to hold up user code.
|
||||
func SetExporter(e Exporter) {
|
||||
p := unsafe.Pointer(&e)
|
||||
if e == nil {
|
||||
// &e is always valid, and so p is always valid, but for the early abort
|
||||
// of ProcessEvent to be efficient it needs to make the nil check on the
|
||||
// pointer without having to dereference it, so we make the nil function
|
||||
// also a nil pointer
|
||||
p = nil
|
||||
}
|
||||
atomic.StorePointer(&exporter, p)
|
||||
}
|
||||
|
||||
// deliver is called to deliver an event to the supplied exporter.
|
||||
// it will fill in the time.
|
||||
func deliver(ctx context.Context, exporter Exporter, ev Event) context.Context {
|
||||
// add the current time to the event
|
||||
ev.at = time.Now()
|
||||
// hand the event off to the current exporter
|
||||
return exporter(ctx, ev, ev)
|
||||
}
|
||||
|
||||
// Export is called to deliver an event to the global exporter if set.
|
||||
func Export(ctx context.Context, ev Event) context.Context {
|
||||
// get the global exporter and abort early if there is not one
|
||||
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
|
||||
if exporterPtr == nil {
|
||||
return ctx
|
||||
}
|
||||
return deliver(ctx, *exporterPtr, ev)
|
||||
}
|
||||
|
||||
// ExportPair is called to deliver a start event to the supplied exporter.
|
||||
// It also returns a function that will deliver the end event to the same
|
||||
// exporter.
|
||||
// It will fill in the time.
|
||||
func ExportPair(ctx context.Context, begin, end Event) (context.Context, func()) {
|
||||
// get the global exporter and abort early if there is not one
|
||||
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter))
|
||||
if exporterPtr == nil {
|
||||
return ctx, func() {}
|
||||
}
|
||||
ctx = deliver(ctx, *exporterPtr, begin)
|
||||
return ctx, func() { deliver(ctx, *exporterPtr, end) }
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/tools/internal/event/keys"
|
||||
"golang.org/x/tools/internal/event/label"
|
||||
)
|
||||
|
||||
// Log1 takes a message and one label delivers a log event to the exporter.
|
||||
// It is a customized version of Print that is faster and does no allocation.
|
||||
func Log1(ctx context.Context, message string, t1 label.Label) {
|
||||
Export(ctx, MakeEvent([3]label.Label{
|
||||
keys.Msg.Of(message),
|
||||
t1,
|
||||
}, nil))
|
||||
}
|
||||
|
||||
// Log2 takes a message and two labels and delivers a log event to the exporter.
|
||||
// It is a customized version of Print that is faster and does no allocation.
|
||||
func Log2(ctx context.Context, message string, t1 label.Label, t2 label.Label) {
|
||||
Export(ctx, MakeEvent([3]label.Label{
|
||||
keys.Msg.Of(message),
|
||||
t1,
|
||||
t2,
|
||||
}, nil))
|
||||
}
|
||||
|
||||
// Metric1 sends a label event to the exporter with the supplied labels.
|
||||
func Metric1(ctx context.Context, t1 label.Label) context.Context {
|
||||
return Export(ctx, MakeEvent([3]label.Label{
|
||||
keys.Metric.New(),
|
||||
t1,
|
||||
}, nil))
|
||||
}
|
||||
|
||||
// Metric2 sends a label event to the exporter with the supplied labels.
|
||||
func Metric2(ctx context.Context, t1, t2 label.Label) context.Context {
|
||||
return Export(ctx, MakeEvent([3]label.Label{
|
||||
keys.Metric.New(),
|
||||
t1,
|
||||
t2,
|
||||
}, nil))
|
||||
}
|
||||
|
||||
// Start1 sends a span start event with the supplied label list to the exporter.
|
||||
// It also returns a function that will end the span, which should normally be
|
||||
// deferred.
|
||||
func Start1(ctx context.Context, name string, t1 label.Label) (context.Context, func()) {
|
||||
return ExportPair(ctx,
|
||||
MakeEvent([3]label.Label{
|
||||
keys.Start.Of(name),
|
||||
t1,
|
||||
}, nil),
|
||||
MakeEvent([3]label.Label{
|
||||
keys.End.New(),
|
||||
}, nil))
|
||||
}
|
||||
|
||||
// Start2 sends a span start event with the supplied label list to the exporter.
|
||||
// It also returns a function that will end the span, which should normally be
|
||||
// deferred.
|
||||
func Start2(ctx context.Context, name string, t1, t2 label.Label) (context.Context, func()) {
|
||||
return ExportPair(ctx,
|
||||
MakeEvent([3]label.Label{
|
||||
keys.Start.Of(name),
|
||||
t1,
|
||||
t2,
|
||||
}, nil),
|
||||
MakeEvent([3]label.Label{
|
||||
keys.End.New(),
|
||||
}, nil))
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package event provides a set of packages that cover the main
|
||||
// concepts of telemetry in an implementation agnostic way.
|
||||
package event
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/tools/internal/event/core"
|
||||
"golang.org/x/tools/internal/event/keys"
|
||||
"golang.org/x/tools/internal/event/label"
|
||||
)
|
||||
|
||||
// Exporter is a function that handles events.
|
||||
// It may return a modified context and event.
|
||||
type Exporter func(context.Context, core.Event, label.Map) context.Context
|
||||
|
||||
// SetExporter sets the global exporter function that handles all events.
|
||||
// The exporter is called synchronously from the event call site, so it should
|
||||
// return quickly so as not to hold up user code.
|
||||
func SetExporter(e Exporter) {
|
||||
core.SetExporter(core.Exporter(e))
|
||||
}
|
||||
|
||||
// Log takes a message and a label list and combines them into a single event
|
||||
// before delivering them to the exporter.
|
||||
func Log(ctx context.Context, message string, labels ...label.Label) {
|
||||
core.Export(ctx, core.MakeEvent([3]label.Label{
|
||||
keys.Msg.Of(message),
|
||||
}, labels))
|
||||
}
|
||||
|
||||
// IsLog returns true if the event was built by the Log function.
|
||||
// It is intended to be used in exporters to identify the semantics of the
|
||||
// event when deciding what to do with it.
|
||||
func IsLog(ev core.Event) bool {
|
||||
return ev.Label(0).Key() == keys.Msg
|
||||
}
|
||||
|
||||
// Error takes a message and a label list and combines them into a single event
|
||||
// before delivering them to the exporter. It captures the error in the
|
||||
// delivered event.
|
||||
func Error(ctx context.Context, message string, err error, labels ...label.Label) {
|
||||
core.Export(ctx, core.MakeEvent([3]label.Label{
|
||||
keys.Msg.Of(message),
|
||||
keys.Err.Of(err),
|
||||
}, labels))
|
||||
}
|
||||
|
||||
// IsError returns true if the event was built by the Error function.
|
||||
// It is intended to be used in exporters to identify the semantics of the
|
||||
// event when deciding what to do with it.
|
||||
func IsError(ev core.Event) bool {
|
||||
return ev.Label(0).Key() == keys.Msg &&
|
||||
ev.Label(1).Key() == keys.Err
|
||||
}
|
||||
|
||||
// Metric sends a label event to the exporter with the supplied labels.
|
||||
func Metric(ctx context.Context, labels ...label.Label) {
|
||||
core.Export(ctx, core.MakeEvent([3]label.Label{
|
||||
keys.Metric.New(),
|
||||
}, labels))
|
||||
}
|
||||
|
||||
// IsMetric returns true if the event was built by the Metric function.
|
||||
// It is intended to be used in exporters to identify the semantics of the
|
||||
// event when deciding what to do with it.
|
||||
func IsMetric(ev core.Event) bool {
|
||||
return ev.Label(0).Key() == keys.Metric
|
||||
}
|
||||
|
||||
// Label sends a label event to the exporter with the supplied labels.
|
||||
func Label(ctx context.Context, labels ...label.Label) context.Context {
|
||||
return core.Export(ctx, core.MakeEvent([3]label.Label{
|
||||
keys.Label.New(),
|
||||
}, labels))
|
||||
}
|
||||
|
||||
// IsLabel returns true if the event was built by the Label function.
|
||||
// It is intended to be used in exporters to identify the semantics of the
|
||||
// event when deciding what to do with it.
|
||||
func IsLabel(ev core.Event) bool {
|
||||
return ev.Label(0).Key() == keys.Label
|
||||
}
|
||||
|
||||
// Start sends a span start event with the supplied label list to the exporter.
|
||||
// It also returns a function that will end the span, which should normally be
|
||||
// deferred.
|
||||
func Start(ctx context.Context, name string, labels ...label.Label) (context.Context, func()) {
|
||||
return core.ExportPair(ctx,
|
||||
core.MakeEvent([3]label.Label{
|
||||
keys.Start.Of(name),
|
||||
}, labels),
|
||||
core.MakeEvent([3]label.Label{
|
||||
keys.End.New(),
|
||||
}, nil))
|
||||
}
|
||||
|
||||
// IsStart returns true if the event was built by the Start function.
|
||||
// It is intended to be used in exporters to identify the semantics of the
|
||||
// event when deciding what to do with it.
|
||||
func IsStart(ev core.Event) bool {
|
||||
return ev.Label(0).Key() == keys.Start
|
||||
}
|
||||
|
||||
// IsEnd returns true if the event was built by the End function.
|
||||
// It is intended to be used in exporters to identify the semantics of the
|
||||
// event when deciding what to do with it.
|
||||
func IsEnd(ev core.Event) bool {
|
||||
return ev.Label(0).Key() == keys.End
|
||||
}
|
||||
|
||||
// Detach returns a context without an associated span.
|
||||
// This allows the creation of spans that are not children of the current span.
|
||||
func Detach(ctx context.Context) context.Context {
|
||||
return core.Export(ctx, core.MakeEvent([3]label.Label{
|
||||
keys.Detach.New(),
|
||||
}, nil))
|
||||
}
|
||||
|
||||
// IsDetach returns true if the event was built by the Detach function.
|
||||
// It is intended to be used in exporters to identify the semantics of the
|
||||
// event when deciding what to do with it.
|
||||
func IsDetach(ev core.Event) bool {
|
||||
return ev.Label(0).Key() == keys.Detach
|
||||
}
|
||||
+564
@@ -0,0 +1,564 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/internal/event/label"
|
||||
)
|
||||
|
||||
// Value represents a key for untyped values.
|
||||
type Value struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// New creates a new Key for untyped values.
|
||||
func New(name, description string) *Value {
|
||||
return &Value{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *Value) Name() string { return k.name }
|
||||
func (k *Value) Description() string { return k.description }
|
||||
|
||||
func (k *Value) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
fmt.Fprint(w, k.From(l))
|
||||
}
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *Value) Get(lm label.Map) interface{} {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *Value) From(t label.Label) interface{} { return t.UnpackValue() }
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *Value) Of(value interface{}) label.Label { return label.OfValue(k, value) }
|
||||
|
||||
// Tag represents a key for tagging labels that have no value.
|
||||
// These are used when the existence of the label is the entire information it
|
||||
// carries, such as marking events to be of a specific kind, or from a specific
|
||||
// package.
|
||||
type Tag struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewTag creates a new Key for tagging labels.
|
||||
func NewTag(name, description string) *Tag {
|
||||
return &Tag{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *Tag) Name() string { return k.name }
|
||||
func (k *Tag) Description() string { return k.description }
|
||||
|
||||
func (k *Tag) Format(w io.Writer, buf []byte, l label.Label) {}
|
||||
|
||||
// New creates a new Label with this key.
|
||||
func (k *Tag) New() label.Label { return label.OfValue(k, nil) }
|
||||
|
||||
// Int represents a key
|
||||
type Int struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewInt creates a new Key for int values.
|
||||
func NewInt(name, description string) *Int {
|
||||
return &Int{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *Int) Name() string { return k.name }
|
||||
func (k *Int) Description() string { return k.description }
|
||||
|
||||
func (k *Int) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10))
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *Int) Of(v int) label.Label { return label.Of64(k, uint64(v)) }
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *Int) Get(lm label.Map) int {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *Int) From(t label.Label) int { return int(t.Unpack64()) }
|
||||
|
||||
// Int8 represents a key
|
||||
type Int8 struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewInt8 creates a new Key for int8 values.
|
||||
func NewInt8(name, description string) *Int8 {
|
||||
return &Int8{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *Int8) Name() string { return k.name }
|
||||
func (k *Int8) Description() string { return k.description }
|
||||
|
||||
func (k *Int8) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10))
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *Int8) Of(v int8) label.Label { return label.Of64(k, uint64(v)) }
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *Int8) Get(lm label.Map) int8 {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *Int8) From(t label.Label) int8 { return int8(t.Unpack64()) }
|
||||
|
||||
// Int16 represents a key
|
||||
type Int16 struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewInt16 creates a new Key for int16 values.
|
||||
func NewInt16(name, description string) *Int16 {
|
||||
return &Int16{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *Int16) Name() string { return k.name }
|
||||
func (k *Int16) Description() string { return k.description }
|
||||
|
||||
func (k *Int16) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10))
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *Int16) Of(v int16) label.Label { return label.Of64(k, uint64(v)) }
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *Int16) Get(lm label.Map) int16 {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *Int16) From(t label.Label) int16 { return int16(t.Unpack64()) }
|
||||
|
||||
// Int32 represents a key
|
||||
type Int32 struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewInt32 creates a new Key for int32 values.
|
||||
func NewInt32(name, description string) *Int32 {
|
||||
return &Int32{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *Int32) Name() string { return k.name }
|
||||
func (k *Int32) Description() string { return k.description }
|
||||
|
||||
func (k *Int32) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10))
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *Int32) Of(v int32) label.Label { return label.Of64(k, uint64(v)) }
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *Int32) Get(lm label.Map) int32 {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *Int32) From(t label.Label) int32 { return int32(t.Unpack64()) }
|
||||
|
||||
// Int64 represents a key
|
||||
type Int64 struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewInt64 creates a new Key for int64 values.
|
||||
func NewInt64(name, description string) *Int64 {
|
||||
return &Int64{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *Int64) Name() string { return k.name }
|
||||
func (k *Int64) Description() string { return k.description }
|
||||
|
||||
func (k *Int64) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
w.Write(strconv.AppendInt(buf, k.From(l), 10))
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *Int64) Of(v int64) label.Label { return label.Of64(k, uint64(v)) }
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *Int64) Get(lm label.Map) int64 {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *Int64) From(t label.Label) int64 { return int64(t.Unpack64()) }
|
||||
|
||||
// UInt represents a key
|
||||
type UInt struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewUInt creates a new Key for uint values.
|
||||
func NewUInt(name, description string) *UInt {
|
||||
return &UInt{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *UInt) Name() string { return k.name }
|
||||
func (k *UInt) Description() string { return k.description }
|
||||
|
||||
func (k *UInt) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10))
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *UInt) Of(v uint) label.Label { return label.Of64(k, uint64(v)) }
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *UInt) Get(lm label.Map) uint {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *UInt) From(t label.Label) uint { return uint(t.Unpack64()) }
|
||||
|
||||
// UInt8 represents a key
|
||||
type UInt8 struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewUInt8 creates a new Key for uint8 values.
|
||||
func NewUInt8(name, description string) *UInt8 {
|
||||
return &UInt8{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *UInt8) Name() string { return k.name }
|
||||
func (k *UInt8) Description() string { return k.description }
|
||||
|
||||
func (k *UInt8) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10))
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *UInt8) Of(v uint8) label.Label { return label.Of64(k, uint64(v)) }
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *UInt8) Get(lm label.Map) uint8 {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *UInt8) From(t label.Label) uint8 { return uint8(t.Unpack64()) }
|
||||
|
||||
// UInt16 represents a key
|
||||
type UInt16 struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewUInt16 creates a new Key for uint16 values.
|
||||
func NewUInt16(name, description string) *UInt16 {
|
||||
return &UInt16{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *UInt16) Name() string { return k.name }
|
||||
func (k *UInt16) Description() string { return k.description }
|
||||
|
||||
func (k *UInt16) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10))
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *UInt16) Of(v uint16) label.Label { return label.Of64(k, uint64(v)) }
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *UInt16) Get(lm label.Map) uint16 {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *UInt16) From(t label.Label) uint16 { return uint16(t.Unpack64()) }
|
||||
|
||||
// UInt32 represents a key
|
||||
type UInt32 struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewUInt32 creates a new Key for uint32 values.
|
||||
func NewUInt32(name, description string) *UInt32 {
|
||||
return &UInt32{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *UInt32) Name() string { return k.name }
|
||||
func (k *UInt32) Description() string { return k.description }
|
||||
|
||||
func (k *UInt32) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10))
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *UInt32) Of(v uint32) label.Label { return label.Of64(k, uint64(v)) }
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *UInt32) Get(lm label.Map) uint32 {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *UInt32) From(t label.Label) uint32 { return uint32(t.Unpack64()) }
|
||||
|
||||
// UInt64 represents a key
|
||||
type UInt64 struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewUInt64 creates a new Key for uint64 values.
|
||||
func NewUInt64(name, description string) *UInt64 {
|
||||
return &UInt64{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *UInt64) Name() string { return k.name }
|
||||
func (k *UInt64) Description() string { return k.description }
|
||||
|
||||
func (k *UInt64) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
w.Write(strconv.AppendUint(buf, k.From(l), 10))
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *UInt64) Of(v uint64) label.Label { return label.Of64(k, v) }
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *UInt64) Get(lm label.Map) uint64 {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *UInt64) From(t label.Label) uint64 { return t.Unpack64() }
|
||||
|
||||
// Float32 represents a key
|
||||
type Float32 struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewFloat32 creates a new Key for float32 values.
|
||||
func NewFloat32(name, description string) *Float32 {
|
||||
return &Float32{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *Float32) Name() string { return k.name }
|
||||
func (k *Float32) Description() string { return k.description }
|
||||
|
||||
func (k *Float32) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
w.Write(strconv.AppendFloat(buf, float64(k.From(l)), 'E', -1, 32))
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *Float32) Of(v float32) label.Label {
|
||||
return label.Of64(k, uint64(math.Float32bits(v)))
|
||||
}
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *Float32) Get(lm label.Map) float32 {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *Float32) From(t label.Label) float32 {
|
||||
return math.Float32frombits(uint32(t.Unpack64()))
|
||||
}
|
||||
|
||||
// Float64 represents a key
|
||||
type Float64 struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewFloat64 creates a new Key for int64 values.
|
||||
func NewFloat64(name, description string) *Float64 {
|
||||
return &Float64{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *Float64) Name() string { return k.name }
|
||||
func (k *Float64) Description() string { return k.description }
|
||||
|
||||
func (k *Float64) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
w.Write(strconv.AppendFloat(buf, k.From(l), 'E', -1, 64))
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *Float64) Of(v float64) label.Label {
|
||||
return label.Of64(k, math.Float64bits(v))
|
||||
}
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *Float64) Get(lm label.Map) float64 {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *Float64) From(t label.Label) float64 {
|
||||
return math.Float64frombits(t.Unpack64())
|
||||
}
|
||||
|
||||
// String represents a key
|
||||
type String struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewString creates a new Key for int64 values.
|
||||
func NewString(name, description string) *String {
|
||||
return &String{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *String) Name() string { return k.name }
|
||||
func (k *String) Description() string { return k.description }
|
||||
|
||||
func (k *String) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
w.Write(strconv.AppendQuote(buf, k.From(l)))
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *String) Of(v string) label.Label { return label.OfString(k, v) }
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *String) Get(lm label.Map) string {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *String) From(t label.Label) string { return t.UnpackString() }
|
||||
|
||||
// Boolean represents a key
|
||||
type Boolean struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewBoolean creates a new Key for bool values.
|
||||
func NewBoolean(name, description string) *Boolean {
|
||||
return &Boolean{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *Boolean) Name() string { return k.name }
|
||||
func (k *Boolean) Description() string { return k.description }
|
||||
|
||||
func (k *Boolean) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
w.Write(strconv.AppendBool(buf, k.From(l)))
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *Boolean) Of(v bool) label.Label {
|
||||
if v {
|
||||
return label.Of64(k, 1)
|
||||
}
|
||||
return label.Of64(k, 0)
|
||||
}
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *Boolean) Get(lm label.Map) bool {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *Boolean) From(t label.Label) bool { return t.Unpack64() > 0 }
|
||||
|
||||
// Error represents a key
|
||||
type Error struct {
|
||||
name string
|
||||
description string
|
||||
}
|
||||
|
||||
// NewError creates a new Key for int64 values.
|
||||
func NewError(name, description string) *Error {
|
||||
return &Error{name: name, description: description}
|
||||
}
|
||||
|
||||
func (k *Error) Name() string { return k.name }
|
||||
func (k *Error) Description() string { return k.description }
|
||||
|
||||
func (k *Error) Format(w io.Writer, buf []byte, l label.Label) {
|
||||
io.WriteString(w, k.From(l).Error())
|
||||
}
|
||||
|
||||
// Of creates a new Label with this key and the supplied value.
|
||||
func (k *Error) Of(v error) label.Label { return label.OfValue(k, v) }
|
||||
|
||||
// Get can be used to get a label for the key from a label.Map.
|
||||
func (k *Error) Get(lm label.Map) error {
|
||||
if t := lm.Find(k); t.Valid() {
|
||||
return k.From(t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// From can be used to get a value from a Label.
|
||||
func (k *Error) From(t label.Label) error {
|
||||
err, _ := t.UnpackValue().(error)
|
||||
return err
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package keys
|
||||
|
||||
var (
|
||||
// Msg is a key used to add message strings to label lists.
|
||||
Msg = NewString("message", "a readable message")
|
||||
// Label is a key used to indicate an event adds labels to the context.
|
||||
Label = NewTag("label", "a label context marker")
|
||||
// Start is used for things like traces that have a name.
|
||||
Start = NewString("start", "span start")
|
||||
// Metric is a key used to indicate an event records metrics.
|
||||
End = NewTag("end", "a span end marker")
|
||||
// Metric is a key used to indicate an event records metrics.
|
||||
Detach = NewTag("detach", "a span detach marker")
|
||||
// Err is a key used to add error values to label lists.
|
||||
Err = NewError("error", "an error that occurred")
|
||||
// Metric is a key used to indicate an event records metrics.
|
||||
Metric = NewTag("metric", "a metric event marker")
|
||||
)
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package label
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Key is used as the identity of a Label.
|
||||
// Keys are intended to be compared by pointer only, the name should be unique
|
||||
// for communicating with external systems, but it is not required or enforced.
|
||||
type Key interface {
|
||||
// Name returns the key name.
|
||||
Name() string
|
||||
// Description returns a string that can be used to describe the value.
|
||||
Description() string
|
||||
|
||||
// Format is used in formatting to append the value of the label to the
|
||||
// supplied buffer.
|
||||
// The formatter may use the supplied buf as a scratch area to avoid
|
||||
// allocations.
|
||||
Format(w io.Writer, buf []byte, l Label)
|
||||
}
|
||||
|
||||
// Label holds a key and value pair.
|
||||
// It is normally used when passing around lists of labels.
|
||||
type Label struct {
|
||||
key Key
|
||||
packed uint64
|
||||
untyped interface{}
|
||||
}
|
||||
|
||||
// Map is the interface to a collection of Labels indexed by key.
|
||||
type Map interface {
|
||||
// Find returns the label that matches the supplied key.
|
||||
Find(key Key) Label
|
||||
}
|
||||
|
||||
// List is the interface to something that provides an iterable
|
||||
// list of labels.
|
||||
// Iteration should start from 0 and continue until Valid returns false.
|
||||
type List interface {
|
||||
// Valid returns true if the index is within range for the list.
|
||||
// It does not imply the label at that index will itself be valid.
|
||||
Valid(index int) bool
|
||||
// Label returns the label at the given index.
|
||||
Label(index int) Label
|
||||
}
|
||||
|
||||
// list implements LabelList for a list of Labels.
|
||||
type list struct {
|
||||
labels []Label
|
||||
}
|
||||
|
||||
// filter wraps a LabelList filtering out specific labels.
|
||||
type filter struct {
|
||||
keys []Key
|
||||
underlying List
|
||||
}
|
||||
|
||||
// listMap implements LabelMap for a simple list of labels.
|
||||
type listMap struct {
|
||||
labels []Label
|
||||
}
|
||||
|
||||
// mapChain implements LabelMap for a list of underlying LabelMap.
|
||||
type mapChain struct {
|
||||
maps []Map
|
||||
}
|
||||
|
||||
// OfValue creates a new label from the key and value.
|
||||
// This method is for implementing new key types, label creation should
|
||||
// normally be done with the Of method of the key.
|
||||
func OfValue(k Key, value interface{}) Label { return Label{key: k, untyped: value} }
|
||||
|
||||
// UnpackValue assumes the label was built using LabelOfValue and returns the value
|
||||
// that was passed to that constructor.
|
||||
// This method is for implementing new key types, for type safety normal
|
||||
// access should be done with the From method of the key.
|
||||
func (t Label) UnpackValue() interface{} { return t.untyped }
|
||||
|
||||
// Of64 creates a new label from a key and a uint64. This is often
|
||||
// used for non uint64 values that can be packed into a uint64.
|
||||
// This method is for implementing new key types, label creation should
|
||||
// normally be done with the Of method of the key.
|
||||
func Of64(k Key, v uint64) Label { return Label{key: k, packed: v} }
|
||||
|
||||
// Unpack64 assumes the label was built using LabelOf64 and returns the value that
|
||||
// was passed to that constructor.
|
||||
// This method is for implementing new key types, for type safety normal
|
||||
// access should be done with the From method of the key.
|
||||
func (t Label) Unpack64() uint64 { return t.packed }
|
||||
|
||||
// OfString creates a new label from a key and a string.
|
||||
// This method is for implementing new key types, label creation should
|
||||
// normally be done with the Of method of the key.
|
||||
func OfString(k Key, v string) Label {
|
||||
hdr := (*reflect.StringHeader)(unsafe.Pointer(&v))
|
||||
return Label{
|
||||
key: k,
|
||||
packed: uint64(hdr.Len),
|
||||
untyped: unsafe.Pointer(hdr.Data),
|
||||
}
|
||||
}
|
||||
|
||||
// UnpackString assumes the label was built using LabelOfString and returns the
|
||||
// value that was passed to that constructor.
|
||||
// This method is for implementing new key types, for type safety normal
|
||||
// access should be done with the From method of the key.
|
||||
func (t Label) UnpackString() string {
|
||||
var v string
|
||||
hdr := (*reflect.StringHeader)(unsafe.Pointer(&v))
|
||||
hdr.Data = uintptr(t.untyped.(unsafe.Pointer))
|
||||
hdr.Len = int(t.packed)
|
||||
return *(*string)(unsafe.Pointer(hdr))
|
||||
}
|
||||
|
||||
// Valid returns true if the Label is a valid one (it has a key).
|
||||
func (t Label) Valid() bool { return t.key != nil }
|
||||
|
||||
// Key returns the key of this Label.
|
||||
func (t Label) Key() Key { return t.key }
|
||||
|
||||
// Format is used for debug printing of labels.
|
||||
func (t Label) Format(f fmt.State, r rune) {
|
||||
if !t.Valid() {
|
||||
io.WriteString(f, `nil`)
|
||||
return
|
||||
}
|
||||
io.WriteString(f, t.Key().Name())
|
||||
io.WriteString(f, "=")
|
||||
var buf [128]byte
|
||||
t.Key().Format(f, buf[:0], t)
|
||||
}
|
||||
|
||||
func (l *list) Valid(index int) bool {
|
||||
return index >= 0 && index < len(l.labels)
|
||||
}
|
||||
|
||||
func (l *list) Label(index int) Label {
|
||||
return l.labels[index]
|
||||
}
|
||||
|
||||
func (f *filter) Valid(index int) bool {
|
||||
return f.underlying.Valid(index)
|
||||
}
|
||||
|
||||
func (f *filter) Label(index int) Label {
|
||||
l := f.underlying.Label(index)
|
||||
for _, f := range f.keys {
|
||||
if l.Key() == f {
|
||||
return Label{}
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (lm listMap) Find(key Key) Label {
|
||||
for _, l := range lm.labels {
|
||||
if l.Key() == key {
|
||||
return l
|
||||
}
|
||||
}
|
||||
return Label{}
|
||||
}
|
||||
|
||||
func (c mapChain) Find(key Key) Label {
|
||||
for _, src := range c.maps {
|
||||
l := src.Find(key)
|
||||
if l.Valid() {
|
||||
return l
|
||||
}
|
||||
}
|
||||
return Label{}
|
||||
}
|
||||
|
||||
var emptyList = &list{}
|
||||
|
||||
func NewList(labels ...Label) List {
|
||||
if len(labels) == 0 {
|
||||
return emptyList
|
||||
}
|
||||
return &list{labels: labels}
|
||||
}
|
||||
|
||||
func Filter(l List, keys ...Key) List {
|
||||
if len(keys) == 0 {
|
||||
return l
|
||||
}
|
||||
return &filter{keys: keys, underlying: l}
|
||||
}
|
||||
|
||||
func NewMap(labels ...Label) Map {
|
||||
return listMap{labels: labels}
|
||||
}
|
||||
|
||||
func MergeMaps(srcs ...Map) Map {
|
||||
var nonNil []Map
|
||||
for _, src := range srcs {
|
||||
if src != nil {
|
||||
nonNil = append(nonNil, src)
|
||||
}
|
||||
}
|
||||
if len(nonNil) == 1 {
|
||||
return nonNil[0]
|
||||
}
|
||||
return mapChain{maps: nonNil}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package gocommand is a helper for calling the go command.
|
||||
package gocommand
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/internal/event"
|
||||
)
|
||||
|
||||
// An Runner will run go command invocations and serialize
|
||||
// them if it sees a concurrency error.
|
||||
type Runner struct {
|
||||
// once guards the runner initialization.
|
||||
once sync.Once
|
||||
|
||||
// inFlight tracks available workers.
|
||||
inFlight chan struct{}
|
||||
|
||||
// serialized guards the ability to run a go command serially,
|
||||
// to avoid deadlocks when claiming workers.
|
||||
serialized chan struct{}
|
||||
}
|
||||
|
||||
const maxInFlight = 10
|
||||
|
||||
func (runner *Runner) initialize() {
|
||||
runner.once.Do(func() {
|
||||
runner.inFlight = make(chan struct{}, maxInFlight)
|
||||
runner.serialized = make(chan struct{}, 1)
|
||||
})
|
||||
}
|
||||
|
||||
// 1.13: go: updates to go.mod needed, but contents have changed
|
||||
// 1.14: go: updating go.mod: existing contents have changed since last read
|
||||
var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`)
|
||||
|
||||
// Run is a convenience wrapper around RunRaw.
|
||||
// It returns only stdout and a "friendly" error.
|
||||
func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) {
|
||||
stdout, _, friendly, _ := runner.RunRaw(ctx, inv)
|
||||
return stdout, friendly
|
||||
}
|
||||
|
||||
// RunPiped runs the invocation serially, always waiting for any concurrent
|
||||
// invocations to complete first.
|
||||
func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) error {
|
||||
_, err := runner.runPiped(ctx, inv, stdout, stderr)
|
||||
return err
|
||||
}
|
||||
|
||||
// RunRaw runs the invocation, serializing requests only if they fight over
|
||||
// go.mod changes.
|
||||
func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
|
||||
// Make sure the runner is always initialized.
|
||||
runner.initialize()
|
||||
|
||||
// First, try to run the go command concurrently.
|
||||
stdout, stderr, friendlyErr, err := runner.runConcurrent(ctx, inv)
|
||||
|
||||
// If we encounter a load concurrency error, we need to retry serially.
|
||||
if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) {
|
||||
return stdout, stderr, friendlyErr, err
|
||||
}
|
||||
event.Error(ctx, "Load concurrency error, will retry serially", err)
|
||||
|
||||
// Run serially by calling runPiped.
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr)
|
||||
return stdout, stderr, friendlyErr, err
|
||||
}
|
||||
|
||||
func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
|
||||
// Wait for 1 worker to become available.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, nil, nil, ctx.Err()
|
||||
case runner.inFlight <- struct{}{}:
|
||||
defer func() { <-runner.inFlight }()
|
||||
}
|
||||
|
||||
stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
|
||||
friendlyErr, err := inv.runWithFriendlyError(ctx, stdout, stderr)
|
||||
return stdout, stderr, friendlyErr, err
|
||||
}
|
||||
|
||||
func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) (error, error) {
|
||||
// Make sure the runner is always initialized.
|
||||
runner.initialize()
|
||||
|
||||
// Acquire the serialization lock. This avoids deadlocks between two
|
||||
// runPiped commands.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case runner.serialized <- struct{}{}:
|
||||
defer func() { <-runner.serialized }()
|
||||
}
|
||||
|
||||
// Wait for all in-progress go commands to return before proceeding,
|
||||
// to avoid load concurrency errors.
|
||||
for i := 0; i < maxInFlight; i++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case runner.inFlight <- struct{}{}:
|
||||
// Make sure we always "return" any workers we took.
|
||||
defer func() { <-runner.inFlight }()
|
||||
}
|
||||
}
|
||||
|
||||
return inv.runWithFriendlyError(ctx, stdout, stderr)
|
||||
}
|
||||
|
||||
// An Invocation represents a call to the go command.
|
||||
type Invocation struct {
|
||||
Verb string
|
||||
Args []string
|
||||
BuildFlags []string
|
||||
Env []string
|
||||
WorkingDir string
|
||||
Logf func(format string, args ...interface{})
|
||||
}
|
||||
|
||||
func (i *Invocation) runWithFriendlyError(ctx context.Context, stdout, stderr io.Writer) (friendlyError error, rawError error) {
|
||||
rawError = i.run(ctx, stdout, stderr)
|
||||
if rawError != nil {
|
||||
friendlyError = rawError
|
||||
// Check for 'go' executable not being found.
|
||||
if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
|
||||
friendlyError = fmt.Errorf("go command required, not found: %v", ee)
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
friendlyError = ctx.Err()
|
||||
}
|
||||
friendlyError = fmt.Errorf("err: %v: stderr: %s", friendlyError, stderr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error {
|
||||
log := i.Logf
|
||||
if log == nil {
|
||||
log = func(string, ...interface{}) {}
|
||||
}
|
||||
|
||||
goArgs := []string{i.Verb}
|
||||
switch i.Verb {
|
||||
case "mod":
|
||||
// mod needs the sub-verb before build flags.
|
||||
goArgs = append(goArgs, i.Args[0])
|
||||
goArgs = append(goArgs, i.BuildFlags...)
|
||||
goArgs = append(goArgs, i.Args[1:]...)
|
||||
case "env":
|
||||
// env doesn't take build flags.
|
||||
goArgs = append(goArgs, i.Args...)
|
||||
default:
|
||||
goArgs = append(goArgs, i.BuildFlags...)
|
||||
goArgs = append(goArgs, i.Args...)
|
||||
}
|
||||
cmd := exec.Command("go", goArgs...)
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
// On darwin the cwd gets resolved to the real path, which breaks anything that
|
||||
// expects the working directory to keep the original path, including the
|
||||
// go command when dealing with modules.
|
||||
// The Go stdlib has a special feature where if the cwd and the PWD are the
|
||||
// same node then it trusts the PWD, so by setting it in the env for the child
|
||||
// process we fix up all the paths returned by the go command.
|
||||
cmd.Env = append(os.Environ(), i.Env...)
|
||||
if i.WorkingDir != "" {
|
||||
cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir)
|
||||
cmd.Dir = i.WorkingDir
|
||||
}
|
||||
defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
|
||||
|
||||
return runCmdContext(ctx, cmd)
|
||||
}
|
||||
|
||||
// runCmdContext is like exec.CommandContext except it sends os.Interrupt
|
||||
// before os.Kill.
|
||||
func runCmdContext(ctx context.Context, cmd *exec.Cmd) error {
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
resChan := make(chan error, 1)
|
||||
go func() {
|
||||
resChan <- cmd.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-resChan:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
}
|
||||
// Cancelled. Interrupt and see if it ends voluntarily.
|
||||
cmd.Process.Signal(os.Interrupt)
|
||||
select {
|
||||
case err := <-resChan:
|
||||
return err
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
// Didn't shut down in response to interrupt. Kill it hard.
|
||||
cmd.Process.Kill()
|
||||
return <-resChan
|
||||
}
|
||||
|
||||
func cmdDebugStr(cmd *exec.Cmd) string {
|
||||
env := make(map[string]string)
|
||||
for _, kv := range cmd.Env {
|
||||
split := strings.Split(kv, "=")
|
||||
k, v := split[0], split[1]
|
||||
env[k] = v
|
||||
}
|
||||
|
||||
return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], cmd.Args)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gocommand
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// ModuleJSON holds information about a module.
|
||||
type ModuleJSON struct {
|
||||
Path string // module path
|
||||
Replace *ModuleJSON // replaced by this module
|
||||
Main bool // is this the main module?
|
||||
Indirect bool // is this module only an indirect dependency of main module?
|
||||
Dir string // directory holding files for this module, if any
|
||||
GoMod string // path to go.mod file for this module, if any
|
||||
GoVersion string // go version used in module
|
||||
}
|
||||
|
||||
var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`)
|
||||
|
||||
// VendorEnabled reports whether vendoring is enabled. It takes a *Runner to execute Go commands
|
||||
// with the supplied context.Context and Invocation. The Invocation can contain pre-defined fields,
|
||||
// of which only Verb and Args are modified to run the appropriate Go command.
|
||||
// Inspired by setDefaultBuildMod in modload/init.go
|
||||
func VendorEnabled(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) {
|
||||
mainMod, go114, err := getMainModuleAnd114(ctx, inv, r)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// We check the GOFLAGS to see if there is anything overridden or not.
|
||||
inv.Verb = "env"
|
||||
inv.Args = []string{"GOFLAGS"}
|
||||
stdout, err := r.Run(ctx, inv)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
goflags := string(bytes.TrimSpace(stdout.Bytes()))
|
||||
matches := modFlagRegexp.FindStringSubmatch(goflags)
|
||||
var modFlag string
|
||||
if len(matches) != 0 {
|
||||
modFlag = matches[1]
|
||||
}
|
||||
if modFlag != "" {
|
||||
// Don't override an explicit '-mod=' argument.
|
||||
return mainMod, modFlag == "vendor", nil
|
||||
}
|
||||
if mainMod == nil || !go114 {
|
||||
return mainMod, false, nil
|
||||
}
|
||||
// Check 1.14's automatic vendor mode.
|
||||
if fi, err := os.Stat(filepath.Join(mainMod.Dir, "vendor")); err == nil && fi.IsDir() {
|
||||
if mainMod.GoVersion != "" && semver.Compare("v"+mainMod.GoVersion, "v1.14") >= 0 {
|
||||
// The Go version is at least 1.14, and a vendor directory exists.
|
||||
// Set -mod=vendor by default.
|
||||
return mainMod, true, nil
|
||||
}
|
||||
}
|
||||
return mainMod, false, nil
|
||||
}
|
||||
|
||||
// getMainModuleAnd114 gets the main module's information and whether the
|
||||
// go command in use is 1.14+. This is the information needed to figure out
|
||||
// if vendoring should be enabled.
|
||||
func getMainModuleAnd114(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) {
|
||||
const format = `{{.Path}}
|
||||
{{.Dir}}
|
||||
{{.GoMod}}
|
||||
{{.GoVersion}}
|
||||
{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}
|
||||
`
|
||||
inv.Verb = "list"
|
||||
inv.Args = []string{"-m", "-f", format}
|
||||
stdout, err := r.Run(ctx, inv)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
lines := strings.Split(stdout.String(), "\n")
|
||||
if len(lines) < 5 {
|
||||
return nil, false, fmt.Errorf("unexpected stdout: %q", stdout.String())
|
||||
}
|
||||
mod := &ModuleJSON{
|
||||
Path: lines[0],
|
||||
Dir: lines[1],
|
||||
GoMod: lines[2],
|
||||
GoVersion: lines[3],
|
||||
Main: true,
|
||||
}
|
||||
return mod, lines[4] == "go1.14", nil
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// Package packagesinternal exposes internal-only fields from go/packages.
|
||||
package packagesinternal
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/internal/gocommand"
|
||||
)
|
||||
|
||||
var GetForTest = func(p interface{}) string { return "" }
|
||||
|
||||
var GetGoCmdRunner = func(config interface{}) *gocommand.Runner { return nil }
|
||||
|
||||
var SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) {}
|
||||
|
||||
var TypecheckCgo int
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package typesinternal
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func SetUsesCgo(conf *types.Config) bool {
|
||||
v := reflect.ValueOf(conf).Elem()
|
||||
|
||||
f := v.FieldByName("go115UsesCgo")
|
||||
if !f.IsValid() {
|
||||
f = v.FieldByName("UsesCgo")
|
||||
if !f.IsValid() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
addr := unsafe.Pointer(f.UnsafeAddr())
|
||||
*(*bool)(addr) = true
|
||||
|
||||
return true
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2019 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
This repository holds the transition packages for the new Go 1.13 error values.
|
||||
See golang.org/design/29934-error-values.
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xerrors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// FormatError calls the FormatError method of f with an errors.Printer
|
||||
// configured according to s and verb, and writes the result to s.
|
||||
func FormatError(f Formatter, s fmt.State, verb rune) {
|
||||
// Assuming this function is only called from the Format method, and given
|
||||
// that FormatError takes precedence over Format, it cannot be called from
|
||||
// any package that supports errors.Formatter. It is therefore safe to
|
||||
// disregard that State may be a specific printer implementation and use one
|
||||
// of our choice instead.
|
||||
|
||||
// limitations: does not support printing error as Go struct.
|
||||
|
||||
var (
|
||||
sep = " " // separator before next error
|
||||
p = &state{State: s}
|
||||
direct = true
|
||||
)
|
||||
|
||||
var err error = f
|
||||
|
||||
switch verb {
|
||||
// Note that this switch must match the preference order
|
||||
// for ordinary string printing (%#v before %+v, and so on).
|
||||
|
||||
case 'v':
|
||||
if s.Flag('#') {
|
||||
if stringer, ok := err.(fmt.GoStringer); ok {
|
||||
io.WriteString(&p.buf, stringer.GoString())
|
||||
goto exit
|
||||
}
|
||||
// proceed as if it were %v
|
||||
} else if s.Flag('+') {
|
||||
p.printDetail = true
|
||||
sep = "\n - "
|
||||
}
|
||||
case 's':
|
||||
case 'q', 'x', 'X':
|
||||
// Use an intermediate buffer in the rare cases that precision,
|
||||
// truncation, or one of the alternative verbs (q, x, and X) are
|
||||
// specified.
|
||||
direct = false
|
||||
|
||||
default:
|
||||
p.buf.WriteString("%!")
|
||||
p.buf.WriteRune(verb)
|
||||
p.buf.WriteByte('(')
|
||||
switch {
|
||||
case err != nil:
|
||||
p.buf.WriteString(reflect.TypeOf(f).String())
|
||||
default:
|
||||
p.buf.WriteString("<nil>")
|
||||
}
|
||||
p.buf.WriteByte(')')
|
||||
io.Copy(s, &p.buf)
|
||||
return
|
||||
}
|
||||
|
||||
loop:
|
||||
for {
|
||||
switch v := err.(type) {
|
||||
case Formatter:
|
||||
err = v.FormatError((*printer)(p))
|
||||
case fmt.Formatter:
|
||||
v.Format(p, 'v')
|
||||
break loop
|
||||
default:
|
||||
io.WriteString(&p.buf, v.Error())
|
||||
break loop
|
||||
}
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if p.needColon || !p.printDetail {
|
||||
p.buf.WriteByte(':')
|
||||
p.needColon = false
|
||||
}
|
||||
p.buf.WriteString(sep)
|
||||
p.inDetail = false
|
||||
p.needNewline = false
|
||||
}
|
||||
|
||||
exit:
|
||||
width, okW := s.Width()
|
||||
prec, okP := s.Precision()
|
||||
|
||||
if !direct || (okW && width > 0) || okP {
|
||||
// Construct format string from State s.
|
||||
format := []byte{'%'}
|
||||
if s.Flag('-') {
|
||||
format = append(format, '-')
|
||||
}
|
||||
if s.Flag('+') {
|
||||
format = append(format, '+')
|
||||
}
|
||||
if s.Flag(' ') {
|
||||
format = append(format, ' ')
|
||||
}
|
||||
if okW {
|
||||
format = strconv.AppendInt(format, int64(width), 10)
|
||||
}
|
||||
if okP {
|
||||
format = append(format, '.')
|
||||
format = strconv.AppendInt(format, int64(prec), 10)
|
||||
}
|
||||
format = append(format, string(verb)...)
|
||||
fmt.Fprintf(s, string(format), p.buf.String())
|
||||
} else {
|
||||
io.Copy(s, &p.buf)
|
||||
}
|
||||
}
|
||||
|
||||
var detailSep = []byte("\n ")
|
||||
|
||||
// state tracks error printing state. It implements fmt.State.
|
||||
type state struct {
|
||||
fmt.State
|
||||
buf bytes.Buffer
|
||||
|
||||
printDetail bool
|
||||
inDetail bool
|
||||
needColon bool
|
||||
needNewline bool
|
||||
}
|
||||
|
||||
func (s *state) Write(b []byte) (n int, err error) {
|
||||
if s.printDetail {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if s.inDetail && s.needColon {
|
||||
s.needNewline = true
|
||||
if b[0] == '\n' {
|
||||
b = b[1:]
|
||||
}
|
||||
}
|
||||
k := 0
|
||||
for i, c := range b {
|
||||
if s.needNewline {
|
||||
if s.inDetail && s.needColon {
|
||||
s.buf.WriteByte(':')
|
||||
s.needColon = false
|
||||
}
|
||||
s.buf.Write(detailSep)
|
||||
s.needNewline = false
|
||||
}
|
||||
if c == '\n' {
|
||||
s.buf.Write(b[k:i])
|
||||
k = i + 1
|
||||
s.needNewline = true
|
||||
}
|
||||
}
|
||||
s.buf.Write(b[k:])
|
||||
if !s.inDetail {
|
||||
s.needColon = true
|
||||
}
|
||||
} else if !s.inDetail {
|
||||
s.buf.Write(b)
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// printer wraps a state to implement an xerrors.Printer.
|
||||
type printer state
|
||||
|
||||
func (s *printer) Print(args ...interface{}) {
|
||||
if !s.inDetail || s.printDetail {
|
||||
fmt.Fprint((*state)(s), args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *printer) Printf(format string, args ...interface{}) {
|
||||
if !s.inDetail || s.printDetail {
|
||||
fmt.Fprintf((*state)(s), format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *printer) Detail() bool {
|
||||
s.inDetail = true
|
||||
return s.printDetail
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
issuerepo: golang/go
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package xerrors implements functions to manipulate errors.
|
||||
//
|
||||
// This package is based on the Go 2 proposal for error values:
|
||||
// https://golang.org/design/29934-error-values
|
||||
//
|
||||
// These functions were incorporated into the standard library's errors package
|
||||
// in Go 1.13:
|
||||
// - Is
|
||||
// - As
|
||||
// - Unwrap
|
||||
//
|
||||
// Also, Errorf's %w verb was incorporated into fmt.Errorf.
|
||||
//
|
||||
// Use this package to get equivalent behavior in all supported Go versions.
|
||||
//
|
||||
// No other features of this package were included in Go 1.13, and at present
|
||||
// there are no plans to include any of them.
|
||||
package xerrors // import "golang.org/x/xerrors"
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xerrors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// errorString is a trivial implementation of error.
|
||||
type errorString struct {
|
||||
s string
|
||||
frame Frame
|
||||
}
|
||||
|
||||
// New returns an error that formats as the given text.
|
||||
//
|
||||
// The returned error contains a Frame set to the caller's location and
|
||||
// implements Formatter to show this information when printed with details.
|
||||
func New(text string) error {
|
||||
return &errorString{text, Caller(1)}
|
||||
}
|
||||
|
||||
func (e *errorString) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) }
|
||||
|
||||
func (e *errorString) FormatError(p Printer) (next error) {
|
||||
p.Print(e.s)
|
||||
e.frame.Format(p)
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user