Compare commits

...
17 Commits
Author SHA1 Message Date
xtaci 89f69a9850 add vendor 2022-03-28 21:52:25 +08:00
xtaci cadb94fdc3 upd deps 2022-03-28 21:51:46 +08:00
xtaci 3acc8a7372 testing serial 2022-03-28 17:05:57 +08:00
林博仁(Buo-ren, Lin)andGitHub b63eeb6301 Fix typo (embeded -> embedded) (#850)
Signed-off-by: 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com>
2021-12-31 10:53:02 +08:00
AnandGitHub 5b5c094e4b miss [unit] (#860) 2021-12-31 10:52:15 +08:00
Purple GrapeandGitHub c4bd77cf28 add rpm support (#862) 2021-12-31 10:52:01 +08:00
xtaci 94c9cacf4f upd deps to smux to v1.5.16 2021-09-22 21:45:09 +08:00
xtaci 9a5b31b470 enable support for Apple M1 2021-04-01 22:23:46 +08:00
xtaci 07ad03fecf remove a duplicated logging 2020-12-20 19:09:43 +08:00
Hugo WangandGitHub 2225d258cb create UDP connection only when needed (#825)
* create udp connection only when needed

* misc

* some more minor changes

* update option description

* sync the doc

* lets keep original

* respect config.Quiet

* misc

* removed the useless muxes init
2020-12-20 16:23:32 +08:00
xtaci e9316f7be4 remove a condition in scavenger 2020-12-19 11:42:24 +08:00
xtaci 9ac04cd24d a smarter scavenger 2020-12-17 17:37:36 +08:00
76c4d231f2 Support Apple M1 Mac (#823)
See issue https://github.com/xtaci/kcptun/issues/822

Co-authored-by: apple <apple@abc.net>
2020-11-26 11:06:46 +08:00
xtaci 160b68cbc5 upd vendor 2020-11-26 11:05:15 +08:00
xtaci 212504f6c2 upd deps to smux 2020-11-26 11:04:38 +08:00
xtaci 114cd97025 upgrade to kcp-go@v5.6.1 2020-10-10 11:52:31 +08:00
xtaci b2be91cdb5 add missing vendor files 2020-10-04 22:40:04 +08:00
665 changed files with 140809 additions and 34478 deletions
+4 -8
View File
@@ -106,7 +106,7 @@ All precompiled releases are genereated from `build-release.sh` script.
> eg: `-mode fast3`
> Aggresiveness/Responsiveness on retransmission for embeded modes are:
> Aggresiveness/Responsiveness on retransmission for embedded modes are:
> *fast3 > fast2 > fast > normal > default*
@@ -152,7 +152,7 @@ GLOBAL OPTIONS:
--mode value profiles: fast3, fast2, fast, normal, manual (default: "fast")
--conn value set num of UDP connections to server (default: 1)
--autoexpire value set auto expiration time(in seconds) for a single UDP connection, 0 to disable (default: 0)
--scavengettl value set how long an expired connection can live(in sec), -1 to disable (default: 600)
--scavengettl value set how long an expired connection can live (in seconds) (default: 600)
--mtu value set maximum transmission unit for UDP packets (default: 1350)
--sndwnd value set send window size(num of packets) (default: 128)
--rcvwnd value set receive window size(num of packets) (default: 512)
@@ -224,8 +224,6 @@ It is able to detect and correct multiple symbol errors. By adding t check symbo
![FED](FEC.png)
Setting parameters of RS-Code with ```-datashard m -parityshard n``` on **BOTH** KCP Client & KCP Server **MUST** be **IDENTICAL**.
#### DSCP
Differentiated services or DiffServ is a computer networking architecture that specifies a simple, scalable and coarse-grained mechanism for classifying and managing network traffic and providing quality of service (QoS) on modern IP networks. DiffServ can, for example, be used to provide low-latency to critical network traffic such as voice or streaming media while providing simple best-effort service to non-critical services such as web traffic or file transfers.
@@ -293,7 +291,7 @@ Reference: https://blog.golang.org/go15gc
Primary memory allocation are done from a global buffer pool *xmit.Buf*, in kcp-go, when we need to allocate some bytes, we can get from that pool, and a *fixed-capacity* 1500 bytes(mtuLimit) will be returned, the *rx queue*, *tx queue* and *fec queue* all receive bytes from there, and they will return the bytes to the pool after using to prevent *unnecessary zer0ing* of bytes.
The pool mechanism maintained a *high watermark* for slice objects, these *in-flight* objects from the pool will survive from the perodical garbage collection, meanwhile the pool kept the ability to return the memory to runtime if in idle, `-sndwnd`,`-rcvwnd`,`-ds`, `-ps`, these parameters affect this *high watermark*, the larger the value, the bigger the memory consumption will be.
`-smuxbuf` also affects the maximum memory consumption, this parameter maintains a subtle balance between *concurrency* and *resource*, you can increase this value(default 4MB) to boost concurrency if you have many clients to serve and you get a powerful server at the same time, and also you can decrease this value to serve only 1 or 2 clients and hope this program can run under some embeded SoC system with limited memory and only you can access. (Notice that the `-smuxbuf` value is not proprotional to concurrency, you need to test.)
`-smuxbuf` also affects the maximum memory consumption, this parameter maintains a subtle balance between *concurrency* and *resource*, you can increase this value(default 4MB) to boost concurrency if you have many clients to serve and you get a powerful server at the same time, and also you can decrease this value to serve only 1 or 2 clients and hope this program can run under some embedded SoC system with limited memory and only you can access. (Notice that the `-smuxbuf` value is not proprotional to concurrency, you need to test.)
#### Compression
@@ -357,13 +355,11 @@ Low-level KCP configuration can be altered by using manual mode like above, make
### Identical Parmeters
The parameters below **MUST** be **IDENTICAL** on **BOTH** side:
These parameters **MUST** be **IDENTICAL** on **BOTH** side:
1. -key
1. -crypt
1. -nocomp
1. -datashard
1. -parityshard
1. -smuxver
### References
+6
View File
@@ -57,6 +57,12 @@ for os in ${OSES[@]}; do
$sum kcptun-${os}-386-$VERSION.tar.gz
done
#Apple M1 device
env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -mod=vendor -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o server_darwin_arm64 github.com/xtaci/kcptun/server
env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -mod=vendor -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o client_darwin_arm64 github.com/xtaci/kcptun/client
tar -zcf kcptun-darwin-arm64-$VERSION.tar.gz client_darwin_arm64 server_darwin_arm64
$sum kcptun-darwin-arm64-$VERSION.tar.gz
# ARM
ARMS=(5 6 7)
for v in ${ARMS[@]}; do
+27 -1
View File
@@ -1,13 +1,39 @@
package main
import (
"strings"
"github.com/pkg/errors"
"github.com/tarm/serial"
kcp "github.com/xtaci/kcp-go/v5"
"github.com/xtaci/kcptun/generic"
"github.com/xtaci/serialpacket"
"github.com/xtaci/tcpraw"
)
func dial(config *Config, block kcp.BlockCrypt) (*kcp.UDPSession, error) {
if config.TCP {
// serial://NAME
if strings.HasPrefix(config.RemoteAddr, generic.SERIAL_PROTO) {
c, err := generic.ParseSerialParams(config.RemoteAddr)
if err != nil {
return nil, err
}
// open serial
s, err := serial.OpenPort(c)
if err != nil {
return nil, err
}
// wrapp to net.PacketConn
serialConn, err := serialpacket.NewConn(s)
if err != nil {
return nil, err
}
// kcp conn over serial
return kcp.NewConn("", block, config.DataShard, config.ParityShard, serialConn)
} else if config.TCP {
conn, err := tcpraw.Dial("tcp", config.RemoteAddr)
if err != nil {
return nil, errors.Wrap(err, "tcpraw.Dial()")
+41 -32
View File
@@ -73,6 +73,11 @@ func checkError(err error) {
}
}
type timedSession struct {
session *smux.Session
expiryDate time.Time
}
func main() {
rand.Seed(int64(time.Now().Nanosecond()))
if VERSION == "SELFBUILD" {
@@ -124,7 +129,7 @@ func main() {
cli.IntFlag{
Name: "scavengettl",
Value: 600,
Usage: "set how long an expired connection can live(in sec), -1 to disable",
Usage: "set how long an expired connection can live (in seconds)",
},
cli.IntFlag{
Name: "mtu",
@@ -421,20 +426,16 @@ func main() {
}
}
numconn := uint16(config.Conn)
muxes := make([]struct {
session *smux.Session
ttl time.Time
}, numconn)
for k := range muxes {
muxes[k].session = waitConn()
muxes[k].ttl = time.Now().Add(time.Duration(config.AutoExpire) * time.Second)
}
chScavenger := make(chan *smux.Session, 128)
go scavenger(chScavenger, config.ScavengeTTL)
// start snmp logger
go generic.SnmpLogger(config.SnmpLog, config.SnmpPeriod)
// start scavenger
chScavenger := make(chan timedSession, 128)
go scavenger(chScavenger, &config)
// start listener
numconn := uint16(config.Conn)
muxes := make([]timedSession, numconn)
rr := uint16(0)
for {
p1, err := listener.AcceptTCP()
@@ -444,10 +445,13 @@ func main() {
idx := rr % numconn
// do auto expiration && reconnection
if muxes[idx].session.IsClosed() || (config.AutoExpire > 0 && time.Now().After(muxes[idx].ttl)) {
chScavenger <- muxes[idx].session
if muxes[idx].session == nil || muxes[idx].session.IsClosed() ||
(config.AutoExpire > 0 && time.Now().After(muxes[idx].expiryDate)) {
muxes[idx].session = waitConn()
muxes[idx].ttl = time.Now().Add(time.Duration(config.AutoExpire) * time.Second)
muxes[idx].expiryDate = time.Now().Add(time.Duration(config.AutoExpire) * time.Second)
if config.AutoExpire > 0 { // only when autoexpire set
chScavenger <- muxes[idx]
}
}
go handleClient(muxes[idx].session, p1, config.Quiet)
@@ -457,30 +461,35 @@ func main() {
myApp.Run(os.Args)
}
type scavengeSession struct {
session *smux.Session
ts time.Time
}
func scavenger(ch chan timedSession, config *Config) {
// When AutoExpire is set to 0 (default), sessionList will keep empty.
// Then this routine won't need to do anything; thus just terminate it.
if config.AutoExpire <= 0 {
return
}
func scavenger(ch chan *smux.Session, ttl int) {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
var sessionList []scavengeSession
var sessionList []timedSession
for {
select {
case sess := <-ch:
sessionList = append(sessionList, scavengeSession{sess, time.Now()})
log.Println("session marked as expired", sess.RemoteAddr())
case item := <-ch:
sessionList = append(sessionList, timedSession{
item.session,
item.expiryDate.Add(time.Duration(config.ScavengeTTL) * time.Second)})
case <-ticker.C:
var newList []scavengeSession
if len(sessionList) == 0 {
continue
}
var newList []timedSession
for k := range sessionList {
s := sessionList[k]
if s.session.NumStreams() == 0 || s.session.IsClosed() {
log.Println("session normally closed", s.session.RemoteAddr())
s.session.Close()
} else if ttl >= 0 && time.Since(s.ts) >= time.Duration(ttl)*time.Second {
log.Println("session reached scavenge ttl", s.session.RemoteAddr())
if s.session.IsClosed() {
log.Println("scavenger: session normally closed:", s.session.LocalAddr())
} else if time.Now().After(s.expiryDate) {
s.session.Close()
log.Println("scavenger: session closed due to ttl:", s.session.LocalAddr())
} else {
newList = append(newList, sessionList[k])
}
+1
View File
@@ -1,3 +1,4 @@
[unit]
Description=kcptun
Wants=network.target
+38
View File
@@ -0,0 +1,38 @@
package generic
import (
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/tarm/serial"
)
const (
// serial@name@baud
// eg:
// serial@/dev/ttys002@9600
// serial@COM4@9600
SERIAL_PROTO = "serial@"
)
func ParseSerialParams(addr string) (*serial.Config, error) {
// serial://NAME
strs := strings.Split(addr, "@")
if len(strs) < 3 {
return nil, errors.Errorf("serial format error:%v", addr)
}
port := strs[1]
// parse baud
baud, err := strconv.Atoi(strs[2])
if err != nil {
return nil, err
}
config := new(serial.Config)
config.Name = port
config.Baud = baud
return config, nil
}
+6 -3
View File
@@ -5,11 +5,14 @@ require (
github.com/golang/snappy v0.0.1
github.com/google/gopacket v1.1.17 // indirect
github.com/pkg/errors v0.9.1
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
github.com/urfave/cli v1.21.0
github.com/xtaci/kcp-go/v5 v5.5.17
github.com/xtaci/smux v1.5.14
github.com/xtaci/kcp-go/v5 v5.6.1
github.com/xtaci/serialpacket v0.1.1
github.com/xtaci/smux v1.5.16
github.com/xtaci/tcpraw v1.2.25
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
golang.org/x/sys v0.0.0-20220327210214-530d0810a4d0 // indirect
)
go 1.14
+36 -8
View File
@@ -1,6 +1,8 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/coreos/go-iptables v0.4.2 h1:KH0EwId05JwWIfb96gWvkiT2cbuOu8ygqUaB+yPAwIg=
github.com/coreos/go-iptables v0.4.2/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY=
@@ -14,7 +16,13 @@ github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 h1:ULR/QWMgcgRiZLU
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/templexxx/cpu v0.0.1 h1:hY4WdLOgKdc8y13EYklu9OUTXik80BkxHoWvTO6MQQY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/cpu v0.0.7 h1:pUEZn8JBy/w5yzdYWgx+0m0xL9uk6j4K91C5kOViAzo=
github.com/templexxx/cpu v0.0.7/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
@@ -24,12 +32,18 @@ github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM=
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/urfave/cli v1.21.0 h1:wYSSj06510qPIzGSua9ZqsncMmWE3Zr55KBERygyrxE=
github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ=
github.com/xtaci/kcp-go/v5 v5.5.17 h1:bkdaqtER0PMlP05BBHfu6W+71kt/NwbAk93KH7F78Ck=
github.com/xtaci/kcp-go/v5 v5.5.17/go.mod h1:pVx3jb4LT5edTmPayc77tIU9nRsjGck8wep5ZV/RBO0=
github.com/xtaci/kcp-go/v5 v5.6.1 h1:Pwn0aoeNSPF9dTS7IgiPXn0HEtaIlVb6y5UKWPsx8bI=
github.com/xtaci/kcp-go/v5 v5.6.1/go.mod h1:W3kVPyNYwZ06p79dNwFWQOVFrdcBpDBsdyvK8moQrYo=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
github.com/xtaci/smux v1.5.14 h1:1j+zJYDZRv9FHaWqCJfH5RPizIm0fSzJIFbfVn8zsfg=
github.com/xtaci/smux v1.5.14/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
github.com/xtaci/serialpacket v0.0.0-20220328062137-d60850dbd37f h1:qnFaiLrOPHZEwcpIesZmIljcaZedg/6LWKMLFLlkkVI=
github.com/xtaci/serialpacket v0.0.0-20220328062137-d60850dbd37f/go.mod h1:ZGGlOTneyNdYs8rDz+1b6kb6Hd+49eHY21WeNXlCZyg=
github.com/xtaci/serialpacket v0.1.0 h1:QbrsrY6zVGSYPWPDr/iZWLDwUz2QZO/qpUiytYgoeJo=
github.com/xtaci/serialpacket v0.1.0/go.mod h1:ZGGlOTneyNdYs8rDz+1b6kb6Hd+49eHY21WeNXlCZyg=
github.com/xtaci/serialpacket v0.1.1 h1:IF/hvhtMyk8RlcdJJ3ujlCRm9oZf5OrYJPMgl+lUFxM=
github.com/xtaci/serialpacket v0.1.1/go.mod h1:ZGGlOTneyNdYs8rDz+1b6kb6Hd+49eHY21WeNXlCZyg=
github.com/xtaci/smux v1.5.16 h1:FBPYOkW8ZTjLKUM4LI4xnnuuDC8CQ/dB04HD519WoEk=
github.com/xtaci/smux v1.5.16/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
github.com/xtaci/tcpraw v1.2.25 h1:VDlqo0op17JeXBM6e2G9ocCNLOJcw9mZbobMbJjo0vk=
github.com/xtaci/tcpraw v1.2.25/go.mod h1:dKyZ2V75s0cZ7cbgJYdxPvms7af0joIeOyx1GgJQbLk=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -39,8 +53,9 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -48,8 +63,9 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -57,9 +73,18 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9 h1:yi1hN8dcqI9l8klZfy4B8mJvFmmAxJEePIQQFNSd7Cs=
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220325203850-36772127a21f h1:TrmogKRsSOxRMJbLYGrB4SBbW+LJcEllYBLME5Zk5pU=
golang.org/x/sys v0.0.0-20220325203850-36772127a21f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220327210214-530d0810a4d0 h1:G6WAvvcMaaFYQhMbC0L5ZWNExEcJ3j3yFTxx4mwOHtM=
golang.org/x/sys v0.0.0-20220327210214-530d0810a4d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123 h1:4JSJPND/+4555t1HfXYF4UEqDqiSKCgeV0+hbA8hMs4=
@@ -69,6 +94,9 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+131
View File
@@ -0,0 +1,131 @@
%global debug_package %{nil}
%global _dwz_low_mem_die_limit 0
%global __os_install_post /usr/lib/rpm/brp-compress %{nil}
Name: kcptun
Version: 20210922
Release: 1
Summary: A Stable & Secure Tunnel based on KCP with N:M multiplexing and FEC
License: MIT
URL: https://github.com/xtaci/kcptun
Source0: %{name}-%{version}.tar.gz
BuildRequires: golang
BuildRequires: glibc-static
%description
A Stable & Secure Tunnel based on KCP with N:M multiplexing and FEC
%package server
Summary: kcptun-server
Requires: systemd
%description server
A Stable & Secure Tunnel based on KCP with N:M multiplexing and FEC
%package client
Summary: kcptun-client
Requires: systemd
%description client
A Stable & Secure Tunnel based on KCP with N:M multiplexing and FEC
%prep
%autosetup
%build
LDFLAGS='-s -w -linkmode=external -extldflags -static -X main.VERSION=%{version}'
go build -ldflags "$LDFLAGS" -o %{name}-server github.com/xtaci/kcptun/server
go build -ldflags "$LDFLAGS" -o %{name}-client github.com/xtaci/kcptun/client
%install
rm -rf $RPM_BUILD_ROOT
%{__mkdir} -p $RPM_BUILD_ROOT%{_bindir}
%{__install} -p -m 755 kcptun-server $RPM_BUILD_ROOT%{_bindir}/kcptun-server
%{__install} -p -m 755 kcptun-client $RPM_BUILD_ROOT%{_bindir}/kcptun-client
%{__mkdir} -p $RPM_BUILD_ROOT%{_sysconfdir}/%{name}
%{__install} -p -m 644 examples/server.json $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/server.json
%{__install} -p -m 644 examples/local.json $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/client.json
%{__mkdir} -p $RPM_BUILD_ROOT%{_unitdir}
cat > $RPM_BUILD_ROOT%{_unitdir}/kcptun-server.service <<EOF
[Unit]
Description=kcptun-server
Wants=network.target
After=syslog.target network-online.target
ConditionFileIsExecutable=/usr/bin/kcptun-server
ConditionPathExists=/etc/kcptun/server.json
[Service]
Type=simple
Environment=GOGC=20
ExecStart=/usr/bin/kcptun-server -c /etc/kcptun/server.json
Restart=on-failure
RestartSec=10
KillMode=process
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
cat > $RPM_BUILD_ROOT%{_unitdir}/kcptun-client.service <<EOF
[Unit]
Description=kcptun-client
After=syslog.target network-online.target
ConditionFileIsExecutable=/usr/bin/kcptun-client
ConditionPathExists=/etc/kcptun/client.json
[Service]
Type=simple
Environment=GOGC=20
ExecStart=/usr/bin/kcptun-client -c /etc/kcptun/client.json
Restart=on-failure
RestartSec=10
KillMode=process
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
%post server
%systemd_post %{name}-server.service
%preun server
%systemd_preun %{name}-server.service
%postun server
%systemd_postun_with_restart %{name}-server.service
%post client
%systemd_post %{name}-client.service
%preun client
%systemd_preun %{name}-client.service
%postun client
%systemd_postun_with_restart %{name}-client.service
%files server
%defattr(-,root,root,-)
%{_bindir}/kcptun-server
%config(noreplace) %{_sysconfdir}/%{name}/server.json
%{_unitdir}/kcptun-server.service
%license LICENSE.md
%doc README.md Dockerfile
%files client
%defattr(-,root,root,-)
%{_bindir}/kcptun-client
%config(noreplace) %{_sysconfdir}/%{name}/client.json
%{_unitdir}/kcptun-client.service
%license LICENSE.md
%doc README.md Dockerfile
%changelog
* Thu Dec 30 2021 Purple Grape <purplegrape4@gmail.com>
- First package for kcptun
+35 -13
View File
@@ -10,14 +10,17 @@ import (
"net/http"
_ "net/http/pprof"
"os"
"strings"
"sync"
"time"
"golang.org/x/crypto/pbkdf2"
"github.com/tarm/serial"
"github.com/urfave/cli"
kcp "github.com/xtaci/kcp-go/v5"
"github.com/xtaci/kcptun/generic"
"github.com/xtaci/serialpacket"
"github.com/xtaci/smux"
"github.com/xtaci/tcpraw"
)
@@ -430,22 +433,41 @@ func main() {
}
}
if config.TCP { // tcp dual stack
if conn, err := tcpraw.Listen("tcp", config.Listen); err == nil {
lis, err := kcp.ServeConn(block, config.DataShard, config.ParityShard, conn)
checkError(err)
wg.Add(1)
go loop(lis)
} else {
log.Println(err)
}
var listener *kcp.Listener
if strings.HasPrefix(config.Listen, generic.SERIAL_PROTO) {
c, err := generic.ParseSerialParams(config.Listen)
checkError(err)
// open serial
s, err := serial.OpenPort(c)
checkError(err)
// wrapp to net.PacketConn
serialConn, err := serialpacket.NewConn(s)
checkError(err)
// kcp conn over serial
lis, err := kcp.ServeConn(block, config.DataShard, config.ParityShard, serialConn)
checkError(err)
listener = lis
} else if config.TCP { // tcp dual stack
conn, err := tcpraw.Listen("tcp", config.Listen)
checkError(err)
lis, err := kcp.ServeConn(block, config.DataShard, config.ParityShard, conn)
checkError(err)
listener = lis
} else {
// udp stack
lis, err := kcp.ListenWithOptions(config.Listen, block, config.DataShard, config.ParityShard)
checkError(err)
listener = lis
}
// udp stack
lis, err := kcp.ListenWithOptions(config.Listen, block, config.DataShard, config.ParityShard)
checkError(err)
wg.Add(1)
go loop(lis)
go loop(listener)
wg.Wait()
return nil
}
+26
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,3 @@
module github.com/klauspost/cpuid
go 1.12
+408
View File
@@ -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)
File diff suppressed because it is too large Load Diff
+11
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,7 @@
module github.com/klauspost/reedsolomon
go 1.14
require (
github.com/klauspost/cpuid v1.2.4
)
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,2 @@
// Package build provides an assembly-like interface for incremental building of avo Files.
package build
+88
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
File diff suppressed because it is too large Load Diff
+72
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,2 @@
// Package gotypes provides helpers for interacting with Go types within avo functions.
package gotypes
+177
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,2 @@
// Package ir provides the intermediate representation of avo programs.
package ir
+355
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,2 @@
// Package operand provides types for instruction operands.
package operand
+151
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 Developers 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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,2 @@
// Package reg provides types for physical and virtual registers, and definitions of x86-64 register families.
package reg
+112
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,2 @@
// Package x86 provides constructors for all x86-64 instructions.
package x86
+4
View File
@@ -0,0 +1,4 @@
package x86
//go:generate avogen -output zctors.go ctors
//go:generate avogen -output zctors_test.go ctorstest
+34629
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
sudo: false
language: go
go:
- "1.9"
- "1.10"
- "1.11"
- tip
env:
- GOOS=linux CGO=1
- GOOS=linux CGO=0
- GOOS=linux GOARCH=arm
- GOOS=linux GOARCH=mips
- GOOS=linux GOARCH=mipsle
- GOOS=windows GOARCH=386
+27
View File
@@ -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.
+82
View File
@@ -0,0 +1,82 @@
[![GoDoc](https://godoc.org/github.com/tarm/serial?status.svg)](http://godoc.org/github.com/tarm/serial)
[![Build Status](https://travis-ci.org/tarm/serial.svg?branch=master)](https://travis-ci.org/tarm/serial)
Serial
========
A Go package to allow you to read and write from the
serial port as a stream of bytes.
Details
-------
It aims to have the same API on all platforms, including windows. As
an added bonus, the windows package does not use cgo, so you can cross
compile for windows from another platform.
You can cross compile with
GOOS=windows GOARCH=386 go install github.com/tarm/serial
Currently there is very little in the way of configurability. You can
set the baud rate. Then you can Read(), Write(), or Close() the
connection. By default Read() will block until at least one byte is
returned. Write is the same.
Currently all ports are opened with 8 data bits, 1 stop bit, no
parity, no hardware flow control, and no software flow control. This
works fine for many real devices and many faux serial devices
including usb-to-serial converters and bluetooth serial ports.
You may Read() and Write() simulantiously on the same connection (from
different goroutines).
Usage
-----
```go
package main
import (
"log"
"github.com/tarm/serial"
)
func main() {
c := &serial.Config{Name: "COM45", Baud: 115200}
s, err := serial.OpenPort(c)
if err != nil {
log.Fatal(err)
}
n, err := s.Write([]byte("test"))
if err != nil {
log.Fatal(err)
}
buf := make([]byte, 128)
n, err = s.Read(buf)
if err != nil {
log.Fatal(err)
}
log.Printf("%q", buf[:n])
}
```
NonBlocking Mode
----------------
By default the returned Port reads in blocking mode. Which means
`Read()` will block until at least one byte is returned. If that's not
what you want, specify a positive ReadTimeout and the Read() will
timeout returning 0 bytes if no bytes are read. Please note that this
is the total timeout the read operation will wait and not the interval
timeout between two bytes.
```go
c := &serial.Config{Name: "COM45", Baud: 115200, ReadTimeout: time.Second * 5}
// In this mode, you will want to suppress error for read
// as 0 bytes return EOF error on Linux / POSIX
n, _ = s.Read(buf)
```
Possible Future Work
--------------------
- better tests (loopback etc)
+167
View File
@@ -0,0 +1,167 @@
/*
Goserial is a simple go package to allow you to read and write from
the serial port as a stream of bytes.
It aims to have the same API on all platforms, including windows. As
an added bonus, the windows package does not use cgo, so you can cross
compile for windows from another platform. Unfortunately goinstall
does not currently let you cross compile so you will have to do it
manually:
GOOS=windows make clean install
Currently there is very little in the way of configurability. You can
set the baud rate. Then you can Read(), Write(), or Close() the
connection. Read() will block until at least one byte is returned.
Write is the same. There is currently no exposed way to set the
timeouts, though patches are welcome.
Currently all ports are opened with 8 data bits, 1 stop bit, no
parity, no hardware flow control, and no software flow control. This
works fine for many real devices and many faux serial devices
including usb-to-serial converters and bluetooth serial ports.
You may Read() and Write() simulantiously on the same connection (from
different goroutines).
Example usage:
package main
import (
"github.com/tarm/serial"
"log"
)
func main() {
c := &serial.Config{Name: "COM5", Baud: 115200}
s, err := serial.OpenPort(c)
if err != nil {
log.Fatal(err)
}
n, err := s.Write([]byte("test"))
if err != nil {
log.Fatal(err)
}
buf := make([]byte, 128)
n, err = s.Read(buf)
if err != nil {
log.Fatal(err)
}
log.Print("%q", buf[:n])
}
*/
package serial
import (
"errors"
"time"
)
const DefaultSize = 8 // Default value for Config.Size
type StopBits byte
type Parity byte
const (
Stop1 StopBits = 1
Stop1Half StopBits = 15
Stop2 StopBits = 2
)
const (
ParityNone Parity = 'N'
ParityOdd Parity = 'O'
ParityEven Parity = 'E'
ParityMark Parity = 'M' // parity bit is always 1
ParitySpace Parity = 'S' // parity bit is always 0
)
// Config contains the information needed to open a serial port.
//
// Currently few options are implemented, but more may be added in the
// future (patches welcome), so it is recommended that you create a
// new config addressing the fields by name rather than by order.
//
// For example:
//
// c0 := &serial.Config{Name: "COM45", Baud: 115200, ReadTimeout: time.Millisecond * 500}
// or
// c1 := new(serial.Config)
// c1.Name = "/dev/tty.usbserial"
// c1.Baud = 115200
// c1.ReadTimeout = time.Millisecond * 500
//
type Config struct {
Name string
Baud int
ReadTimeout time.Duration // Total timeout
// Size is the number of data bits. If 0, DefaultSize is used.
Size byte
// Parity is the bit to use and defaults to ParityNone (no parity bit).
Parity Parity
// Number of stop bits to use. Default is 1 (1 stop bit).
StopBits StopBits
// RTSFlowControl bool
// DTRFlowControl bool
// XONFlowControl bool
// CRLFTranslate bool
}
// ErrBadSize is returned if Size is not supported.
var ErrBadSize error = errors.New("unsupported serial data size")
// ErrBadStopBits is returned if the specified StopBits setting not supported.
var ErrBadStopBits error = errors.New("unsupported stop bit setting")
// ErrBadParity is returned if the parity is not supported.
var ErrBadParity error = errors.New("unsupported parity setting")
// OpenPort opens a serial port with the specified configuration
func OpenPort(c *Config) (*Port, error) {
size, par, stop := c.Size, c.Parity, c.StopBits
if size == 0 {
size = DefaultSize
}
if par == 0 {
par = ParityNone
}
if stop == 0 {
stop = Stop1
}
return openPort(c.Name, c.Baud, size, par, stop, c.ReadTimeout)
}
// Converts the timeout values for Linux / POSIX systems
func posixTimeoutValues(readTimeout time.Duration) (vmin uint8, vtime uint8) {
const MAXUINT8 = 1<<8 - 1 // 255
// set blocking / non-blocking read
var minBytesToRead uint8 = 1
var readTimeoutInDeci int64
if readTimeout > 0 {
// EOF on zero read
minBytesToRead = 0
// convert timeout to deciseconds as expected by VTIME
readTimeoutInDeci = (readTimeout.Nanoseconds() / 1e6 / 100)
// capping the timeout
if readTimeoutInDeci < 1 {
// min possible timeout 1 Deciseconds (0.1s)
readTimeoutInDeci = 1
} else if readTimeoutInDeci > MAXUINT8 {
// max possible timeout is 255 deciseconds (25.5s)
readTimeoutInDeci = MAXUINT8
}
}
return minBytesToRead, uint8(readTimeoutInDeci)
}
// func SendBreak()
// func RegisterBreakHandler(func())
+164
View File
@@ -0,0 +1,164 @@
// +build linux
package serial
import (
"fmt"
"os"
"time"
"unsafe"
"golang.org/x/sys/unix"
)
func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) {
var bauds = map[int]uint32{
50: unix.B50,
75: unix.B75,
110: unix.B110,
134: unix.B134,
150: unix.B150,
200: unix.B200,
300: unix.B300,
600: unix.B600,
1200: unix.B1200,
1800: unix.B1800,
2400: unix.B2400,
4800: unix.B4800,
9600: unix.B9600,
19200: unix.B19200,
38400: unix.B38400,
57600: unix.B57600,
115200: unix.B115200,
230400: unix.B230400,
460800: unix.B460800,
500000: unix.B500000,
576000: unix.B576000,
921600: unix.B921600,
1000000: unix.B1000000,
1152000: unix.B1152000,
1500000: unix.B1500000,
2000000: unix.B2000000,
2500000: unix.B2500000,
3000000: unix.B3000000,
3500000: unix.B3500000,
4000000: unix.B4000000,
}
rate, ok := bauds[baud]
if !ok {
return nil, fmt.Errorf("Unrecognized baud rate")
}
f, err := os.OpenFile(name, unix.O_RDWR|unix.O_NOCTTY|unix.O_NONBLOCK, 0666)
if err != nil {
return nil, err
}
defer func() {
if err != nil && f != nil {
f.Close()
}
}()
// Base settings
cflagToUse := unix.CREAD | unix.CLOCAL | rate
switch databits {
case 5:
cflagToUse |= unix.CS5
case 6:
cflagToUse |= unix.CS6
case 7:
cflagToUse |= unix.CS7
case 8:
cflagToUse |= unix.CS8
default:
return nil, ErrBadSize
}
// Stop bits settings
switch stopbits {
case Stop1:
// default is 1 stop bit
case Stop2:
cflagToUse |= unix.CSTOPB
default:
// Don't know how to set 1.5
return nil, ErrBadStopBits
}
// Parity settings
switch parity {
case ParityNone:
// default is no parity
case ParityOdd:
cflagToUse |= unix.PARENB
cflagToUse |= unix.PARODD
case ParityEven:
cflagToUse |= unix.PARENB
default:
return nil, ErrBadParity
}
fd := f.Fd()
vmin, vtime := posixTimeoutValues(readTimeout)
t := unix.Termios{
Iflag: unix.IGNPAR,
Cflag: cflagToUse,
Ispeed: rate,
Ospeed: rate,
}
t.Cc[unix.VMIN] = vmin
t.Cc[unix.VTIME] = vtime
if _, _, errno := unix.Syscall6(
unix.SYS_IOCTL,
uintptr(fd),
uintptr(unix.TCSETS),
uintptr(unsafe.Pointer(&t)),
0,
0,
0,
); errno != 0 {
return nil, errno
}
if err = unix.SetNonblock(int(fd), false); err != nil {
return
}
return &Port{f: f}, nil
}
type Port struct {
// We intentionly do not use an "embedded" struct so that we
// don't export File
f *os.File
}
func (p *Port) Read(b []byte) (n int, err error) {
return p.f.Read(b)
}
func (p *Port) Write(b []byte) (n int, err error) {
return p.f.Write(b)
}
// Discards data written to the port but not transmitted,
// or data received but not read
func (p *Port) Flush() error {
const TCFLSH = 0x540B
_, _, errno := unix.Syscall(
unix.SYS_IOCTL,
uintptr(p.f.Fd()),
uintptr(TCFLSH),
uintptr(unix.TCIOFLUSH),
)
if errno == 0 {
return nil
}
return errno
}
func (p *Port) Close() (err error) {
return p.f.Close()
}
+197
View File
@@ -0,0 +1,197 @@
// +build !windows,!linux,cgo
package serial
// #include <termios.h>
// #include <unistd.h>
import "C"
// TODO: Maybe change to using syscall package + ioctl instead of cgo
import (
"errors"
"fmt"
"os"
"syscall"
"time"
//"unsafe"
)
func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) {
f, err := os.OpenFile(name, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK, 0666)
if err != nil {
return
}
fd := C.int(f.Fd())
if C.isatty(fd) != 1 {
f.Close()
return nil, errors.New("File is not a tty")
}
var st C.struct_termios
_, err = C.tcgetattr(fd, &st)
if err != nil {
f.Close()
return nil, err
}
var speed C.speed_t
switch baud {
case 115200:
speed = C.B115200
case 57600:
speed = C.B57600
case 38400:
speed = C.B38400
case 19200:
speed = C.B19200
case 9600:
speed = C.B9600
case 4800:
speed = C.B4800
case 2400:
speed = C.B2400
case 1200:
speed = C.B1200
case 600:
speed = C.B600
case 300:
speed = C.B300
case 200:
speed = C.B200
case 150:
speed = C.B150
case 134:
speed = C.B134
case 110:
speed = C.B110
case 75:
speed = C.B75
case 50:
speed = C.B50
default:
f.Close()
return nil, fmt.Errorf("Unknown baud rate %v", baud)
}
_, err = C.cfsetispeed(&st, speed)
if err != nil {
f.Close()
return nil, err
}
_, err = C.cfsetospeed(&st, speed)
if err != nil {
f.Close()
return nil, err
}
// Turn off break interrupts, CR->NL, Parity checks, strip, and IXON
st.c_iflag &= ^C.tcflag_t(C.BRKINT | C.ICRNL | C.INPCK | C.ISTRIP | C.IXOFF | C.IXON | C.PARMRK)
// Select local mode, turn off parity, set to 8 bits
st.c_cflag &= ^C.tcflag_t(C.CSIZE | C.PARENB)
st.c_cflag |= (C.CLOCAL | C.CREAD)
// databits
switch databits {
case 5:
st.c_cflag |= C.CS5
case 6:
st.c_cflag |= C.CS6
case 7:
st.c_cflag |= C.CS7
case 8:
st.c_cflag |= C.CS8
default:
return nil, ErrBadSize
}
// Parity settings
switch parity {
case ParityNone:
// default is no parity
case ParityOdd:
st.c_cflag |= C.PARENB
st.c_cflag |= C.PARODD
case ParityEven:
st.c_cflag |= C.PARENB
st.c_cflag &= ^C.tcflag_t(C.PARODD)
default:
return nil, ErrBadParity
}
// Stop bits settings
switch stopbits {
case Stop1:
// as is, default is 1 bit
case Stop2:
st.c_cflag |= C.CSTOPB
default:
return nil, ErrBadStopBits
}
// Select raw mode
st.c_lflag &= ^C.tcflag_t(C.ICANON | C.ECHO | C.ECHOE | C.ISIG)
st.c_oflag &= ^C.tcflag_t(C.OPOST)
// set blocking / non-blocking read
/*
* http://man7.org/linux/man-pages/man3/termios.3.html
* - Supports blocking read and read with timeout operations
*/
vmin, vtime := posixTimeoutValues(readTimeout)
st.c_cc[C.VMIN] = C.cc_t(vmin)
st.c_cc[C.VTIME] = C.cc_t(vtime)
_, err = C.tcsetattr(fd, C.TCSANOW, &st)
if err != nil {
f.Close()
return nil, err
}
//fmt.Println("Tweaking", name)
r1, _, e := syscall.Syscall(syscall.SYS_FCNTL,
uintptr(f.Fd()),
uintptr(syscall.F_SETFL),
uintptr(0))
if e != 0 || r1 != 0 {
s := fmt.Sprint("Clearing NONBLOCK syscall error:", e, r1)
f.Close()
return nil, errors.New(s)
}
/*
r1, _, e = syscall.Syscall(syscall.SYS_IOCTL,
uintptr(f.Fd()),
uintptr(0x80045402), // IOSSIOSPEED
uintptr(unsafe.Pointer(&baud)));
if e != 0 || r1 != 0 {
s := fmt.Sprint("Baudrate syscall error:", e, r1)
f.Close()
return nil, os.NewError(s)
}
*/
return &Port{f: f}, nil
}
type Port struct {
// We intentionly do not use an "embedded" struct so that we
// don't export File
f *os.File
}
func (p *Port) Read(b []byte) (n int, err error) {
return p.f.Read(b)
}
func (p *Port) Write(b []byte) (n int, err error) {
return p.f.Write(b)
}
// Discards data written to the port but not transmitted,
// or data received but not read
func (p *Port) Flush() error {
_, err := C.tcflush(C.int(p.f.Fd()), C.TCIOFLUSH)
return err
}
func (p *Port) Close() (err error) {
return p.f.Close()
}
+327
View File
@@ -0,0 +1,327 @@
// +build windows
package serial
import (
"fmt"
"os"
"sync"
"syscall"
"time"
"unsafe"
)
type Port struct {
f *os.File
fd syscall.Handle
rl sync.Mutex
wl sync.Mutex
ro *syscall.Overlapped
wo *syscall.Overlapped
}
type structDCB struct {
DCBlength, BaudRate uint32
flags [4]byte
wReserved, XonLim, XoffLim uint16
ByteSize, Parity, StopBits byte
XonChar, XoffChar, ErrorChar, EofChar, EvtChar byte
wReserved1 uint16
}
type structTimeouts struct {
ReadIntervalTimeout uint32
ReadTotalTimeoutMultiplier uint32
ReadTotalTimeoutConstant uint32
WriteTotalTimeoutMultiplier uint32
WriteTotalTimeoutConstant uint32
}
func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) {
if len(name) > 0 && name[0] != '\\' {
name = "\\\\.\\" + name
}
h, err := syscall.CreateFile(syscall.StringToUTF16Ptr(name),
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
0,
nil,
syscall.OPEN_EXISTING,
syscall.FILE_ATTRIBUTE_NORMAL|syscall.FILE_FLAG_OVERLAPPED,
0)
if err != nil {
return nil, err
}
f := os.NewFile(uintptr(h), name)
defer func() {
if err != nil {
f.Close()
}
}()
if err = setCommState(h, baud, databits, parity, stopbits); err != nil {
return nil, err
}
if err = setupComm(h, 64, 64); err != nil {
return nil, err
}
if err = setCommTimeouts(h, readTimeout); err != nil {
return nil, err
}
if err = setCommMask(h); err != nil {
return nil, err
}
ro, err := newOverlapped()
if err != nil {
return nil, err
}
wo, err := newOverlapped()
if err != nil {
return nil, err
}
port := new(Port)
port.f = f
port.fd = h
port.ro = ro
port.wo = wo
return port, nil
}
func (p *Port) Close() error {
return p.f.Close()
}
func (p *Port) Write(buf []byte) (int, error) {
p.wl.Lock()
defer p.wl.Unlock()
if err := resetEvent(p.wo.HEvent); err != nil {
return 0, err
}
var n uint32
err := syscall.WriteFile(p.fd, buf, &n, p.wo)
if err != nil && err != syscall.ERROR_IO_PENDING {
return int(n), err
}
return getOverlappedResult(p.fd, p.wo)
}
func (p *Port) Read(buf []byte) (int, error) {
if p == nil || p.f == nil {
return 0, fmt.Errorf("Invalid port on read")
}
p.rl.Lock()
defer p.rl.Unlock()
if err := resetEvent(p.ro.HEvent); err != nil {
return 0, err
}
var done uint32
err := syscall.ReadFile(p.fd, buf, &done, p.ro)
if err != nil && err != syscall.ERROR_IO_PENDING {
return int(done), err
}
return getOverlappedResult(p.fd, p.ro)
}
// Discards data written to the port but not transmitted,
// or data received but not read
func (p *Port) Flush() error {
return purgeComm(p.fd)
}
var (
nSetCommState,
nSetCommTimeouts,
nSetCommMask,
nSetupComm,
nGetOverlappedResult,
nCreateEvent,
nResetEvent,
nPurgeComm,
nFlushFileBuffers uintptr
)
func init() {
k32, err := syscall.LoadLibrary("kernel32.dll")
if err != nil {
panic("LoadLibrary " + err.Error())
}
defer syscall.FreeLibrary(k32)
nSetCommState = getProcAddr(k32, "SetCommState")
nSetCommTimeouts = getProcAddr(k32, "SetCommTimeouts")
nSetCommMask = getProcAddr(k32, "SetCommMask")
nSetupComm = getProcAddr(k32, "SetupComm")
nGetOverlappedResult = getProcAddr(k32, "GetOverlappedResult")
nCreateEvent = getProcAddr(k32, "CreateEventW")
nResetEvent = getProcAddr(k32, "ResetEvent")
nPurgeComm = getProcAddr(k32, "PurgeComm")
nFlushFileBuffers = getProcAddr(k32, "FlushFileBuffers")
}
func getProcAddr(lib syscall.Handle, name string) uintptr {
addr, err := syscall.GetProcAddress(lib, name)
if err != nil {
panic(name + " " + err.Error())
}
return addr
}
func setCommState(h syscall.Handle, baud int, databits byte, parity Parity, stopbits StopBits) error {
var params structDCB
params.DCBlength = uint32(unsafe.Sizeof(params))
params.flags[0] = 0x01 // fBinary
params.flags[0] |= 0x10 // Assert DSR
params.BaudRate = uint32(baud)
params.ByteSize = databits
switch parity {
case ParityNone:
params.Parity = 0
case ParityOdd:
params.Parity = 1
case ParityEven:
params.Parity = 2
case ParityMark:
params.Parity = 3
case ParitySpace:
params.Parity = 4
default:
return ErrBadParity
}
switch stopbits {
case Stop1:
params.StopBits = 0
case Stop1Half:
params.StopBits = 1
case Stop2:
params.StopBits = 2
default:
return ErrBadStopBits
}
r, _, err := syscall.Syscall(nSetCommState, 2, uintptr(h), uintptr(unsafe.Pointer(&params)), 0)
if r == 0 {
return err
}
return nil
}
func setCommTimeouts(h syscall.Handle, readTimeout time.Duration) error {
var timeouts structTimeouts
const MAXDWORD = 1<<32 - 1
// blocking read by default
var timeoutMs int64 = MAXDWORD - 1
if readTimeout > 0 {
// non-blocking read
timeoutMs = readTimeout.Nanoseconds() / 1e6
if timeoutMs < 1 {
timeoutMs = 1
} else if timeoutMs > MAXDWORD-1 {
timeoutMs = MAXDWORD - 1
}
}
/* From http://msdn.microsoft.com/en-us/library/aa363190(v=VS.85).aspx
For blocking I/O see below:
Remarks:
If an application sets ReadIntervalTimeout and
ReadTotalTimeoutMultiplier to MAXDWORD and sets
ReadTotalTimeoutConstant to a value greater than zero and
less than MAXDWORD, one of the following occurs when the
ReadFile function is called:
If there are any bytes in the input buffer, ReadFile returns
immediately with the bytes in the buffer.
If there are no bytes in the input buffer, ReadFile waits
until a byte arrives and then returns immediately.
If no bytes arrive within the time specified by
ReadTotalTimeoutConstant, ReadFile times out.
*/
timeouts.ReadIntervalTimeout = MAXDWORD
timeouts.ReadTotalTimeoutMultiplier = MAXDWORD
timeouts.ReadTotalTimeoutConstant = uint32(timeoutMs)
r, _, err := syscall.Syscall(nSetCommTimeouts, 2, uintptr(h), uintptr(unsafe.Pointer(&timeouts)), 0)
if r == 0 {
return err
}
return nil
}
func setupComm(h syscall.Handle, in, out int) error {
r, _, err := syscall.Syscall(nSetupComm, 3, uintptr(h), uintptr(in), uintptr(out))
if r == 0 {
return err
}
return nil
}
func setCommMask(h syscall.Handle) error {
const EV_RXCHAR = 0x0001
r, _, err := syscall.Syscall(nSetCommMask, 2, uintptr(h), EV_RXCHAR, 0)
if r == 0 {
return err
}
return nil
}
func resetEvent(h syscall.Handle) error {
r, _, err := syscall.Syscall(nResetEvent, 1, uintptr(h), 0, 0)
if r == 0 {
return err
}
return nil
}
func purgeComm(h syscall.Handle) error {
const PURGE_TXABORT = 0x0001
const PURGE_RXABORT = 0x0002
const PURGE_TXCLEAR = 0x0004
const PURGE_RXCLEAR = 0x0008
r, _, err := syscall.Syscall(nPurgeComm, 2, uintptr(h),
PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR, 0)
if r == 0 {
return err
}
return nil
}
func newOverlapped() (*syscall.Overlapped, error) {
var overlapped syscall.Overlapped
r, _, err := syscall.Syscall6(nCreateEvent, 4, 0, 1, 0, 0, 0, 0)
if r == 0 {
return nil, err
}
overlapped.HEvent = syscall.Handle(r)
return &overlapped, nil
}
func getOverlappedResult(h syscall.Handle, overlapped *syscall.Overlapped) (int, error) {
var n int
r, _, err := syscall.Syscall6(nGetOverlappedResult, 4,
uintptr(h),
uintptr(unsafe.Pointer(overlapped)),
uintptr(unsafe.Pointer(&n)), 1, 0, 0)
if r == 0 {
return n, err
}
return n, nil
}
+1 -1
View File
@@ -14,7 +14,7 @@ install:
- go get github.com/xtaci/kcp-go
script:
- go test -coverprofile=coverage.txt -covermode=atomic -bench .
- go test -coverprofile=coverage.txt -covermode=atomic -bench . -timeout 10m
after_success:
- bash <(curl -s https://codecov.io/bash)
+64
View File
@@ -0,0 +1,64 @@
package kcp
const maxAutoTuneSamples = 258
// pulse represents a 0/1 signal with time sequence
type pulse struct {
bit bool // 0 or 1
seq uint32 // sequence of the signal
}
// autoTune object
type autoTune struct {
pulses [maxAutoTuneSamples]pulse
}
// Sample adds a signal sample to the pulse buffer
func (tune *autoTune) Sample(bit bool, seq uint32) {
tune.pulses[seq%maxAutoTuneSamples] = pulse{bit, seq}
}
// Find a period for a given signal
// returns -1 if not found
//
// --- ------
// | |
// |______________|
// Period
// Falling Edge Rising Edge
func (tune *autoTune) FindPeriod(bit bool) int {
// last pulse and initial index setup
lastPulse := tune.pulses[0]
idx := 1
// left edge
var leftEdge int
for ; idx < len(tune.pulses); idx++ {
if lastPulse.bit != bit && tune.pulses[idx].bit == bit { // edge found
if lastPulse.seq+1 == tune.pulses[idx].seq { // ensure edge continuity
leftEdge = idx
break
}
}
lastPulse = tune.pulses[idx]
}
// right edge
var rightEdge int
lastPulse = tune.pulses[leftEdge]
idx = leftEdge + 1
for ; idx < len(tune.pulses); idx++ {
if lastPulse.seq+1 == tune.pulses[idx].seq { // ensure pulses in this level monotonic
if lastPulse.bit == bit && tune.pulses[idx].bit != bit { // edge found
rightEdge = idx
break
}
} else {
return -1
}
lastPulse = tune.pulses[idx]
}
return rightEdge - leftEdge
}
+49 -5
View File
@@ -13,6 +13,7 @@ const (
typeData = 0xf1
typeParity = 0xf2
fecExpire = 60000
rxFECMulti = 3 // FEC keeps rxFECMulti* (dataShard+parityShard) ordered packets in memory
)
// fecPacket is a decoded FEC packet
@@ -45,21 +46,21 @@ type fecDecoder struct {
// RS decoder
codec reedsolomon.Encoder
// auto tune fec parameter
autoTune autoTune
}
func newFECDecoder(rxlimit, dataShards, parityShards int) *fecDecoder {
func newFECDecoder(dataShards, parityShards int) *fecDecoder {
if dataShards <= 0 || parityShards <= 0 {
return nil
}
if rxlimit < dataShards+parityShards {
return nil
}
dec := new(fecDecoder)
dec.rxlimit = rxlimit
dec.dataShards = dataShards
dec.parityShards = parityShards
dec.shardSize = dataShards + parityShards
dec.rxlimit = rxFECMulti * dec.shardSize
codec, err := reedsolomon.New(dataShards, parityShards)
if err != nil {
return nil
@@ -73,6 +74,49 @@ func newFECDecoder(rxlimit, dataShards, parityShards int) *fecDecoder {
// decode a fec packet
func (dec *fecDecoder) decode(in fecPacket) (recovered [][]byte) {
// sample to auto FEC tuner
if in.flag() == typeData {
dec.autoTune.Sample(true, in.seqid())
} else {
dec.autoTune.Sample(false, in.seqid())
}
// check if FEC parameters is out of sync
var shouldTune bool
if int(in.seqid())%dec.shardSize < dec.dataShards {
if in.flag() != typeData { // expect typeData
shouldTune = true
}
} else {
if in.flag() != typeParity {
shouldTune = true
}
}
if shouldTune {
autoDS := dec.autoTune.FindPeriod(true)
autoPS := dec.autoTune.FindPeriod(false)
// edges found, we can tune parameters now
if autoDS > 0 && autoPS > 0 && autoDS < 256 && autoPS < 256 {
// and make sure it's different
if autoDS != dec.dataShards || autoPS != dec.parityShards {
dec.dataShards = autoDS
dec.parityShards = autoPS
dec.shardSize = autoDS + autoPS
dec.rxlimit = rxFECMulti * dec.shardSize
codec, err := reedsolomon.New(autoDS, autoPS)
if err != nil {
return nil
}
dec.codec = codec
dec.decodeCache = make([][]byte, dec.shardSize)
dec.flagCache = make([]bool, dec.shardSize)
//log.Println("autotune to :", dec.dataShards, dec.parityShards)
}
}
}
// insertion
n := len(dec.rx) - 1
insertIdx := 0
+1
View File
@@ -5,6 +5,7 @@ require (
github.com/klauspost/reedsolomon v1.9.9
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 // indirect
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.6.1
github.com/templexxx/cpu v0.0.7 // indirect
github.com/templexxx/xorsimd v0.4.1
github.com/tjfoc/gmsm v1.3.2
+11
View File
@@ -1,3 +1,5 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
@@ -7,6 +9,11 @@ github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 h1:ULR/QWMgcgRiZLU
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/templexxx/cpu v0.0.1 h1:hY4WdLOgKdc8y13EYklu9OUTXik80BkxHoWvTO6MQQY=
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/cpu v0.0.7 h1:pUEZn8JBy/w5yzdYWgx+0m0xL9uk6j4K91C5kOViAzo=
@@ -55,4 +62,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IV
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+2 -2
View File
@@ -34,8 +34,8 @@ const (
// monotonic reference time point
var refTime time.Time = time.Now()
// currentMs returns current elasped monotonic milliseconds since program startup
func currentMs() uint32 { return uint32(time.Now().Sub(refTime) / time.Millisecond) }
// currentMs returns current elapsed monotonic milliseconds since program startup
func currentMs() uint32 { return uint32(time.Since(refTime) / time.Millisecond) }
// output_callback is a prototype which ought capture conn and call conn.Write
type output_callback func(buf []byte, size int)
+2 -11
View File
@@ -18,12 +18,7 @@ func (s *UDPSession) defaultReadLoop() {
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
continue
}
if n >= s.headerSize+IKCP_OVERHEAD {
s.packetInput(buf[:n])
} else {
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
}
s.packetInput(buf[:n])
} else {
s.notifyReadError(errors.WithStack(err))
return
@@ -35,11 +30,7 @@ func (l *Listener) defaultMonitor() {
buf := make([]byte, mtuLimit)
for {
if n, from, err := l.conn.ReadFrom(buf); err == nil {
if n >= l.headerSize+IKCP_OVERHEAD {
l.packetInput(buf[:n], from)
} else {
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
}
l.packetInput(buf[:n], from)
} else {
l.notifyReadError(errors.WithStack(err))
return
+1 -10
View File
@@ -39,11 +39,6 @@ func (s *UDPSession) readLoop() {
continue
}
if msg.N < s.headerSize+IKCP_OVERHEAD {
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
continue
}
// source and size has validated
s.packetInput(msg.Buffers[0][:msg.N])
}
@@ -95,11 +90,7 @@ func (l *Listener) monitor() {
if count, err := xconn.ReadBatch(msgs, 0); err == nil {
for i := 0; i < count; i++ {
msg := &msgs[i]
if msg.N >= l.headerSize+IKCP_OVERHEAD {
l.packetInput(msg.Buffers[0][:msg.N], msg.Addr)
} else {
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
}
l.packetInput(msg.Buffers[0][:msg.N], msg.Addr)
}
} else {
// compatibility issue:
+68 -81
View File
@@ -35,9 +35,6 @@ const (
// maximum packet size
mtuLimit = 1500
// FEC keeps rxFECMulti* (dataShard+parityShard) ordered packets in memory
rxFECMulti = 3
// accept backlog
acceptBacklog = 128
)
@@ -155,7 +152,7 @@ func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn
}
// FEC codec initialization
sess.fecDecoder = newFECDecoder(rxFECMulti*(dataShards+parityShards), dataShards, parityShards)
sess.fecDecoder = newFECDecoder(dataShards, parityShards)
if sess.block != nil {
sess.fecEncoder = newFECEncoder(dataShards, parityShards, cryptHeaderSize)
} else {
@@ -216,7 +213,7 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
return size, nil
}
// if necessary resize the stream buffer to guarantee a sufficent buffer space
// if necessary resize the stream buffer to guarantee a sufficient buffer space
if cap(s.recvbuf) < size {
s.recvbuf = make([]byte, size)
}
@@ -240,7 +237,7 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
return 0, errors.WithStack(errTimeout)
}
delay := s.rd.Sub(time.Now())
delay := time.Until(s.rd)
timeout = time.NewTimer(delay)
c = timeout.C
}
@@ -311,7 +308,7 @@ func (s *UDPSession) WriteBuffers(v [][]byte) (n int, err error) {
s.mu.Unlock()
return 0, errors.WithStack(errTimeout)
}
delay := s.wd.Sub(time.Now())
delay := time.Until(s.wd)
timeout = time.NewTimer(delay)
c = timeout.C
}
@@ -343,7 +340,6 @@ func (s *UDPSession) uncork() {
}
s.txqueue = s.txqueue[:0]
}
return
}
// Close closes the connection.
@@ -655,22 +651,22 @@ func (s *UDPSession) notifyWriteError(err error) {
// packet input stage
func (s *UDPSession) packetInput(data []byte) {
dataValid := false
if s.block != nil {
decrypted := false
if s.block != nil && len(data) >= cryptHeaderSize {
s.block.Decrypt(data, data)
data = data[nonceSize:]
checksum := crc32.ChecksumIEEE(data[crcSize:])
if checksum == binary.LittleEndian.Uint32(data) {
data = data[crcSize:]
dataValid = true
decrypted = true
} else {
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
}
} else if s.block == nil {
dataValid = true
decrypted = true
}
if dataValid {
if decrypted && len(data) >= IKCP_OVERHEAD {
s.kcpInput(data)
}
}
@@ -678,57 +674,58 @@ func (s *UDPSession) packetInput(data []byte) {
func (s *UDPSession) kcpInput(data []byte) {
var kcpInErrors, fecErrs, fecRecovered, fecParityShards uint64
if s.fecDecoder != nil {
if len(data) > fecHeaderSize { // must be larger than fec header size
fecFlag := binary.LittleEndian.Uint16(data[4:])
if fecFlag == typeData || fecFlag == typeParity { // 16bit kcp cmd [81-84] and frg [0-255] will not overlap with FEC type 0x00f1 0x00f2
if len(data) >= fecHeaderSizePlus2 {
f := fecPacket(data)
if f.flag() == typeData || f.flag() == typeParity { // header check
if f.flag() == typeParity {
fecParityShards++
}
if f.flag() == typeParity {
fecParityShards++
}
// lock
s.mu.Lock()
recovers := s.fecDecoder.decode(f)
if f.flag() == typeData {
if ret := s.kcp.Input(data[fecHeaderSizePlus2:], true, s.ackNoDelay); ret != 0 {
kcpInErrors++
}
// lock
s.mu.Lock()
// if fecDecoder is not initialized, create one with default parameter
if s.fecDecoder == nil {
s.fecDecoder = newFECDecoder(1, 1)
}
recovers := s.fecDecoder.decode(f)
if f.flag() == typeData {
if ret := s.kcp.Input(data[fecHeaderSizePlus2:], true, s.ackNoDelay); ret != 0 {
kcpInErrors++
}
}
for _, r := range recovers {
if len(r) >= 2 { // must be larger than 2bytes
sz := binary.LittleEndian.Uint16(r)
if int(sz) <= len(r) && sz >= 2 {
if ret := s.kcp.Input(r[2:sz], false, s.ackNoDelay); ret == 0 {
fecRecovered++
} else {
kcpInErrors++
}
for _, r := range recovers {
if len(r) >= 2 { // must be larger than 2bytes
sz := binary.LittleEndian.Uint16(r)
if int(sz) <= len(r) && sz >= 2 {
if ret := s.kcp.Input(r[2:sz], false, s.ackNoDelay); ret == 0 {
fecRecovered++
} else {
fecErrs++
kcpInErrors++
}
} else {
fecErrs++
}
// recycle the recovers
xmitBuf.Put(r)
} else {
fecErrs++
}
// to notify the readers to receive the data
if n := s.kcp.PeekSize(); n > 0 {
s.notifyReadEvent()
}
// to notify the writers
waitsnd := s.kcp.WaitSnd()
if waitsnd < int(s.kcp.snd_wnd) && waitsnd < int(s.kcp.rmt_wnd) {
s.notifyWriteEvent()
}
s.uncork()
s.mu.Unlock()
} else {
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
// recycle the recovers
xmitBuf.Put(r)
}
// to notify the readers to receive the data
if n := s.kcp.PeekSize(); n > 0 {
s.notifyReadEvent()
}
// to notify the writers
waitsnd := s.kcp.WaitSnd()
if waitsnd < int(s.kcp.snd_wnd) && waitsnd < int(s.kcp.rmt_wnd) {
s.notifyWriteEvent()
}
s.uncork()
s.mu.Unlock()
} else {
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
}
@@ -771,7 +768,6 @@ type (
block BlockCrypt // block encryption
dataShards int // FEC data shard
parityShards int // FEC parity shard
fecDecoder *fecDecoder // FEC mock initialization
conn net.PacketConn // the underlying packet connection
ownConn bool // true if we created conn internally, false if provided by caller
@@ -779,7 +775,6 @@ type (
sessionLock sync.RWMutex
chAccepts chan *UDPSession // Listen() backlog
chSessionClosed chan net.Addr // session close queue
headerSize int // the additional header to a KCP frame
die chan struct{} // notify the listener has closed
dieOnce sync.Once
@@ -795,43 +790,45 @@ type (
// packet input stage
func (l *Listener) packetInput(data []byte, addr net.Addr) {
dataValid := false
if l.block != nil {
decrypted := false
if l.block != nil && len(data) >= cryptHeaderSize {
l.block.Decrypt(data, data)
data = data[nonceSize:]
checksum := crc32.ChecksumIEEE(data[crcSize:])
if checksum == binary.LittleEndian.Uint32(data) {
data = data[crcSize:]
dataValid = true
decrypted = true
} else {
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
}
} else if l.block == nil {
dataValid = true
decrypted = true
}
if dataValid {
if decrypted && len(data) >= IKCP_OVERHEAD {
l.sessionLock.RLock()
s, ok := l.sessions[addr.String()]
l.sessionLock.RUnlock()
var conv, sn uint32
convValid := false
if l.fecDecoder != nil {
isfec := binary.LittleEndian.Uint16(data[4:])
if isfec == typeData {
convRecovered := false
fecFlag := binary.LittleEndian.Uint16(data[4:])
if fecFlag == typeData || fecFlag == typeParity { // 16bit kcp cmd [81-84] and frg [0-255] will not overlap with FEC type 0x00f1 0x00f2
// packet with FEC
if fecFlag == typeData && len(data) >= fecHeaderSizePlus2+IKCP_OVERHEAD {
conv = binary.LittleEndian.Uint32(data[fecHeaderSizePlus2:])
sn = binary.LittleEndian.Uint32(data[fecHeaderSizePlus2+IKCP_SN_OFFSET:])
convValid = true
convRecovered = true
}
} else {
// packet without FEC
conv = binary.LittleEndian.Uint32(data)
sn = binary.LittleEndian.Uint32(data[IKCP_SN_OFFSET:])
convValid = true
convRecovered = true
}
if ok { // existing connection
if !convValid || conv == s.kcp.conv { // parity or valid data shard
if !convRecovered || conv == s.kcp.conv { // parity data or valid conversation
s.kcpInput(data)
} else if sn == 0 { // should replace current connection
s.Close()
@@ -839,7 +836,7 @@ func (l *Listener) packetInput(data []byte, addr net.Addr) {
}
}
if s == nil && convValid { // new session
if s == nil && convRecovered { // new session
if len(l.chAccepts) < cap(l.chAccepts) { // do not let the new sessions overwhelm accept queue
s := newUDPSession(conv, l.dataShards, l.parityShards, l, l.conn, false, addr, l.block)
s.kcpInput(data)
@@ -917,7 +914,7 @@ func (l *Listener) Accept() (net.Conn, error) {
func (l *Listener) AcceptKCP() (*UDPSession, error) {
var timeout <-chan time.Time
if tdeadline, ok := l.rd.Load().(time.Time); ok && !tdeadline.IsZero() {
timeout = time.After(tdeadline.Sub(time.Now()))
timeout = time.After(time.Until(tdeadline))
}
select {
@@ -988,7 +985,7 @@ func Listen(laddr string) (net.Listener, error) { return ListenWithOptions(laddr
//
// 'block' is the block encryption algorithm to encrypt packets.
//
// 'dataShards', 'parityShards' specifiy how many parity packets will be generated following the data packets.
// 'dataShards', 'parityShards' specify how many parity packets will be generated following the data packets.
//
// Check https://github.com/klauspost/reedsolomon for details
func ListenWithOptions(laddr string, block BlockCrypt, dataShards, parityShards int) (*Listener, error) {
@@ -1020,17 +1017,7 @@ func serveConn(block BlockCrypt, dataShards, parityShards int, conn net.PacketCo
l.dataShards = dataShards
l.parityShards = parityShards
l.block = block
l.fecDecoder = newFECDecoder(rxFECMulti*(dataShards+parityShards), dataShards, parityShards)
l.chSocketReadError = make(chan struct{})
// calculate header size
if l.block != nil {
l.headerSize += cryptHeaderSize
}
if l.fecDecoder != nil {
l.headerSize += fecHeaderSizePlus2
}
go l.monitor()
return l, nil
}
@@ -1042,7 +1029,7 @@ func Dial(raddr string) (net.Conn, error) { return DialWithOptions(raddr, nil, 0
//
// 'block' is the block encryption algorithm to encrypt packets.
//
// 'dataShards', 'parityShards' specifiy how many parity packets will be generated following the data packets.
// 'dataShards', 'parityShards' specify how many parity packets will be generated following the data packets.
//
// Check https://github.com/klauspost/reedsolomon for details
func DialWithOptions(raddr string, block BlockCrypt, dataShards, parityShards int) (*UDPSession, error) {
+1 -1
View File
@@ -25,7 +25,7 @@ type Snmp struct {
RetransSegs uint64 // accmulated retransmited segments
FastRetransSegs uint64 // accmulated fast retransmitted segments
EarlyRetransSegs uint64 // accmulated early retransmitted segments
LostSegs uint64 // number of segs infered as lost
LostSegs uint64 // number of segs inferred as lost
RepeatSegs uint64 // number of segs duplicated
FECRecovered uint64 // correct packets recovered from FEC
FECErrs uint64 // incorrect packets recovered from FEC
+15
View File
@@ -0,0 +1,15 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 xtaci
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+16
View File
@@ -0,0 +1,16 @@
# serialpacket
SerialPacket is a net.PacketConn implementation over RS232.
It's designed to work with [kcp-go](https://github.com/xtaci/kcp-go) to provide reliable transmission over [LoRa](https://en.wikipedia.org/wiki/LoRa) or other noisy channels.
Test:
```
$ socat -d -d pty,raw,echo=0 pty,raw,echo=0
2022/03/27 22:48:28 socat[14099] N PTY is /dev/pts/5
2022/03/27 22:48:28 socat[14099] N PTY is /dev/pts/6
2022/03/27 22:48:28 socat[14099] N starting data transfer loop with FDs [5,5] and [7,7]
$ export PORT1="/dev/pts/5"
$ export PORT1="/dev/pts/6"
```
+24
View File
@@ -0,0 +1,24 @@
module github.com/xtaci/serialpacket
go 1.17
require (
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
github.com/xtaci/kcp-go/v5 v5.6.1
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
)
require (
github.com/klauspost/cpuid v1.3.1 // indirect
github.com/klauspost/reedsolomon v1.9.9 // indirect
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/templexxx/cpu v0.0.7 // indirect
github.com/templexxx/xorsimd v0.4.1 // indirect
github.com/tjfoc/gmsm v1.3.2 // indirect
golang.org/x/mod v0.3.0 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/sys v0.0.0-20220325203850-36772127a21f // indirect
golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
)
+78
View File
@@ -0,0 +1,78 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/klauspost/reedsolomon v1.9.9 h1:qCL7LZlv17xMixl55nq2/Oa1Y86nfO8EqDfv2GHND54=
github.com/klauspost/reedsolomon v1.9.9/go.mod h1:O7yFFHiQwDR6b2t63KPUpccPtNdp5ADgh1gg4fd12wo=
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 h1:ULR/QWMgcgRiZLUjSSJMU+fW+RDMstRdmnDWj9Q+AsA=
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/cpu v0.0.7 h1:pUEZn8JBy/w5yzdYWgx+0m0xL9uk6j4K91C5kOViAzo=
github.com/templexxx/cpu v0.0.7/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/xorsimd v0.4.1 h1:iUZcywbOYDRAZUasAs2eSCUW8eobuZDy0I9FJiORkVg=
github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo=
github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM=
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/xtaci/kcp-go/v5 v5.6.1 h1:Pwn0aoeNSPF9dTS7IgiPXn0HEtaIlVb6y5UKWPsx8bI=
github.com/xtaci/kcp-go/v5 v5.6.1/go.mod h1:W3kVPyNYwZ06p79dNwFWQOVFrdcBpDBsdyvK8moQrYo=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220325203850-36772127a21f h1:TrmogKRsSOxRMJbLYGrB4SBbW+LJcEllYBLME5Zk5pU=
golang.org/x/sys v0.0.0-20220325203850-36772127a21f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123 h1:4JSJPND/+4555t1HfXYF4UEqDqiSKCgeV0+hbA8hMs4=
golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+143
View File
@@ -0,0 +1,143 @@
package serialpacket
import (
"errors"
"fmt"
"io"
"net"
"time"
"github.com/tarm/serial"
)
// Frame Definition
// |MAGIC(4B) | LENGTH (1B) | RESERVE(1B) | DATA (LENGTH) |
// Max Packet Size: 240
const (
HEADER_SIZE = 2
MAGIC_SIZE = 4
MTU = 240
)
var (
MagicBytes = []byte{0xFF, 0x00, 0xAA, 0x55}
)
var (
ErrNotImplemented = errors.New("not implemented")
)
// header definition
type rawHeader [HEADER_SIZE]byte
func (h rawHeader) Length() int { return int(h[0]) }
// SerialPacketAddr is the address definition in net.Addr
type SerialPacketAddr struct{ name string }
func (addr *SerialPacketAddr) Network() string { return "serial" }
func (addr *SerialPacketAddr) String() string { return "serial://" + addr.name }
// NewSerialPacketAddr creates an address with given name and port
func NewSerialPacketAddr(name string) *SerialPacketAddr {
addr := new(SerialPacketAddr)
addr.name = name
return addr
}
// Conn is the packet connection definition for a serial connection
type Conn struct {
port *serial.Port
addr *SerialPacketAddr
raddr *SerialPacketAddr
header rawHeader
magicPos int
magic [4]byte
}
func (c *Conn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
// 0xFF00AA55 to sync frame
for {
n, err = c.port.Read(c.magic[c.magicPos : c.magicPos+1])
if err != nil {
return 0, nil, err
}
if c.magic[c.magicPos] != MagicBytes[c.magicPos] {
c.magicPos = 0
continue
}
if c.magicPos == 3 {
c.magicPos = 0
break
}
c.magicPos++
}
// read full header
n, err = io.ReadFull(c.port, c.header[:])
if err != nil {
return 0, nil, err
}
// log.Println("header:", c.header)
sz := c.header.Length()
if len(p) < sz {
return 0, c.raddr, fmt.Errorf("buffer too small: need %v, given %v", sz, len(p))
}
// read full body
n, err = io.ReadFull(c.port, p[:sz])
if err != nil {
return n, c.raddr, err
}
//log.Println("body:", sz, p[:sz])
return n, c.raddr, nil
}
func (c *Conn) WriteTo(p []byte, _ net.Addr) (n int, err error) {
if len(p) > MTU {
return 0, fmt.Errorf("packet too large(MTU:%v) actual %v", MTU, len(p))
}
packet := make([]byte, MAGIC_SIZE+HEADER_SIZE+len(p))
magic := packet[:]
copy(magic, MagicBytes)
header := magic[MAGIC_SIZE:]
header[0] = byte(len(p))
data := header[HEADER_SIZE:]
copy(data, p)
// write full packet until error
written := 0
for len(packet) > 0 {
n, err = c.port.Write(packet)
if err != nil {
return written, err
} else {
written += n
packet = packet[written:]
}
}
return written - HEADER_SIZE - MAGIC_SIZE, nil
}
func (c *Conn) Close() error { return c.port.Close() }
func (c *Conn) LocalAddr() net.Addr { return c.addr }
func (c *Conn) SetDeadline(t time.Time) error { return ErrNotImplemented }
func (c *Conn) SetReadDeadline(t time.Time) error { return ErrNotImplemented }
func (c *Conn) SetWriteDeadline(t time.Time) error { return ErrNotImplemented }
// NewConn creates a net.PacketConn on a serial line
func NewConn(port *serial.Port) (*Conn, error) {
c := new(Conn)
c.port = port
c.addr = NewSerialPacketAddr("local")
c.raddr = NewSerialPacketAddr("remote")
return c, nil
}
+3
View File
@@ -1,3 +1,6 @@
arch:
- amd64
- ppc64le
language: go
go:
- 1.9.x
+2 -2
View File
@@ -5,7 +5,7 @@
<img src="mux.jpg" alt="smux" height="120px" />
[1]: https://godoc.org/github.com/xtaci/smux?status.svg
[2]: https://pkg.go.dev/github.com/xtaci/smux
[2]: https://godoc.org/github.com/xtaci/smux
[3]: https://img.shields.io/badge/license-MIT-blue.svg
[4]: LICENSE
[5]: https://travis-ci.org/xtaci/smux.svg?branch=master
@@ -34,7 +34,7 @@ Smux ( **S**imple **MU**ltiple**X**ing) is a multiplexing library for Golang. It
## Documentation
For complete documentation, see the associated [Godoc](https://pkg.go.dev/github.com/xtaci/smux).
For complete documentation, see the associated [Godoc](https://godoc.org/github.com/xtaci/smux).
## Benchmark
```
+10 -5
View File
@@ -17,6 +17,9 @@ type Config struct {
// SMUX Protocol version, support 1,2
Version int
// Disabled keepalive
KeepAliveDisabled bool
// KeepAliveInterval is how often to send a NOP command to the remote
KeepAliveInterval time.Duration
@@ -54,11 +57,13 @@ func VerifyConfig(config *Config) error {
if !(config.Version == 1 || config.Version == 2) {
return errors.New("unsupported protocol version")
}
if config.KeepAliveInterval == 0 {
return errors.New("keep-alive interval must be positive")
}
if config.KeepAliveTimeout < config.KeepAliveInterval {
return fmt.Errorf("keep-alive timeout must be larger than keep-alive interval")
if !config.KeepAliveDisabled {
if config.KeepAliveInterval == 0 {
return errors.New("keep-alive interval must be positive")
}
if config.KeepAliveTimeout < config.KeepAliveInterval {
return fmt.Errorf("keep-alive timeout must be larger than keep-alive interval")
}
}
if config.MaxFrameSize <= 0 {
return errors.New("max frame size must be positive")
+5 -3
View File
@@ -24,7 +24,7 @@ var (
)
type writeRequest struct {
prio uint64
prio uint32
frame Frame
result chan writeResult
}
@@ -104,7 +104,9 @@ func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
go s.shaperLoop()
go s.recvLoop()
go s.sendLoop()
go s.keepalive()
if !config.KeepAliveDisabled {
go s.keepalive()
}
return s
}
@@ -494,7 +496,7 @@ func (s *Session) writeFrame(f Frame) (n int, err error) {
}
// internal writeFrame version to support deadline used in keepalive
func (s *Session) writeFrameInternal(f Frame, deadline <-chan time.Time, prio uint64) (int, error) {
func (s *Session) writeFrameInternal(f Frame, deadline <-chan time.Time, prio uint32) (int, error) {
req := writeRequest{
prio: prio,
frame: f,
+5 -1
View File
@@ -1,9 +1,13 @@
package smux
func _itimediff(later, earlier uint32) int32 {
return (int32)(later - earlier)
}
type shaperHeap []writeRequest
func (h shaperHeap) Len() int { return len(h) }
func (h shaperHeap) Less(i, j int) bool { return h[i].prio < h[j].prio }
func (h shaperHeap) Less(i, j int) bool { return _itimediff(h[j].prio, h[i].prio) > 0 }
func (h shaperHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *shaperHeap) Push(x interface{}) { *h = append(*h, x.(writeRequest)) }
+8 -2
View File
@@ -272,6 +272,12 @@ func (s *Stream) waitRead() error {
case <-s.chReadEvent:
return nil
case <-s.chFinEvent:
// BUG(xtaci): Fix for https://github.com/xtaci/smux/issues/82
s.bufferLock.Lock()
defer s.bufferLock.Unlock()
if len(s.buffers) > 0 {
return nil
}
return io.EOF
case <-s.sess.chSocketReadError:
return s.sess.socketReadError.Load().(error)
@@ -319,7 +325,7 @@ func (s *Stream) Write(b []byte) (n int, err error) {
}
frame.data = bts[:sz]
bts = bts[sz:]
n, err := s.sess.writeFrameInternal(frame, deadline, uint64(s.numWritten))
n, err := s.sess.writeFrameInternal(frame, deadline, s.numWritten)
s.numWritten++
sent += n
if err != nil {
@@ -387,7 +393,7 @@ func (s *Stream) writeV2(b []byte) (n int, err error) {
}
frame.data = bts[:sz]
bts = bts[sz:]
n, err := s.sess.writeFrameInternal(frame, deadline, uint64(atomic.LoadUint32(&s.numWritten)))
n, err := s.sess.writeFrameInternal(frame, deadline, atomic.LoadUint32(&s.numWritten))
atomic.AddUint32(&s.numWritten, uint32(sz))
sent += n
if err != nil {
+2 -1
View File
@@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !appengine
//go:build !purego
// +build !purego
// Package subtle implements functions that are often useful in cryptographic
// code but require careful thought to use correctly.
@@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build appengine
//go:build purego
// +build purego
// Package subtle implements functions that are often useful in cryptographic
// code but require careful thought to use correctly.
+2 -1
View File
@@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build amd64,!appengine,!gccgo
//go:build amd64 && !purego && gc
// +build amd64,!purego,gc
package salsa
+118 -120
View File
@@ -2,13 +2,14 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build amd64,!appengine,!gccgo
//go:build amd64 && !purego && gc
// +build amd64,!purego,gc
// This code was translated into a form compatible with 6a from the public
// domain sources in SUPERCOP: https://bench.cr.yp.to/supercop.html
// func salsa2020XORKeyStream(out, in *byte, n uint64, nonce, key *byte)
// This needs up to 64 bytes at 360(SP); hence the non-obvious frame size.
// This needs up to 64 bytes at 360(R12); hence the non-obvious frame size.
TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
MOVQ out+0(FP),DI
MOVQ in+8(FP),SI
@@ -17,10 +18,8 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
MOVQ key+32(FP),R8
MOVQ SP,R12
MOVQ SP,R9
ADDQ $31, R9
ANDQ $~31, R9
MOVQ R9, SP
ADDQ $31, R12
ANDQ $~31, R12
MOVQ DX,R9
MOVQ CX,DX
@@ -32,116 +31,116 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
MOVL 0(R10),R8
MOVL 0(DX),AX
MOVL 16(R10),R11
MOVL CX,0(SP)
MOVL R8, 4 (SP)
MOVL AX, 8 (SP)
MOVL R11, 12 (SP)
MOVL CX,0(R12)
MOVL R8, 4 (R12)
MOVL AX, 8 (R12)
MOVL R11, 12 (R12)
MOVL 8(DX),CX
MOVL 24(R10),R8
MOVL 4(R10),AX
MOVL 4(DX),R11
MOVL CX,16(SP)
MOVL R8, 20 (SP)
MOVL AX, 24 (SP)
MOVL R11, 28 (SP)
MOVL CX,16(R12)
MOVL R8, 20 (R12)
MOVL AX, 24 (R12)
MOVL R11, 28 (R12)
MOVL 12(DX),CX
MOVL 12(R10),DX
MOVL 28(R10),R8
MOVL 8(R10),AX
MOVL DX,32(SP)
MOVL CX, 36 (SP)
MOVL R8, 40 (SP)
MOVL AX, 44 (SP)
MOVL DX,32(R12)
MOVL CX, 36 (R12)
MOVL R8, 40 (R12)
MOVL AX, 44 (R12)
MOVQ $1634760805,DX
MOVQ $857760878,CX
MOVQ $2036477234,R8
MOVQ $1797285236,AX
MOVL DX,48(SP)
MOVL CX, 52 (SP)
MOVL R8, 56 (SP)
MOVL AX, 60 (SP)
MOVL DX,48(R12)
MOVL CX, 52 (R12)
MOVL R8, 56 (R12)
MOVL AX, 60 (R12)
CMPQ R9,$256
JB BYTESBETWEEN1AND255
MOVOA 48(SP),X0
MOVOA 48(R12),X0
PSHUFL $0X55,X0,X1
PSHUFL $0XAA,X0,X2
PSHUFL $0XFF,X0,X3
PSHUFL $0X00,X0,X0
MOVOA X1,64(SP)
MOVOA X2,80(SP)
MOVOA X3,96(SP)
MOVOA X0,112(SP)
MOVOA 0(SP),X0
MOVOA X1,64(R12)
MOVOA X2,80(R12)
MOVOA X3,96(R12)
MOVOA X0,112(R12)
MOVOA 0(R12),X0
PSHUFL $0XAA,X0,X1
PSHUFL $0XFF,X0,X2
PSHUFL $0X00,X0,X3
PSHUFL $0X55,X0,X0
MOVOA X1,128(SP)
MOVOA X2,144(SP)
MOVOA X3,160(SP)
MOVOA X0,176(SP)
MOVOA 16(SP),X0
MOVOA X1,128(R12)
MOVOA X2,144(R12)
MOVOA X3,160(R12)
MOVOA X0,176(R12)
MOVOA 16(R12),X0
PSHUFL $0XFF,X0,X1
PSHUFL $0X55,X0,X2
PSHUFL $0XAA,X0,X0
MOVOA X1,192(SP)
MOVOA X2,208(SP)
MOVOA X0,224(SP)
MOVOA 32(SP),X0
MOVOA X1,192(R12)
MOVOA X2,208(R12)
MOVOA X0,224(R12)
MOVOA 32(R12),X0
PSHUFL $0X00,X0,X1
PSHUFL $0XAA,X0,X2
PSHUFL $0XFF,X0,X0
MOVOA X1,240(SP)
MOVOA X2,256(SP)
MOVOA X0,272(SP)
MOVOA X1,240(R12)
MOVOA X2,256(R12)
MOVOA X0,272(R12)
BYTESATLEAST256:
MOVL 16(SP),DX
MOVL 36 (SP),CX
MOVL DX,288(SP)
MOVL CX,304(SP)
MOVL 16(R12),DX
MOVL 36 (R12),CX
MOVL DX,288(R12)
MOVL CX,304(R12)
SHLQ $32,CX
ADDQ CX,DX
ADDQ $1,DX
MOVQ DX,CX
SHRQ $32,CX
MOVL DX, 292 (SP)
MOVL CX, 308 (SP)
MOVL DX, 292 (R12)
MOVL CX, 308 (R12)
ADDQ $1,DX
MOVQ DX,CX
SHRQ $32,CX
MOVL DX, 296 (SP)
MOVL CX, 312 (SP)
MOVL DX, 296 (R12)
MOVL CX, 312 (R12)
ADDQ $1,DX
MOVQ DX,CX
SHRQ $32,CX
MOVL DX, 300 (SP)
MOVL CX, 316 (SP)
MOVL DX, 300 (R12)
MOVL CX, 316 (R12)
ADDQ $1,DX
MOVQ DX,CX
SHRQ $32,CX
MOVL DX,16(SP)
MOVL CX, 36 (SP)
MOVQ R9,352(SP)
MOVL DX,16(R12)
MOVL CX, 36 (R12)
MOVQ R9,352(R12)
MOVQ $20,DX
MOVOA 64(SP),X0
MOVOA 80(SP),X1
MOVOA 96(SP),X2
MOVOA 256(SP),X3
MOVOA 272(SP),X4
MOVOA 128(SP),X5
MOVOA 144(SP),X6
MOVOA 176(SP),X7
MOVOA 192(SP),X8
MOVOA 208(SP),X9
MOVOA 224(SP),X10
MOVOA 304(SP),X11
MOVOA 112(SP),X12
MOVOA 160(SP),X13
MOVOA 240(SP),X14
MOVOA 288(SP),X15
MOVOA 64(R12),X0
MOVOA 80(R12),X1
MOVOA 96(R12),X2
MOVOA 256(R12),X3
MOVOA 272(R12),X4
MOVOA 128(R12),X5
MOVOA 144(R12),X6
MOVOA 176(R12),X7
MOVOA 192(R12),X8
MOVOA 208(R12),X9
MOVOA 224(R12),X10
MOVOA 304(R12),X11
MOVOA 112(R12),X12
MOVOA 160(R12),X13
MOVOA 240(R12),X14
MOVOA 288(R12),X15
MAINLOOP1:
MOVOA X1,320(SP)
MOVOA X2,336(SP)
MOVOA X1,320(R12)
MOVOA X2,336(R12)
MOVOA X13,X1
PADDL X12,X1
MOVOA X1,X2
@@ -191,8 +190,8 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
PXOR X1,X12
PSRLL $14,X2
PXOR X2,X12
MOVOA 320(SP),X1
MOVOA X12,320(SP)
MOVOA 320(R12),X1
MOVOA X12,320(R12)
MOVOA X9,X2
PADDL X7,X2
MOVOA X2,X12
@@ -207,8 +206,8 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
PXOR X2,X3
PSRLL $25,X12
PXOR X12,X3
MOVOA 336(SP),X2
MOVOA X0,336(SP)
MOVOA 336(R12),X2
MOVOA X0,336(R12)
MOVOA X6,X0
PADDL X2,X0
MOVOA X0,X12
@@ -251,8 +250,8 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
PXOR X0,X1
PSRLL $14,X12
PXOR X12,X1
MOVOA 320(SP),X0
MOVOA X1,320(SP)
MOVOA 320(R12),X0
MOVOA X1,320(R12)
MOVOA X4,X1
PADDL X0,X1
MOVOA X1,X12
@@ -267,8 +266,8 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
PXOR X1,X2
PSRLL $14,X12
PXOR X12,X2
MOVOA 336(SP),X12
MOVOA X2,336(SP)
MOVOA 336(R12),X12
MOVOA X2,336(R12)
MOVOA X14,X1
PADDL X12,X1
MOVOA X1,X2
@@ -311,8 +310,8 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
PXOR X1,X0
PSRLL $14,X2
PXOR X2,X0
MOVOA 320(SP),X1
MOVOA X0,320(SP)
MOVOA 320(R12),X1
MOVOA X0,320(R12)
MOVOA X8,X0
PADDL X14,X0
MOVOA X0,X2
@@ -327,8 +326,8 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
PXOR X0,X6
PSRLL $25,X2
PXOR X2,X6
MOVOA 336(SP),X2
MOVOA X12,336(SP)
MOVOA 336(R12),X2
MOVOA X12,336(R12)
MOVOA X3,X0
PADDL X2,X0
MOVOA X0,X12
@@ -378,14 +377,14 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
PXOR X0,X2
PSRLL $14,X12
PXOR X12,X2
MOVOA 320(SP),X12
MOVOA 336(SP),X0
MOVOA 320(R12),X12
MOVOA 336(R12),X0
SUBQ $2,DX
JA MAINLOOP1
PADDL 112(SP),X12
PADDL 176(SP),X7
PADDL 224(SP),X10
PADDL 272(SP),X4
PADDL 112(R12),X12
PADDL 176(R12),X7
PADDL 224(R12),X10
PADDL 272(R12),X4
MOVD X12,DX
MOVD X7,CX
MOVD X10,R8
@@ -446,10 +445,10 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
MOVL CX,196(DI)
MOVL R8,200(DI)
MOVL R9,204(DI)
PADDL 240(SP),X14
PADDL 64(SP),X0
PADDL 128(SP),X5
PADDL 192(SP),X8
PADDL 240(R12),X14
PADDL 64(R12),X0
PADDL 128(R12),X5
PADDL 192(R12),X8
MOVD X14,DX
MOVD X0,CX
MOVD X5,R8
@@ -510,10 +509,10 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
MOVL CX,212(DI)
MOVL R8,216(DI)
MOVL R9,220(DI)
PADDL 288(SP),X15
PADDL 304(SP),X11
PADDL 80(SP),X1
PADDL 144(SP),X6
PADDL 288(R12),X15
PADDL 304(R12),X11
PADDL 80(R12),X1
PADDL 144(R12),X6
MOVD X15,DX
MOVD X11,CX
MOVD X1,R8
@@ -574,10 +573,10 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
MOVL CX,228(DI)
MOVL R8,232(DI)
MOVL R9,236(DI)
PADDL 160(SP),X13
PADDL 208(SP),X9
PADDL 256(SP),X3
PADDL 96(SP),X2
PADDL 160(R12),X13
PADDL 208(R12),X9
PADDL 256(R12),X3
PADDL 96(R12),X2
MOVD X13,DX
MOVD X9,CX
MOVD X3,R8
@@ -638,7 +637,7 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
MOVL CX,244(DI)
MOVL R8,248(DI)
MOVL R9,252(DI)
MOVQ 352(SP),R9
MOVQ 352(R12),R9
SUBQ $256,R9
ADDQ $256,SI
ADDQ $256,DI
@@ -650,17 +649,17 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
CMPQ R9,$64
JAE NOCOPY
MOVQ DI,DX
LEAQ 360(SP),DI
LEAQ 360(R12),DI
MOVQ R9,CX
REP; MOVSB
LEAQ 360(SP),DI
LEAQ 360(SP),SI
LEAQ 360(R12),DI
LEAQ 360(R12),SI
NOCOPY:
MOVQ R9,352(SP)
MOVOA 48(SP),X0
MOVOA 0(SP),X1
MOVOA 16(SP),X2
MOVOA 32(SP),X3
MOVQ R9,352(R12)
MOVOA 48(R12),X0
MOVOA 0(R12),X1
MOVOA 16(R12),X2
MOVOA 32(R12),X3
MOVOA X1,X4
MOVQ $20,CX
MAINLOOP2:
@@ -791,10 +790,10 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
PSHUFL $0X39,X3,X3
PXOR X6,X0
JA MAINLOOP2
PADDL 48(SP),X0
PADDL 0(SP),X1
PADDL 16(SP),X2
PADDL 32(SP),X3
PADDL 48(R12),X0
PADDL 0(R12),X1
PADDL 16(R12),X2
PADDL 32(R12),X3
MOVD X0,CX
MOVD X1,R8
MOVD X2,R9
@@ -855,16 +854,16 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
MOVL R8,44(DI)
MOVL R9,28(DI)
MOVL AX,12(DI)
MOVQ 352(SP),R9
MOVL 16(SP),CX
MOVL 36 (SP),R8
MOVQ 352(R12),R9
MOVL 16(R12),CX
MOVL 36 (R12),R8
ADDQ $1,CX
SHLQ $32,R8
ADDQ R8,CX
MOVQ CX,R8
SHRQ $32,R8
MOVL CX,16(SP)
MOVL R8, 36 (SP)
MOVL CX,16(R12)
MOVL R8, 36 (R12)
CMPQ R9,$64
JA BYTESATLEAST65
JAE BYTESATLEAST64
@@ -874,7 +873,6 @@ TEXT ·salsa2020XORKeyStream(SB),0,$456-40 // frame = 424 + 32 byte alignment
REP; MOVSB
BYTESATLEAST64:
DONE:
MOVQ R12,SP
RET
BYTESATLEAST65:
SUBQ $64,R9
+2 -1
View File
@@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !amd64 appengine gccgo
//go:build !amd64 || purego || !gc
// +build !amd64 purego !gc
package salsa
+27
View File
@@ -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.

Some files were not shown because too many files have changed in this diff Show More