mirror of
https://github.com/xtaci/kcptun.git
synced 2024-04-21 12:32:32 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89f69a9850 | ||
|
|
cadb94fdc3 | ||
|
|
3acc8a7372 | ||
|
|
b63eeb6301 | ||
|
|
5b5c094e4b | ||
|
|
c4bd77cf28 | ||
|
|
94c9cacf4f | ||
|
|
9a5b31b470 | ||
|
|
07ad03fecf | ||
|
|
2225d258cb | ||
|
|
e9316f7be4 | ||
|
|
9ac04cd24d | ||
|
|
76c4d231f2 | ||
|
|
160b68cbc5 | ||
|
|
212504f6c2 | ||
|
|
114cd97025 | ||
|
|
b2be91cdb5 |
@@ -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
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||
@@ -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
@@ -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
@@ -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,3 +1,4 @@
|
||||
[unit]
|
||||
Description=kcptun
|
||||
|
||||
Wants=network.target
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//+build arm64,!gccgo
|
||||
|
||||
// See https://www.kernel.org/doc/Documentation/arm64/cpu-feature-registers.txt
|
||||
|
||||
// func getMidr
|
||||
TEXT ·getMidr(SB), 7, $0
|
||||
WORD $0xd5380000 // mrs x0, midr_el1 /* Main ID Register */
|
||||
MOVD R0, midr+0(FP)
|
||||
RET
|
||||
|
||||
// func getProcFeatures
|
||||
TEXT ·getProcFeatures(SB), 7, $0
|
||||
WORD $0xd5380400 // mrs x0, id_aa64pfr0_el1 /* Processor Feature Register 0 */
|
||||
MOVD R0, procFeatures+0(FP)
|
||||
RET
|
||||
|
||||
// func getInstAttributes
|
||||
TEXT ·getInstAttributes(SB), 7, $0
|
||||
WORD $0xd5380600 // mrs x0, id_aa64isar0_el1 /* Instruction Set Attribute Register 0 */
|
||||
WORD $0xd5380621 // mrs x1, id_aa64isar1_el1 /* Instruction Set Attribute Register 1 */
|
||||
MOVD R0, instAttrReg0+0(FP)
|
||||
MOVD R1, instAttrReg1+8(FP)
|
||||
RET
|
||||
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
//+build arm64,!gccgo,!noasm,!appengine
|
||||
|
||||
package cpuid
|
||||
|
||||
func getMidr() (midr uint64)
|
||||
func getProcFeatures() (procFeatures uint64)
|
||||
func getInstAttributes() (instAttrReg0, instAttrReg1 uint64)
|
||||
|
||||
func initCPU() {
|
||||
cpuid = func(uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
cpuidex = func(x, y uint32) (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
xgetbv = func(uint32) (a, b uint32) { return 0, 0 }
|
||||
rdtscpAsm = func() (a, b, c, d uint32) { return 0, 0, 0, 0 }
|
||||
}
|
||||
|
||||
func addInfo(c *CPUInfo) {
|
||||
// ARM64 disabled for now.
|
||||
if true {
|
||||
return
|
||||
}
|
||||
// midr := getMidr()
|
||||
|
||||
// MIDR_EL1 - Main ID Register
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | Implementer | [31-24] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | Variant | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | Architecture | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | PartNum | [15-4] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | Revision | [3-0] | y |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
// fmt.Printf(" implementer: 0x%02x\n", (midr>>24)&0xff)
|
||||
// fmt.Printf(" variant: 0x%01x\n", (midr>>20)&0xf)
|
||||
// fmt.Printf("architecture: 0x%01x\n", (midr>>16)&0xf)
|
||||
// fmt.Printf(" part num: 0x%03x\n", (midr>>4)&0xfff)
|
||||
// fmt.Printf(" revision: 0x%01x\n", (midr>>0)&0xf)
|
||||
|
||||
procFeatures := getProcFeatures()
|
||||
|
||||
// ID_AA64PFR0_EL1 - Processor Feature Register 0
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | DIT | [51-48] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SVE | [35-32] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | GIC | [27-24] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | AdvSIMD | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | FP | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | EL3 | [15-12] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | EL2 | [11-8] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | EL1 | [7-4] | n |
|
||||
// |--------------------------------------------------|
|
||||
// | EL0 | [3-0] | n |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
var f ArmFlags
|
||||
// if procFeatures&(0xf<<48) != 0 {
|
||||
// fmt.Println("DIT")
|
||||
// }
|
||||
if procFeatures&(0xf<<32) != 0 {
|
||||
f |= SVE
|
||||
}
|
||||
if procFeatures&(0xf<<20) != 15<<20 {
|
||||
f |= ASIMD
|
||||
if procFeatures&(0xf<<20) == 1<<20 {
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64pfr0_el1
|
||||
// 0b0001 --> As for 0b0000, and also includes support for half-precision floating-point arithmetic.
|
||||
f |= FPHP
|
||||
f |= ASIMDHP
|
||||
}
|
||||
}
|
||||
if procFeatures&(0xf<<16) != 0 {
|
||||
f |= FP
|
||||
}
|
||||
|
||||
instAttrReg0, instAttrReg1 := getInstAttributes()
|
||||
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
|
||||
//
|
||||
// ID_AA64ISAR0_EL1 - Instruction Set Attribute Register 0
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | TS | [55-52] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | FHM | [51-48] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | DP | [47-44] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SM4 | [43-40] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SM3 | [39-36] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SHA3 | [35-32] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | RDM | [31-28] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | ATOMICS | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | CRC32 | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SHA2 | [15-12] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | SHA1 | [11-8] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | AES | [7-4] | y |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
// if instAttrReg0&(0xf<<52) != 0 {
|
||||
// fmt.Println("TS")
|
||||
// }
|
||||
// if instAttrReg0&(0xf<<48) != 0 {
|
||||
// fmt.Println("FHM")
|
||||
// }
|
||||
if instAttrReg0&(0xf<<44) != 0 {
|
||||
f |= ASIMDDP
|
||||
}
|
||||
if instAttrReg0&(0xf<<40) != 0 {
|
||||
f |= SM4
|
||||
}
|
||||
if instAttrReg0&(0xf<<36) != 0 {
|
||||
f |= SM3
|
||||
}
|
||||
if instAttrReg0&(0xf<<32) != 0 {
|
||||
f |= SHA3
|
||||
}
|
||||
if instAttrReg0&(0xf<<28) != 0 {
|
||||
f |= ASIMDRDM
|
||||
}
|
||||
if instAttrReg0&(0xf<<20) != 0 {
|
||||
f |= ATOMICS
|
||||
}
|
||||
if instAttrReg0&(0xf<<16) != 0 {
|
||||
f |= CRC32
|
||||
}
|
||||
if instAttrReg0&(0xf<<12) != 0 {
|
||||
f |= SHA2
|
||||
}
|
||||
if instAttrReg0&(0xf<<12) == 2<<12 {
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
|
||||
// 0b0010 --> As 0b0001, plus SHA512H, SHA512H2, SHA512SU0, and SHA512SU1 instructions implemented.
|
||||
f |= SHA512
|
||||
}
|
||||
if instAttrReg0&(0xf<<8) != 0 {
|
||||
f |= SHA1
|
||||
}
|
||||
if instAttrReg0&(0xf<<4) != 0 {
|
||||
f |= AES
|
||||
}
|
||||
if instAttrReg0&(0xf<<4) == 2<<4 {
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar0_el1
|
||||
// 0b0010 --> As for 0b0001, plus PMULL/PMULL2 instructions operating on 64-bit data quantities.
|
||||
f |= PMULL
|
||||
}
|
||||
|
||||
// https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/id_aa64isar1_el1
|
||||
//
|
||||
// ID_AA64ISAR1_EL1 - Instruction set attribute register 1
|
||||
// x--------------------------------------------------x
|
||||
// | Name | bits | visible |
|
||||
// |--------------------------------------------------|
|
||||
// | GPI | [31-28] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | GPA | [27-24] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | LRCPC | [23-20] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | FCMA | [19-16] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | JSCVT | [15-12] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | API | [11-8] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | APA | [7-4] | y |
|
||||
// |--------------------------------------------------|
|
||||
// | DPB | [3-0] | y |
|
||||
// x--------------------------------------------------x
|
||||
|
||||
// if instAttrReg1&(0xf<<28) != 0 {
|
||||
// fmt.Println("GPI")
|
||||
// }
|
||||
if instAttrReg1&(0xf<<28) != 24 {
|
||||
f |= GPA
|
||||
}
|
||||
if instAttrReg1&(0xf<<20) != 0 {
|
||||
f |= LRCPC
|
||||
}
|
||||
if instAttrReg1&(0xf<<16) != 0 {
|
||||
f |= FCMA
|
||||
}
|
||||
if instAttrReg1&(0xf<<12) != 0 {
|
||||
f |= JSCVT
|
||||
}
|
||||
// if instAttrReg1&(0xf<<8) != 0 {
|
||||
// fmt.Println("API")
|
||||
// }
|
||||
// if instAttrReg1&(0xf<<4) != 0 {
|
||||
// fmt.Println("APA")
|
||||
// }
|
||||
if instAttrReg1&(0xf<<0) != 0 {
|
||||
f |= DCPOP
|
||||
}
|
||||
c.Arm = f
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module github.com/klauspost/cpuid
|
||||
|
||||
go 1.12
|
||||
+408
@@ -0,0 +1,408 @@
|
||||
// Code generated by command: go run gen.go -out galois_gen_amd64.s -stubs galois_gen_amd64.go. DO NOT EDIT.
|
||||
|
||||
// +build !appengine
|
||||
// +build !noasm
|
||||
// +build !nogen
|
||||
// +build gc
|
||||
|
||||
package reedsolomon
|
||||
|
||||
// mulAvxTwo_1x1 takes 1 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_1x2 takes 1 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_1x3 takes 1 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_1x4 takes 1 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_1x5 takes 1 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_1x6 takes 1 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_1x7 takes 1 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_1x8 takes 1 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_1x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x1 takes 2 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x2 takes 2 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x3 takes 2 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x4 takes 2 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x5 takes 2 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x6 takes 2 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x7 takes 2 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_2x8 takes 2 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_2x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x1 takes 3 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x2 takes 3 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x3 takes 3 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x4 takes 3 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x5 takes 3 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x6 takes 3 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x7 takes 3 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_3x8 takes 3 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_3x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x1 takes 4 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x2 takes 4 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x3 takes 4 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x4 takes 4 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x5 takes 4 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x6 takes 4 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x7 takes 4 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_4x8 takes 4 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_4x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x1 takes 5 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x2 takes 5 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x3 takes 5 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x4 takes 5 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x5 takes 5 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x6 takes 5 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x7 takes 5 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_5x8 takes 5 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_5x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x1 takes 6 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x2 takes 6 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x3 takes 6 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x4 takes 6 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x5 takes 6 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x6 takes 6 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x7 takes 6 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_6x8 takes 6 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_6x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x1 takes 7 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x2 takes 7 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x3 takes 7 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x4 takes 7 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x5 takes 7 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x6 takes 7 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x7 takes 7 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_7x8 takes 7 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_7x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x1 takes 8 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x2 takes 8 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x3 takes 8 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x4 takes 8 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x5 takes 8 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x6 takes 8 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x7 takes 8 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_8x8 takes 8 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_8x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x1 takes 9 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x2 takes 9 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x3 takes 9 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x4 takes 9 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x5 takes 9 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x6 takes 9 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x7 takes 9 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_9x8 takes 9 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_9x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x1 takes 10 inputs and produces 1 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x1(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x2 takes 10 inputs and produces 2 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x2(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x3 takes 10 inputs and produces 3 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x3(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x4 takes 10 inputs and produces 4 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x4(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x5 takes 10 inputs and produces 5 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x5(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x6 takes 10 inputs and produces 6 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x6(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x7 takes 10 inputs and produces 7 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x7(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
|
||||
// mulAvxTwo_10x8 takes 10 inputs and produces 8 outputs.
|
||||
// The output is initialized to 0.
|
||||
//go:noescape
|
||||
func mulAvxTwo_10x8(matrix []byte, in [][]byte, out [][]byte, start int, n int)
|
||||
+18526
File diff suppressed because it is too large
Load Diff
+11
@@ -0,0 +1,11 @@
|
||||
//+build !amd64 noasm appengine gccgo nogen
|
||||
|
||||
package reedsolomon
|
||||
|
||||
const maxAvx2Inputs = 0
|
||||
const maxAvx2Outputs = 0
|
||||
const avx2CodeGen = false
|
||||
|
||||
func galMulSlicesAvx2(matrix []byte, in, out [][]byte, start, stop int) int {
|
||||
panic("avx2 codegen not available")
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
// Code generated by command: go generate gen.go. DO NOT EDIT.
|
||||
|
||||
// +build !appengine
|
||||
// +build !noasm
|
||||
// +build gc
|
||||
// +build !nogen
|
||||
|
||||
package reedsolomon
|
||||
|
||||
import "fmt"
|
||||
|
||||
const avx2CodeGen = true
|
||||
const maxAvx2Inputs = 10
|
||||
const maxAvx2Outputs = 8
|
||||
|
||||
func galMulSlicesAvx2(matrix []byte, in, out [][]byte, start, stop int) int {
|
||||
n := stop - start
|
||||
n = (n >> 5) << 5
|
||||
|
||||
switch len(in) {
|
||||
case 1:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_1x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_1x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_1x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_1x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_1x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_1x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_1x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_1x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 2:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_2x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_2x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_2x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_2x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_2x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_2x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_2x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_2x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 3:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_3x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_3x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_3x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_3x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_3x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_3x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_3x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_3x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 4:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_4x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_4x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_4x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_4x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_4x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_4x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_4x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_4x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 5:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_5x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_5x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_5x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_5x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_5x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_5x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_5x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_5x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 6:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_6x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_6x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_6x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_6x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_6x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_6x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_6x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_6x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 7:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_7x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_7x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_7x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_7x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_7x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_7x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_7x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_7x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 8:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_8x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_8x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_8x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_8x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_8x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_8x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_8x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_8x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 9:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_9x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_9x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_9x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_9x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_9x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_9x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_9x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_9x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
case 10:
|
||||
switch len(out) {
|
||||
case 1:
|
||||
mulAvxTwo_10x1(matrix, in, out, start, n)
|
||||
return n
|
||||
case 2:
|
||||
mulAvxTwo_10x2(matrix, in, out, start, n)
|
||||
return n
|
||||
case 3:
|
||||
mulAvxTwo_10x3(matrix, in, out, start, n)
|
||||
return n
|
||||
case 4:
|
||||
mulAvxTwo_10x4(matrix, in, out, start, n)
|
||||
return n
|
||||
case 5:
|
||||
mulAvxTwo_10x5(matrix, in, out, start, n)
|
||||
return n
|
||||
case 6:
|
||||
mulAvxTwo_10x6(matrix, in, out, start, n)
|
||||
return n
|
||||
case 7:
|
||||
mulAvxTwo_10x7(matrix, in, out, start, n)
|
||||
return n
|
||||
case 8:
|
||||
mulAvxTwo_10x8(matrix, in, out, start, n)
|
||||
return n
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("unhandled size: %dx%d", len(in), len(out)))
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//+build !amd64 noasm appengine gccgo
|
||||
|
||||
// Copyright 2020, Klaus Post, see LICENSE for details.
|
||||
|
||||
package reedsolomon
|
||||
|
||||
func (r *reedSolomon) codeSomeShardsAvx512(matrixRows, inputs, outputs [][]byte, outputCount, byteCount int) {
|
||||
panic("codeSomeShardsAvx512 should not be called if built without asm")
|
||||
}
|
||||
|
||||
func (r *reedSolomon) codeSomeShardsAvx512P(matrixRows, inputs, outputs [][]byte, outputCount, byteCount int) {
|
||||
panic("codeSomeShardsAvx512P should not be called if built without asm")
|
||||
}
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
//+build generate
|
||||
|
||||
//go:generate go run gen.go -out galois_gen_amd64.s -stubs galois_gen_amd64.go
|
||||
//go:generate gofmt -w galois_gen_switch_amd64.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
. "github.com/mmcloughlin/avo/build"
|
||||
"github.com/mmcloughlin/avo/buildtags"
|
||||
. "github.com/mmcloughlin/avo/operand"
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
// Technically we can do slightly bigger, but we stay reasonable.
|
||||
const inputMax = 10
|
||||
const outputMax = 8
|
||||
|
||||
var switchDefs [inputMax][outputMax]string
|
||||
var switchDefsX [inputMax][outputMax]string
|
||||
|
||||
const perLoopBits = 5
|
||||
const perLoop = 1 << perLoopBits
|
||||
|
||||
func main() {
|
||||
Constraint(buildtags.Not("appengine").ToConstraint())
|
||||
Constraint(buildtags.Not("noasm").ToConstraint())
|
||||
Constraint(buildtags.Not("nogen").ToConstraint())
|
||||
Constraint(buildtags.Term("gc").ToConstraint())
|
||||
|
||||
for i := 1; i <= inputMax; i++ {
|
||||
for j := 1; j <= outputMax; j++ {
|
||||
//genMulAvx2(fmt.Sprintf("mulAvxTwoXor_%dx%d", i, j), i, j, true)
|
||||
genMulAvx2(fmt.Sprintf("mulAvxTwo_%dx%d", i, j), i, j, false)
|
||||
}
|
||||
}
|
||||
f, err := os.Create("galois_gen_switch_amd64.go")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
w := bufio.NewWriter(f)
|
||||
defer w.Flush()
|
||||
w.WriteString(`// Code generated by command: go generate ` + os.Getenv("GOFILE") + `. DO NOT EDIT.
|
||||
|
||||
// +build !appengine
|
||||
// +build !noasm
|
||||
// +build gc
|
||||
// +build !nogen
|
||||
|
||||
package reedsolomon
|
||||
|
||||
import "fmt"
|
||||
|
||||
`)
|
||||
|
||||
w.WriteString("const avx2CodeGen = true\n")
|
||||
w.WriteString(fmt.Sprintf("const maxAvx2Inputs = %d\nconst maxAvx2Outputs = %d\n", inputMax, outputMax))
|
||||
w.WriteString(`
|
||||
|
||||
func galMulSlicesAvx2(matrix []byte, in, out [][]byte, start, stop int) int {
|
||||
n := stop-start
|
||||
`)
|
||||
|
||||
w.WriteString(fmt.Sprintf("n = (n>>%d)<<%d\n\n", perLoopBits, perLoopBits))
|
||||
w.WriteString(`switch len(in) {
|
||||
`)
|
||||
for in, defs := range switchDefs[:] {
|
||||
w.WriteString(fmt.Sprintf(" case %d:\n switch len(out) {\n", in+1))
|
||||
for out, def := range defs[:] {
|
||||
w.WriteString(fmt.Sprintf(" case %d:\n", out+1))
|
||||
w.WriteString(def)
|
||||
}
|
||||
w.WriteString("}\n")
|
||||
}
|
||||
w.WriteString(`}
|
||||
panic(fmt.Sprintf("unhandled size: %dx%d", len(in), len(out)))
|
||||
}
|
||||
`)
|
||||
Generate()
|
||||
}
|
||||
|
||||
func genMulAvx2(name string, inputs int, outputs int, xor bool) {
|
||||
total := inputs * outputs
|
||||
|
||||
doc := []string{
|
||||
fmt.Sprintf("%s takes %d inputs and produces %d outputs.", name, inputs, outputs),
|
||||
}
|
||||
if !xor {
|
||||
doc = append(doc, "The output is initialized to 0.")
|
||||
}
|
||||
|
||||
// Load shuffle masks on every use.
|
||||
var loadNone bool
|
||||
// Use registers for destination registers.
|
||||
var regDst = true
|
||||
|
||||
// lo, hi, 1 in, 1 out, 2 tmp, 1 mask
|
||||
est := total*2 + outputs + 5
|
||||
if outputs == 1 {
|
||||
// We don't need to keep a copy of the input if only 1 output.
|
||||
est -= 2
|
||||
}
|
||||
|
||||
if est > 16 {
|
||||
loadNone = true
|
||||
// We run out of GP registers first, now.
|
||||
if inputs+outputs > 12 {
|
||||
regDst = false
|
||||
}
|
||||
}
|
||||
|
||||
TEXT(name, 0, fmt.Sprintf("func(matrix []byte, in [][]byte, out [][]byte, start, n int)"))
|
||||
|
||||
// SWITCH DEFINITION:
|
||||
s := fmt.Sprintf(" mulAvxTwo_%dx%d(matrix, in, out, start, n)\n", inputs, outputs)
|
||||
s += fmt.Sprintf("\t\t\t\treturn n\n")
|
||||
switchDefs[inputs-1][outputs-1] = s
|
||||
|
||||
if loadNone {
|
||||
Comment("Loading no tables to registers")
|
||||
} else {
|
||||
// loadNone == false
|
||||
Comment("Loading all tables to registers")
|
||||
}
|
||||
|
||||
Doc(doc...)
|
||||
Pragma("noescape")
|
||||
Commentf("Full registers estimated %d YMM used", est)
|
||||
|
||||
length := Load(Param("n"), GP64())
|
||||
matrixBase := GP64()
|
||||
MOVQ(Param("matrix").Base().MustAddr(), matrixBase)
|
||||
SHRQ(U8(perLoopBits), length)
|
||||
TESTQ(length, length)
|
||||
JZ(LabelRef(name + "_end"))
|
||||
|
||||
dst := make([]reg.VecVirtual, outputs)
|
||||
dstPtr := make([]reg.GPVirtual, outputs)
|
||||
outBase := Param("out").Base().MustAddr()
|
||||
outSlicePtr := GP64()
|
||||
MOVQ(outBase, outSlicePtr)
|
||||
for i := range dst {
|
||||
dst[i] = YMM()
|
||||
if !regDst {
|
||||
continue
|
||||
}
|
||||
ptr := GP64()
|
||||
MOVQ(Mem{Base: outSlicePtr, Disp: i * 24}, ptr)
|
||||
dstPtr[i] = ptr
|
||||
}
|
||||
|
||||
inLo := make([]reg.VecVirtual, total)
|
||||
inHi := make([]reg.VecVirtual, total)
|
||||
|
||||
for i := range inLo {
|
||||
if loadNone {
|
||||
break
|
||||
}
|
||||
tableLo := YMM()
|
||||
tableHi := YMM()
|
||||
VMOVDQU(Mem{Base: matrixBase, Disp: i * 64}, tableLo)
|
||||
VMOVDQU(Mem{Base: matrixBase, Disp: i*64 + 32}, tableHi)
|
||||
inLo[i] = tableLo
|
||||
inHi[i] = tableHi
|
||||
}
|
||||
|
||||
inPtrs := make([]reg.GPVirtual, inputs)
|
||||
inSlicePtr := GP64()
|
||||
MOVQ(Param("in").Base().MustAddr(), inSlicePtr)
|
||||
for i := range inPtrs {
|
||||
ptr := GP64()
|
||||
MOVQ(Mem{Base: inSlicePtr, Disp: i * 24}, ptr)
|
||||
inPtrs[i] = ptr
|
||||
}
|
||||
|
||||
tmpMask := GP64()
|
||||
MOVQ(U32(15), tmpMask)
|
||||
lowMask := YMM()
|
||||
MOVQ(tmpMask, lowMask.AsX())
|
||||
VPBROADCASTB(lowMask.AsX(), lowMask)
|
||||
|
||||
offset := GP64()
|
||||
MOVQ(Param("start").MustAddr(), offset)
|
||||
Label(name + "_loop")
|
||||
if xor {
|
||||
Commentf("Load %d outputs", outputs)
|
||||
} else {
|
||||
Commentf("Clear %d outputs", outputs)
|
||||
}
|
||||
for i := range dst {
|
||||
if xor {
|
||||
if regDst {
|
||||
VMOVDQU(Mem{Base: dstPtr[i], Index: offset, Scale: 1}, dst[i])
|
||||
continue
|
||||
}
|
||||
ptr := GP64()
|
||||
MOVQ(outBase, ptr)
|
||||
VMOVDQU(Mem{Base: ptr, Index: offset, Scale: 1}, dst[i])
|
||||
} else {
|
||||
VPXOR(dst[i], dst[i], dst[i])
|
||||
}
|
||||
}
|
||||
|
||||
lookLow, lookHigh := YMM(), YMM()
|
||||
inLow, inHigh := YMM(), YMM()
|
||||
for i := range inPtrs {
|
||||
Commentf("Load and process 32 bytes from input %d to %d outputs", i, outputs)
|
||||
VMOVDQU(Mem{Base: inPtrs[i], Index: offset, Scale: 1}, inLow)
|
||||
VPSRLQ(U8(4), inLow, inHigh)
|
||||
VPAND(lowMask, inLow, inLow)
|
||||
VPAND(lowMask, inHigh, inHigh)
|
||||
for j := range dst {
|
||||
if loadNone {
|
||||
VMOVDQU(Mem{Base: matrixBase, Disp: 64 * (i*outputs + j)}, lookLow)
|
||||
VMOVDQU(Mem{Base: matrixBase, Disp: 32 + 64*(i*outputs+j)}, lookHigh)
|
||||
VPSHUFB(inLow, lookLow, lookLow)
|
||||
VPSHUFB(inHigh, lookHigh, lookHigh)
|
||||
} else {
|
||||
VPSHUFB(inLow, inLo[i*outputs+j], lookLow)
|
||||
VPSHUFB(inHigh, inHi[i*outputs+j], lookHigh)
|
||||
}
|
||||
VPXOR(lookLow, lookHigh, lookLow)
|
||||
VPXOR(lookLow, dst[j], dst[j])
|
||||
}
|
||||
}
|
||||
Commentf("Store %d outputs", outputs)
|
||||
for i := range dst {
|
||||
if regDst {
|
||||
VMOVDQU(dst[i], Mem{Base: dstPtr[i], Index: offset, Scale: 1})
|
||||
continue
|
||||
}
|
||||
ptr := GP64()
|
||||
MOVQ(Mem{Base: outSlicePtr, Disp: i * 24}, ptr)
|
||||
VMOVDQU(dst[i], Mem{Base: ptr, Index: offset, Scale: 1})
|
||||
}
|
||||
Comment("Prepare for next loop")
|
||||
ADDQ(U8(perLoop), offset)
|
||||
DECQ(length)
|
||||
JNZ(LabelRef(name + "_loop"))
|
||||
VZEROUPPER()
|
||||
|
||||
Label(name + "_end")
|
||||
RET()
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
module github.com/klauspost/reedsolomon
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/klauspost/cpuid v1.2.4
|
||||
)
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
github.com/klauspost/cpuid v1.2.4 h1:EBfaK0SWSwk+fgk6efYFWdzl8MwRWoOO1gkmiaTXPW4=
|
||||
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2018, Michael McLoughlin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
// Package attr provides attributes for text and data sections.
|
||||
package attr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Attribute represents TEXT or DATA flags.
|
||||
type Attribute uint16
|
||||
|
||||
// Reference: https://github.com/golang/go/blob/aafe257390cc9048e8b5df898fabd79a9e0d4c39/src/runtime/textflag.h#L11-L37
|
||||
//
|
||||
// // Don't profile the marked routine. This flag is deprecated.
|
||||
// #define NOPROF 1
|
||||
// // It is ok for the linker to get multiple of these symbols. It will
|
||||
// // pick one of the duplicates to use.
|
||||
// #define DUPOK 2
|
||||
// // Don't insert stack check preamble.
|
||||
// #define NOSPLIT 4
|
||||
// // Put this data in a read-only section.
|
||||
// #define RODATA 8
|
||||
// // This data contains no pointers.
|
||||
// #define NOPTR 16
|
||||
// // This is a wrapper function and should not count as disabling 'recover'.
|
||||
// #define WRAPPER 32
|
||||
// // This function uses its incoming context register.
|
||||
// #define NEEDCTXT 64
|
||||
// // Allocate a word of thread local storage and store the offset from the
|
||||
// // thread local base to the thread local storage in this variable.
|
||||
// #define TLSBSS 256
|
||||
// // Do not insert instructions to allocate a stack frame for this function.
|
||||
// // Only valid on functions that declare a frame size of 0.
|
||||
// // TODO(mwhudson): only implemented for ppc64x at present.
|
||||
// #define NOFRAME 512
|
||||
// // Function can call reflect.Type.Method or reflect.Type.MethodByName.
|
||||
// #define REFLECTMETHOD 1024
|
||||
// // Function is the top of the call stack. Call stack unwinders should stop
|
||||
// // at this function.
|
||||
// #define TOPFRAME 2048
|
||||
//
|
||||
const (
|
||||
NOPROF Attribute = 1 << iota
|
||||
DUPOK
|
||||
NOSPLIT
|
||||
RODATA
|
||||
NOPTR
|
||||
WRAPPER
|
||||
NEEDCTXT
|
||||
_
|
||||
TLSBSS
|
||||
NOFRAME
|
||||
REFLECTMETHOD
|
||||
TOPFRAME
|
||||
)
|
||||
|
||||
// Asm returns a representation of the attributes in assembly syntax. This may use macros from "textflags.h"; see ContainsTextFlags() to determine if this header is required.
|
||||
func (a Attribute) Asm() string {
|
||||
parts, rest := a.split()
|
||||
if len(parts) == 0 || rest != 0 {
|
||||
parts = append(parts, fmt.Sprintf("%d", rest))
|
||||
}
|
||||
return strings.Join(parts, "|")
|
||||
}
|
||||
|
||||
// ContainsTextFlags returns whether the Asm() representation requires macros in "textflags.h".
|
||||
func (a Attribute) ContainsTextFlags() bool {
|
||||
flags, _ := a.split()
|
||||
return len(flags) > 0
|
||||
}
|
||||
|
||||
// split splits a into known flags and any remaining bits.
|
||||
func (a Attribute) split() ([]string, Attribute) {
|
||||
var flags []string
|
||||
var rest Attribute
|
||||
for a != 0 {
|
||||
i := uint(bits.TrailingZeros16(uint16(a)))
|
||||
bit := Attribute(1) << i
|
||||
if flag := attrname[bit]; flag != "" {
|
||||
flags = append(flags, flag)
|
||||
} else {
|
||||
rest |= bit
|
||||
}
|
||||
a ^= bit
|
||||
}
|
||||
return flags, rest
|
||||
}
|
||||
|
||||
var attrname = map[Attribute]string{
|
||||
NOPROF: "NOPROF",
|
||||
DUPOK: "DUPOK",
|
||||
NOSPLIT: "NOSPLIT",
|
||||
RODATA: "RODATA",
|
||||
NOPTR: "NOPTR",
|
||||
WRAPPER: "WRAPPER",
|
||||
NEEDCTXT: "NEEDCTXT",
|
||||
TLSBSS: "TLSBSS",
|
||||
NOFRAME: "NOFRAME",
|
||||
REFLECTMETHOD: "REFLECTMETHOD",
|
||||
TOPFRAME: "TOPFRAME",
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package build
|
||||
|
||||
import "github.com/mmcloughlin/avo/attr"
|
||||
|
||||
// TEXT and DATA attribute values included for convenience.
|
||||
const (
|
||||
NOPROF = attr.NOPROF
|
||||
DUPOK = attr.DUPOK
|
||||
NOSPLIT = attr.NOSPLIT
|
||||
RODATA = attr.RODATA
|
||||
NOPTR = attr.NOPTR
|
||||
WRAPPER = attr.WRAPPER
|
||||
NEEDCTXT = attr.NEEDCTXT
|
||||
TLSBSS = attr.TLSBSS
|
||||
NOFRAME = attr.NOFRAME
|
||||
REFLECTMETHOD = attr.REFLECTMETHOD
|
||||
TOPFRAME = attr.TOPFRAME
|
||||
)
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
|
||||
"github.com/mmcloughlin/avo/pass"
|
||||
"github.com/mmcloughlin/avo/printer"
|
||||
)
|
||||
|
||||
// Config contains options for an avo main function.
|
||||
type Config struct {
|
||||
ErrOut io.Writer
|
||||
MaxErrors int // max errors to report; 0 means unlimited
|
||||
CPUProfile io.WriteCloser
|
||||
Passes []pass.Interface
|
||||
}
|
||||
|
||||
// Main is the standard main function for an avo program. This extracts the
|
||||
// result from the build Context (logging and exiting on error), and performs
|
||||
// configured passes.
|
||||
func Main(cfg *Config, context *Context) int {
|
||||
diag := log.New(cfg.ErrOut, "", 0)
|
||||
|
||||
if cfg.CPUProfile != nil {
|
||||
defer cfg.CPUProfile.Close()
|
||||
if err := pprof.StartCPUProfile(cfg.CPUProfile); err != nil {
|
||||
diag.Println("could not start CPU profile: ", err)
|
||||
return 1
|
||||
}
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
f, err := context.Result()
|
||||
if err != nil {
|
||||
LogError(diag, err, cfg.MaxErrors)
|
||||
return 1
|
||||
}
|
||||
|
||||
p := pass.Concat(cfg.Passes...)
|
||||
if err := p.Execute(f); err != nil {
|
||||
diag.Println(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// Flags represents CLI flags for an avo program.
|
||||
type Flags struct {
|
||||
errout *outputValue
|
||||
allerrors bool
|
||||
cpuprof *outputValue
|
||||
pkg string
|
||||
printers []*printerValue
|
||||
}
|
||||
|
||||
// NewFlags initializes avo flags for the given FlagSet.
|
||||
func NewFlags(fs *flag.FlagSet) *Flags {
|
||||
f := &Flags{}
|
||||
|
||||
f.errout = newOutputValue(os.Stderr)
|
||||
fs.Var(f.errout, "log", "diagnostics output")
|
||||
|
||||
fs.BoolVar(&f.allerrors, "e", false, "no limit on number of errors reported")
|
||||
|
||||
f.cpuprof = newOutputValue(nil)
|
||||
fs.Var(f.cpuprof, "cpuprofile", "write cpu profile to `file`")
|
||||
|
||||
fs.StringVar(&f.pkg, "pkg", "", "package name (defaults to current directory name)")
|
||||
|
||||
goasm := newPrinterValue(printer.NewGoAsm, os.Stdout)
|
||||
fs.Var(goasm, "out", "assembly output")
|
||||
f.printers = append(f.printers, goasm)
|
||||
|
||||
stubs := newPrinterValue(printer.NewStubs, nil)
|
||||
fs.Var(stubs, "stubs", "go stub file")
|
||||
f.printers = append(f.printers, stubs)
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// Config builds a configuration object based on flag values.
|
||||
func (f *Flags) Config() *Config {
|
||||
pc := printer.NewGoRunConfig()
|
||||
if f.pkg != "" {
|
||||
pc.Pkg = f.pkg
|
||||
}
|
||||
passes := []pass.Interface{pass.Compile}
|
||||
for _, pv := range f.printers {
|
||||
p := pv.Build(pc)
|
||||
if p != nil {
|
||||
passes = append(passes, p)
|
||||
}
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
ErrOut: f.errout.w,
|
||||
MaxErrors: 10,
|
||||
CPUProfile: f.cpuprof.w,
|
||||
Passes: passes,
|
||||
}
|
||||
|
||||
if f.allerrors {
|
||||
cfg.MaxErrors = 0
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
type outputValue struct {
|
||||
w io.WriteCloser
|
||||
filename string
|
||||
}
|
||||
|
||||
func newOutputValue(dflt io.WriteCloser) *outputValue {
|
||||
return &outputValue{w: dflt}
|
||||
}
|
||||
|
||||
func (o *outputValue) String() string {
|
||||
if o == nil {
|
||||
return ""
|
||||
}
|
||||
return o.filename
|
||||
}
|
||||
|
||||
func (o *outputValue) Set(s string) error {
|
||||
o.filename = s
|
||||
if s == "-" {
|
||||
o.w = nopwritecloser{os.Stdout}
|
||||
return nil
|
||||
}
|
||||
f, err := os.Create(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.w = f
|
||||
return nil
|
||||
}
|
||||
|
||||
type printerValue struct {
|
||||
*outputValue
|
||||
Builder printer.Builder
|
||||
}
|
||||
|
||||
func newPrinterValue(b printer.Builder, dflt io.WriteCloser) *printerValue {
|
||||
return &printerValue{
|
||||
outputValue: newOutputValue(dflt),
|
||||
Builder: b,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *printerValue) Build(cfg printer.Config) pass.Interface {
|
||||
if p.outputValue.w == nil {
|
||||
return nil
|
||||
}
|
||||
return &pass.Output{
|
||||
Writer: p.outputValue.w,
|
||||
Printer: p.Builder(cfg),
|
||||
}
|
||||
}
|
||||
|
||||
// nopwritecloser wraps a Writer and provides a null implementation of Close().
|
||||
type nopwritecloser struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (nopwritecloser) Close() error { return nil }
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
|
||||
"github.com/mmcloughlin/avo/attr"
|
||||
"github.com/mmcloughlin/avo/buildtags"
|
||||
"github.com/mmcloughlin/avo/gotypes"
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
// Context maintains state for incrementally building an avo File.
|
||||
type Context struct {
|
||||
pkg *packages.Package
|
||||
file *ir.File
|
||||
function *ir.Function
|
||||
global *ir.Global
|
||||
errs ErrorList
|
||||
reg.Collection
|
||||
}
|
||||
|
||||
// NewContext initializes an empty build Context.
|
||||
func NewContext() *Context {
|
||||
return &Context{
|
||||
file: ir.NewFile(),
|
||||
Collection: *reg.NewCollection(),
|
||||
}
|
||||
}
|
||||
|
||||
// Package sets the package the generated file will belong to. Required to be able to reference types in the package.
|
||||
func (c *Context) Package(path string) {
|
||||
cfg := &packages.Config{
|
||||
Mode: packages.NeedTypes | packages.NeedDeps | packages.NeedImports,
|
||||
}
|
||||
pkgs, err := packages.Load(cfg, path)
|
||||
if err != nil {
|
||||
c.adderror(err)
|
||||
return
|
||||
}
|
||||
pkg := pkgs[0]
|
||||
if len(pkg.Errors) > 0 {
|
||||
for _, err := range pkg.Errors {
|
||||
c.adderror(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
c.pkg = pkg
|
||||
}
|
||||
|
||||
// Constraints sets build constraints for the file.
|
||||
func (c *Context) Constraints(t buildtags.ConstraintsConvertable) {
|
||||
cs := t.ToConstraints()
|
||||
if err := cs.Validate(); err != nil {
|
||||
c.adderror(err)
|
||||
return
|
||||
}
|
||||
c.file.Constraints = cs
|
||||
}
|
||||
|
||||
// Constraint appends a constraint to the file's build constraints.
|
||||
func (c *Context) Constraint(t buildtags.ConstraintConvertable) {
|
||||
c.Constraints(append(c.file.Constraints, t.ToConstraint()))
|
||||
}
|
||||
|
||||
// ConstraintExpr appends a constraint to the file's build constraints. The
|
||||
// constraint to add is parsed from the given expression. The expression should
|
||||
// look the same as the content following "// +build " in regular build
|
||||
// constraint comments.
|
||||
func (c *Context) ConstraintExpr(expr string) {
|
||||
constraint, err := buildtags.ParseConstraint(expr)
|
||||
if err != nil {
|
||||
c.adderror(err)
|
||||
return
|
||||
}
|
||||
c.Constraint(constraint)
|
||||
}
|
||||
|
||||
// Function starts building a new function with the given name.
|
||||
func (c *Context) Function(name string) {
|
||||
c.function = ir.NewFunction(name)
|
||||
c.file.AddSection(c.function)
|
||||
}
|
||||
|
||||
// Doc sets documentation comment lines for the currently active function.
|
||||
func (c *Context) Doc(lines ...string) {
|
||||
c.activefunc().Doc = lines
|
||||
}
|
||||
|
||||
// Pragma adds a compiler directive to the currently active function.
|
||||
func (c *Context) Pragma(directive string, args ...string) {
|
||||
c.activefunc().AddPragma(directive, args...)
|
||||
}
|
||||
|
||||
// Attributes sets function attributes for the currently active function.
|
||||
func (c *Context) Attributes(a attr.Attribute) {
|
||||
c.activefunc().Attributes = a
|
||||
}
|
||||
|
||||
// Signature sets the signature for the currently active function.
|
||||
func (c *Context) Signature(s *gotypes.Signature) {
|
||||
c.activefunc().SetSignature(s)
|
||||
}
|
||||
|
||||
// SignatureExpr parses the signature expression and sets it as the active function's signature.
|
||||
func (c *Context) SignatureExpr(expr string) {
|
||||
s, err := gotypes.ParseSignatureInPackage(c.types(), expr)
|
||||
if err != nil {
|
||||
c.adderror(err)
|
||||
return
|
||||
}
|
||||
c.Signature(s)
|
||||
}
|
||||
|
||||
// Implement starts building a function of the given name, whose type is
|
||||
// specified by a stub in the containing package.
|
||||
func (c *Context) Implement(name string) {
|
||||
pkg := c.types()
|
||||
if pkg == nil {
|
||||
c.adderrormessage("no package specified")
|
||||
return
|
||||
}
|
||||
s, err := gotypes.LookupSignature(pkg, name)
|
||||
if err != nil {
|
||||
c.adderror(err)
|
||||
return
|
||||
}
|
||||
c.Function(name)
|
||||
c.Signature(s)
|
||||
}
|
||||
|
||||
func (c *Context) types() *types.Package {
|
||||
if c.pkg == nil {
|
||||
return nil
|
||||
}
|
||||
return c.pkg.Types
|
||||
}
|
||||
|
||||
// AllocLocal allocates size bytes in the stack of the currently active function.
|
||||
// Returns a reference to the base pointer for the newly allocated region.
|
||||
func (c *Context) AllocLocal(size int) operand.Mem {
|
||||
return c.activefunc().AllocLocal(size)
|
||||
}
|
||||
|
||||
// Instruction adds an instruction to the active function.
|
||||
func (c *Context) Instruction(i *ir.Instruction) {
|
||||
c.activefunc().AddInstruction(i)
|
||||
}
|
||||
|
||||
// Label adds a label to the active function.
|
||||
func (c *Context) Label(name string) {
|
||||
c.activefunc().AddLabel(ir.Label(name))
|
||||
}
|
||||
|
||||
// Comment adds comment lines to the active function.
|
||||
func (c *Context) Comment(lines ...string) {
|
||||
c.activefunc().AddComment(lines...)
|
||||
}
|
||||
|
||||
// Commentf adds a formtted comment line.
|
||||
func (c *Context) Commentf(format string, a ...interface{}) {
|
||||
c.Comment(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func (c *Context) activefunc() *ir.Function {
|
||||
if c.function == nil {
|
||||
c.adderrormessage("no active function")
|
||||
return ir.NewFunction("")
|
||||
}
|
||||
return c.function
|
||||
}
|
||||
|
||||
//go:generate avogen -output zinstructions.go build
|
||||
|
||||
// StaticGlobal adds a new static data section to the file and returns a pointer to it.
|
||||
func (c *Context) StaticGlobal(name string) operand.Mem {
|
||||
c.global = ir.NewStaticGlobal(name)
|
||||
c.file.AddSection(c.global)
|
||||
return c.global.Base()
|
||||
}
|
||||
|
||||
// DataAttributes sets the attributes on the current active global data section.
|
||||
func (c *Context) DataAttributes(a attr.Attribute) {
|
||||
c.activeglobal().Attributes = a
|
||||
}
|
||||
|
||||
// AddDatum adds constant v at offset to the current active global data section.
|
||||
func (c *Context) AddDatum(offset int, v operand.Constant) {
|
||||
if err := c.activeglobal().AddDatum(ir.NewDatum(offset, v)); err != nil {
|
||||
c.adderror(err)
|
||||
}
|
||||
}
|
||||
|
||||
// AppendDatum appends a constant to the current active global data section.
|
||||
func (c *Context) AppendDatum(v operand.Constant) {
|
||||
c.activeglobal().Append(v)
|
||||
}
|
||||
|
||||
func (c *Context) activeglobal() *ir.Global {
|
||||
if c.global == nil {
|
||||
c.adderrormessage("no active global")
|
||||
return ir.NewStaticGlobal("")
|
||||
}
|
||||
return c.global
|
||||
}
|
||||
|
||||
func (c *Context) adderror(err error) {
|
||||
c.errs.addext(err)
|
||||
}
|
||||
|
||||
func (c *Context) adderrormessage(msg string) {
|
||||
c.adderror(errors.New(msg))
|
||||
}
|
||||
|
||||
// Result returns the built file and any accumulated errors.
|
||||
func (c *Context) Result() (*ir.File, error) {
|
||||
return c.file, c.errs.Err()
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package build provides an assembly-like interface for incremental building of avo Files.
|
||||
package build
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/mmcloughlin/avo/internal/stack"
|
||||
"github.com/mmcloughlin/avo/src"
|
||||
)
|
||||
|
||||
// Error represents an error during building, optionally tagged with the position at which it happened.
|
||||
type Error struct {
|
||||
Position src.Position
|
||||
Err error
|
||||
}
|
||||
|
||||
// exterr constructs an Error with position derived from the first frame in the
|
||||
// call stack outside this package.
|
||||
func exterr(err error) Error {
|
||||
e := Error{Err: err}
|
||||
if f := stack.ExternalCaller(); f != nil {
|
||||
e.Position = src.FramePosition(*f).Relwd()
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
msg := e.Err.Error()
|
||||
if e.Position.IsValid() {
|
||||
return e.Position.String() + ": " + msg
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// ErrorList is a collection of errors for a source file.
|
||||
type ErrorList []Error
|
||||
|
||||
// Add appends an error to the list.
|
||||
func (e *ErrorList) Add(err Error) {
|
||||
*e = append(*e, err)
|
||||
}
|
||||
|
||||
// AddAt appends an error at position p.
|
||||
func (e *ErrorList) AddAt(p src.Position, err error) {
|
||||
e.Add(Error{p, err})
|
||||
}
|
||||
|
||||
// addext appends an error to the list, tagged with the
|
||||
func (e *ErrorList) addext(err error) {
|
||||
e.Add(exterr(err))
|
||||
}
|
||||
|
||||
// Err returns an error equivalent to this error list.
|
||||
// If the list is empty, Err returns nil.
|
||||
func (e ErrorList) Err() error {
|
||||
if len(e) == 0 {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// An ErrorList implements the error interface.
|
||||
func (e ErrorList) Error() string {
|
||||
switch len(e) {
|
||||
case 0:
|
||||
return "no errors"
|
||||
case 1:
|
||||
return e[0].Error()
|
||||
}
|
||||
return fmt.Sprintf("%s (and %d more errors)", e[0], len(e)-1)
|
||||
}
|
||||
|
||||
// LogError logs a list of errors, one error per line, if the err parameter is
|
||||
// an ErrorList. Otherwise it just logs the err string. Reports at most max
|
||||
// errors, or unlimited if max is 0.
|
||||
func LogError(l *log.Logger, err error, max int) {
|
||||
if list, ok := err.(ErrorList); ok {
|
||||
for i, e := range list {
|
||||
if max > 0 && i == max {
|
||||
l.Print("too many errors")
|
||||
return
|
||||
}
|
||||
l.Printf("%s\n", e)
|
||||
}
|
||||
} else if err != nil {
|
||||
l.Printf("%s\n", err)
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/mmcloughlin/avo/attr"
|
||||
"github.com/mmcloughlin/avo/buildtags"
|
||||
"github.com/mmcloughlin/avo/gotypes"
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
// ctx provides a global build context.
|
||||
var ctx = NewContext()
|
||||
|
||||
// TEXT starts building a new function called name, with attributes a, and sets its signature (see SignatureExpr).
|
||||
func TEXT(name string, a attr.Attribute, signature string) {
|
||||
ctx.Function(name)
|
||||
ctx.Attributes(a)
|
||||
ctx.SignatureExpr(signature)
|
||||
}
|
||||
|
||||
// GLOBL declares a new static global data section with the given attributes.
|
||||
func GLOBL(name string, a attr.Attribute) operand.Mem {
|
||||
// TODO(mbm): should this be static?
|
||||
g := ctx.StaticGlobal(name)
|
||||
ctx.DataAttributes(a)
|
||||
return g
|
||||
}
|
||||
|
||||
// DATA adds a data value to the active data section.
|
||||
func DATA(offset int, v operand.Constant) {
|
||||
ctx.AddDatum(offset, v)
|
||||
}
|
||||
|
||||
var flags = NewFlags(flag.CommandLine)
|
||||
|
||||
// Generate builds and compiles the avo file built with the global context. This
|
||||
// should be the final line of any avo program. Configuration is determined from command-line flags.
|
||||
func Generate() {
|
||||
if !flag.Parsed() {
|
||||
flag.Parse()
|
||||
}
|
||||
cfg := flags.Config()
|
||||
|
||||
status := Main(cfg, ctx)
|
||||
|
||||
// To record coverage of integration tests we wrap main() functions in a test
|
||||
// functions. In this case we need the main function to terminate, therefore we
|
||||
// only exit for failure status codes.
|
||||
if status != 0 {
|
||||
os.Exit(status)
|
||||
}
|
||||
}
|
||||
|
||||
// Package sets the package the generated file will belong to. Required to be able to reference types in the package.
|
||||
func Package(path string) { ctx.Package(path) }
|
||||
|
||||
// Constraints sets build constraints for the file.
|
||||
func Constraints(t buildtags.ConstraintsConvertable) { ctx.Constraints(t) }
|
||||
|
||||
// Constraint appends a constraint to the file's build constraints.
|
||||
func Constraint(t buildtags.ConstraintConvertable) { ctx.Constraint(t) }
|
||||
|
||||
// ConstraintExpr appends a constraint to the file's build constraints. The
|
||||
// constraint to add is parsed from the given expression. The expression should
|
||||
// look the same as the content following "// +build " in regular build
|
||||
// constraint comments.
|
||||
func ConstraintExpr(expr string) { ctx.ConstraintExpr(expr) }
|
||||
|
||||
// GP8L allocates and returns a general-purpose 8-bit register (low byte).
|
||||
func GP8L() reg.GPVirtual { return ctx.GP8L() }
|
||||
|
||||
// GP8H allocates and returns a general-purpose 8-bit register (high byte).
|
||||
func GP8H() reg.GPVirtual { return ctx.GP8H() }
|
||||
|
||||
// GP8 allocates and returns a general-purpose 8-bit register (low byte).
|
||||
func GP8() reg.GPVirtual { return ctx.GP8() }
|
||||
|
||||
// GP16 allocates and returns a general-purpose 16-bit register.
|
||||
func GP16() reg.GPVirtual { return ctx.GP16() }
|
||||
|
||||
// GP32 allocates and returns a general-purpose 32-bit register.
|
||||
func GP32() reg.GPVirtual { return ctx.GP32() }
|
||||
|
||||
// GP64 allocates and returns a general-purpose 64-bit register.
|
||||
func GP64() reg.GPVirtual { return ctx.GP64() }
|
||||
|
||||
// XMM allocates and returns a 128-bit vector register.
|
||||
func XMM() reg.VecVirtual { return ctx.XMM() }
|
||||
|
||||
// YMM allocates and returns a 256-bit vector register.
|
||||
func YMM() reg.VecVirtual { return ctx.YMM() }
|
||||
|
||||
// ZMM allocates and returns a 512-bit vector register.
|
||||
func ZMM() reg.VecVirtual { return ctx.ZMM() }
|
||||
|
||||
// Param returns a the named argument of the active function.
|
||||
func Param(name string) gotypes.Component { return ctx.Param(name) }
|
||||
|
||||
// ParamIndex returns the ith argument of the active function.
|
||||
func ParamIndex(i int) gotypes.Component { return ctx.ParamIndex(i) }
|
||||
|
||||
// Return returns a the named return value of the active function.
|
||||
func Return(name string) gotypes.Component { return ctx.Return(name) }
|
||||
|
||||
// ReturnIndex returns the ith argument of the active function.
|
||||
func ReturnIndex(i int) gotypes.Component { return ctx.ReturnIndex(i) }
|
||||
|
||||
// Load the function argument src into register dst. Returns the destination
|
||||
// register. This is syntactic sugar: it will attempt to select the right MOV
|
||||
// instruction based on the types involved.
|
||||
func Load(src gotypes.Component, dst reg.Register) reg.Register { return ctx.Load(src, dst) }
|
||||
|
||||
// Store register src into return value dst. This is syntactic sugar: it will
|
||||
// attempt to select the right MOV instruction based on the types involved.
|
||||
func Store(src reg.Register, dst gotypes.Component) { ctx.Store(src, dst) }
|
||||
|
||||
// Dereference loads a pointer and returns its element type.
|
||||
func Dereference(ptr gotypes.Component) gotypes.Component { return ctx.Dereference(ptr) }
|
||||
|
||||
// Doc sets documentation comment lines for the currently active function.
|
||||
func Doc(lines ...string) { ctx.Doc(lines...) }
|
||||
|
||||
// Pragma adds a compiler directive to the currently active function.
|
||||
func Pragma(directive string, args ...string) { ctx.Pragma(directive, args...) }
|
||||
|
||||
// Attributes sets function attributes for the currently active function.
|
||||
func Attributes(a attr.Attribute) { ctx.Attributes(a) }
|
||||
|
||||
// Implement starts building a function of the given name, whose type is
|
||||
// specified by a stub in the containing package.
|
||||
func Implement(name string) { ctx.Implement(name) }
|
||||
|
||||
// AllocLocal allocates size bytes in the stack of the currently active function.
|
||||
// Returns a reference to the base pointer for the newly allocated region.
|
||||
func AllocLocal(size int) operand.Mem { return ctx.AllocLocal(size) }
|
||||
|
||||
// Label adds a label to the active function.
|
||||
func Label(name string) { ctx.Label(name) }
|
||||
|
||||
// Comment adds comment lines to the active function.
|
||||
func Comment(lines ...string) { ctx.Comment(lines...) }
|
||||
|
||||
// Commentf adds a formtted comment line.
|
||||
func Commentf(format string, a ...interface{}) { ctx.Commentf(format, a...) }
|
||||
|
||||
// ConstData builds a static data section containing just the given constant.
|
||||
func ConstData(name string, v operand.Constant) operand.Mem { return ctx.ConstData(name, v) }
|
||||
|
||||
// Instruction adds an instruction to the active function.
|
||||
func Instruction(i *ir.Instruction) { ctx.Instruction(i) }
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"github.com/mmcloughlin/avo/attr"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
|
||||
"github.com/mmcloughlin/avo/gotypes"
|
||||
)
|
||||
|
||||
//go:generate avogen -output zmov.go mov
|
||||
|
||||
// Param returns a the named argument of the active function.
|
||||
func (c *Context) Param(name string) gotypes.Component {
|
||||
return c.activefunc().Signature.Params().Lookup(name)
|
||||
}
|
||||
|
||||
// ParamIndex returns the ith argument of the active function.
|
||||
func (c *Context) ParamIndex(i int) gotypes.Component {
|
||||
return c.activefunc().Signature.Params().At(i)
|
||||
}
|
||||
|
||||
// Return returns a the named return value of the active function.
|
||||
func (c *Context) Return(name string) gotypes.Component {
|
||||
return c.activefunc().Signature.Results().Lookup(name)
|
||||
}
|
||||
|
||||
// ReturnIndex returns the ith argument of the active function.
|
||||
func (c *Context) ReturnIndex(i int) gotypes.Component {
|
||||
return c.activefunc().Signature.Results().At(i)
|
||||
}
|
||||
|
||||
// Load the function argument src into register dst. Returns the destination
|
||||
// register. This is syntactic sugar: it will attempt to select the right MOV
|
||||
// instruction based on the types involved.
|
||||
func (c *Context) Load(src gotypes.Component, dst reg.Register) reg.Register {
|
||||
b, err := src.Resolve()
|
||||
if err != nil {
|
||||
c.adderror(err)
|
||||
return dst
|
||||
}
|
||||
c.mov(b.Addr, dst, int(gotypes.Sizes.Sizeof(b.Type)), int(dst.Size()), b.Type)
|
||||
return dst
|
||||
}
|
||||
|
||||
// Store register src into return value dst. This is syntactic sugar: it will
|
||||
// attempt to select the right MOV instruction based on the types involved.
|
||||
func (c *Context) Store(src reg.Register, dst gotypes.Component) {
|
||||
b, err := dst.Resolve()
|
||||
if err != nil {
|
||||
c.adderror(err)
|
||||
return
|
||||
}
|
||||
c.mov(src, b.Addr, int(src.Size()), int(gotypes.Sizes.Sizeof(b.Type)), b.Type)
|
||||
}
|
||||
|
||||
// Dereference loads a pointer and returns its element type.
|
||||
func (c *Context) Dereference(ptr gotypes.Component) gotypes.Component {
|
||||
r := c.GP64()
|
||||
c.Load(ptr, r)
|
||||
return ptr.Dereference(r)
|
||||
}
|
||||
|
||||
// ConstData builds a static data section containing just the given constant.
|
||||
func (c *Context) ConstData(name string, v operand.Constant) operand.Mem {
|
||||
g := c.StaticGlobal(name)
|
||||
c.DataAttributes(attr.RODATA | attr.NOPTR)
|
||||
c.AppendDatum(v)
|
||||
return g
|
||||
}
|
||||
+26315
File diff suppressed because it is too large
Load Diff
+72
@@ -0,0 +1,72 @@
|
||||
// Code generated by command: avogen -output zmov.go mov. DO NOT EDIT.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
)
|
||||
|
||||
func (c *Context) mov(a, b operand.Op, an, bn int, t *types.Basic) {
|
||||
switch {
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 1 && bn == 1:
|
||||
c.MOVB(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) == 0 && an == 1 && bn == 4:
|
||||
c.MOVBLSX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) != 0 && an == 1 && bn == 4:
|
||||
c.MOVBLZX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) == 0 && an == 1 && bn == 8:
|
||||
c.MOVBQSX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) != 0 && an == 1 && bn == 8:
|
||||
c.MOVBQZX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) == 0 && an == 1 && bn == 2:
|
||||
c.MOVBWSX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) != 0 && an == 1 && bn == 2:
|
||||
c.MOVBWZX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 4 && bn == 4:
|
||||
c.MOVL(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) == 0 && an == 4 && bn == 8:
|
||||
c.MOVLQSX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) != 0 && an == 4 && bn == 8:
|
||||
c.MOVLQZX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 16 && bn == 16:
|
||||
c.MOVOU(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 4 && bn == 16:
|
||||
c.MOVQ(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 8 && bn == 8:
|
||||
c.MOVQ(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 8 && bn == 16:
|
||||
c.MOVQ(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 16 && bn == 4:
|
||||
c.MOVQ(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 16 && bn == 8:
|
||||
c.MOVQ(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 16 && bn == 16:
|
||||
c.MOVQ(a, b)
|
||||
case (t.Info()&types.IsFloat) != 0 && an == 8 && bn == 16:
|
||||
c.MOVSD(a, b)
|
||||
case (t.Info()&types.IsFloat) != 0 && an == 16 && bn == 8:
|
||||
c.MOVSD(a, b)
|
||||
case (t.Info()&types.IsFloat) != 0 && an == 16 && bn == 16:
|
||||
c.MOVSD(a, b)
|
||||
case (t.Info()&types.IsFloat) != 0 && an == 4 && bn == 16:
|
||||
c.MOVSS(a, b)
|
||||
case (t.Info()&types.IsFloat) != 0 && an == 16 && bn == 4:
|
||||
c.MOVSS(a, b)
|
||||
case (t.Info()&types.IsFloat) != 0 && an == 16 && bn == 16:
|
||||
c.MOVSS(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && an == 2 && bn == 2:
|
||||
c.MOVW(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) == 0 && an == 2 && bn == 4:
|
||||
c.MOVWLSX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) != 0 && an == 2 && bn == 4:
|
||||
c.MOVWLZX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) == 0 && an == 2 && bn == 8:
|
||||
c.MOVWQSX(a, b)
|
||||
case (t.Info()&types.IsInteger) != 0 && (t.Info()&types.IsUnsigned) != 0 && an == 2 && bn == 8:
|
||||
c.MOVWQZX(a, b)
|
||||
default:
|
||||
c.adderrormessage("could not deduce mov instruction")
|
||||
}
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
// Package buildtags provides types for representing and manipulating build constraints.
|
||||
//
|
||||
// In Go, build constraints are represented as comments in source code together with file naming conventions. For example
|
||||
//
|
||||
// // +build linux,386 darwin,!cgo
|
||||
// // +build !purego
|
||||
//
|
||||
// Any terms provided in the filename can be thought of as an implicit extra
|
||||
// constraint comment line. Collectively, these are referred to as
|
||||
// ``constraints''. Each line is a ``constraint''. Within each constraint the
|
||||
// space-separated terms are ``options'', and within that the comma-separated
|
||||
// items are ``terms'' which may be negated with at most one exclaimation mark.
|
||||
//
|
||||
// These represent a boolean formulae. The constraints are evaluated as the AND
|
||||
// of constraint lines; a constraint is evaluated as the OR of its options and
|
||||
// an option is evaluated as the AND of its terms. Overall build constraints are
|
||||
// a boolean formula that is an AND of ORs of ANDs.
|
||||
//
|
||||
// This level of complexity is rarely used in Go programs. Therefore this
|
||||
// package aims to provide access to all these layers of nesting if required,
|
||||
// but make it easy to forget about for basic use cases too.
|
||||
package buildtags
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Reference: https://github.com/golang/go/blob/204a8f55dc2e0ac8d27a781dab0da609b98560da/src/go/build/doc.go#L73-L92
|
||||
//
|
||||
// // A build constraint is evaluated as the OR of space-separated options;
|
||||
// // each option evaluates as the AND of its comma-separated terms;
|
||||
// // and each term is an alphanumeric word or, preceded by !, its negation.
|
||||
// // That is, the build constraint:
|
||||
// //
|
||||
// // // +build linux,386 darwin,!cgo
|
||||
// //
|
||||
// // corresponds to the boolean formula:
|
||||
// //
|
||||
// // (linux AND 386) OR (darwin AND (NOT cgo))
|
||||
// //
|
||||
// // A file may have multiple build constraints. The overall constraint is the AND
|
||||
// // of the individual constraints. That is, the build constraints:
|
||||
// //
|
||||
// // // +build linux darwin
|
||||
// // // +build 386
|
||||
// //
|
||||
// // corresponds to the boolean formula:
|
||||
// //
|
||||
// // (linux OR darwin) AND 386
|
||||
//
|
||||
|
||||
// Interface represents a build constraint.
|
||||
type Interface interface {
|
||||
ConstraintsConvertable
|
||||
fmt.GoStringer
|
||||
Evaluate(v map[string]bool) bool
|
||||
Validate() error
|
||||
}
|
||||
|
||||
// ConstraintsConvertable can be converted to a Constraints object.
|
||||
type ConstraintsConvertable interface {
|
||||
ToConstraints() Constraints
|
||||
}
|
||||
|
||||
// ConstraintConvertable can be converted to a Constraint.
|
||||
type ConstraintConvertable interface {
|
||||
ToConstraint() Constraint
|
||||
}
|
||||
|
||||
// OptionConvertable can be converted to an Option.
|
||||
type OptionConvertable interface {
|
||||
ToOption() Option
|
||||
}
|
||||
|
||||
// Constraints represents the AND of a list of Constraint lines.
|
||||
type Constraints []Constraint
|
||||
|
||||
// And builds Constraints that will be true if all of its constraints are true.
|
||||
func And(cs ...ConstraintConvertable) Constraints {
|
||||
constraints := Constraints{}
|
||||
for _, c := range cs {
|
||||
constraints = append(constraints, c.ToConstraint())
|
||||
}
|
||||
return constraints
|
||||
}
|
||||
|
||||
// ToConstraints returns cs.
|
||||
func (cs Constraints) ToConstraints() Constraints { return cs }
|
||||
|
||||
// Validate validates the constraints set.
|
||||
func (cs Constraints) Validate() error {
|
||||
for _, c := range cs {
|
||||
if err := c.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Evaluate the boolean formula represented by cs under the given assignment of
|
||||
// tag values. This is the AND of the values of the constituent Constraints.
|
||||
func (cs Constraints) Evaluate(v map[string]bool) bool {
|
||||
r := true
|
||||
for _, c := range cs {
|
||||
r = r && c.Evaluate(v)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// GoString represents Constraints as +build comment lines.
|
||||
func (cs Constraints) GoString() string {
|
||||
s := ""
|
||||
for _, c := range cs {
|
||||
s += c.GoString()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Constraint represents the OR of a list of Options.
|
||||
type Constraint []Option
|
||||
|
||||
// Any builds a Constraint that will be true if any of its options are true.
|
||||
func Any(opts ...OptionConvertable) Constraint {
|
||||
c := Constraint{}
|
||||
for _, opt := range opts {
|
||||
c = append(c, opt.ToOption())
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// ParseConstraint parses a space-separated list of options.
|
||||
func ParseConstraint(expr string) (Constraint, error) {
|
||||
c := Constraint{}
|
||||
for _, field := range strings.Fields(expr) {
|
||||
opt, err := ParseOption(field)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
c = append(c, opt)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// ToConstraints returns the list of constraints containing just c.
|
||||
func (c Constraint) ToConstraints() Constraints { return Constraints{c} }
|
||||
|
||||
// ToConstraint returns c.
|
||||
func (c Constraint) ToConstraint() Constraint { return c }
|
||||
|
||||
// Validate validates the constraint.
|
||||
func (c Constraint) Validate() error {
|
||||
for _, o := range c {
|
||||
if err := o.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Evaluate the boolean formula represented by c under the given assignment of
|
||||
// tag values. This is the OR of the values of the constituent Options.
|
||||
func (c Constraint) Evaluate(v map[string]bool) bool {
|
||||
r := false
|
||||
for _, o := range c {
|
||||
r = r || o.Evaluate(v)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// GoString represents the Constraint as one +build comment line.
|
||||
func (c Constraint) GoString() string {
|
||||
s := "// +build"
|
||||
for _, o := range c {
|
||||
s += " " + o.GoString()
|
||||
}
|
||||
return s + "\n"
|
||||
}
|
||||
|
||||
// Option represents the AND of a list of Terms.
|
||||
type Option []Term
|
||||
|
||||
// Opt builds an Option from the list of Terms.
|
||||
func Opt(terms ...Term) Option {
|
||||
return Option(terms)
|
||||
}
|
||||
|
||||
// ParseOption parses a comma-separated list of terms.
|
||||
func ParseOption(expr string) (Option, error) {
|
||||
opt := Option{}
|
||||
for _, t := range strings.Split(expr, ",") {
|
||||
opt = append(opt, Term(t))
|
||||
}
|
||||
return opt, opt.Validate()
|
||||
}
|
||||
|
||||
// ToConstraints returns Constraints containing just this option.
|
||||
func (o Option) ToConstraints() Constraints { return o.ToConstraint().ToConstraints() }
|
||||
|
||||
// ToConstraint returns a Constraint containing just this option.
|
||||
func (o Option) ToConstraint() Constraint { return Constraint{o} }
|
||||
|
||||
// ToOption returns o.
|
||||
func (o Option) ToOption() Option { return o }
|
||||
|
||||
// Validate validates o.
|
||||
func (o Option) Validate() error {
|
||||
for _, t := range o {
|
||||
if err := t.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid term \"%s\": %s", t, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Evaluate the boolean formula represented by o under the given assignment of
|
||||
// tag values. This is the AND of the values of the constituent Terms.
|
||||
func (o Option) Evaluate(v map[string]bool) bool {
|
||||
r := true
|
||||
for _, t := range o {
|
||||
r = r && t.Evaluate(v)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// GoString represents the Option as a comma-separated list of terms.
|
||||
func (o Option) GoString() string {
|
||||
var ts []string
|
||||
for _, t := range o {
|
||||
ts = append(ts, t.GoString())
|
||||
}
|
||||
return strings.Join(ts, ",")
|
||||
}
|
||||
|
||||
// Term is an atomic term in a build constraint: an identifier or its negation.
|
||||
type Term string
|
||||
|
||||
// Not returns a term for the negation of ident.
|
||||
func Not(ident string) Term {
|
||||
return Term("!" + ident)
|
||||
}
|
||||
|
||||
// ToConstraints returns Constraints containing just this term.
|
||||
func (t Term) ToConstraints() Constraints { return t.ToOption().ToConstraints() }
|
||||
|
||||
// ToConstraint returns a Constraint containing just this term.
|
||||
func (t Term) ToConstraint() Constraint { return t.ToOption().ToConstraint() }
|
||||
|
||||
// ToOption returns an Option containing just this term.
|
||||
func (t Term) ToOption() Option { return Option{t} }
|
||||
|
||||
// IsNegated reports whether t is the negation of an identifier.
|
||||
func (t Term) IsNegated() bool { return strings.HasPrefix(string(t), "!") }
|
||||
|
||||
// Name returns the identifier for this term.
|
||||
func (t Term) Name() string {
|
||||
return strings.TrimPrefix(string(t), "!")
|
||||
}
|
||||
|
||||
// Validate the term.
|
||||
func (t Term) Validate() error {
|
||||
// Reference: https://github.com/golang/go/blob/204a8f55dc2e0ac8d27a781dab0da609b98560da/src/cmd/go/internal/imports/build.go#L110-L112
|
||||
//
|
||||
// if strings.HasPrefix(name, "!!") { // bad syntax, reject always
|
||||
// return false
|
||||
// }
|
||||
//
|
||||
if strings.HasPrefix(string(t), "!!") {
|
||||
return errors.New("at most one '!' allowed")
|
||||
}
|
||||
|
||||
if len(t.Name()) == 0 {
|
||||
return errors.New("empty tag name")
|
||||
}
|
||||
|
||||
// Reference: https://github.com/golang/go/blob/204a8f55dc2e0ac8d27a781dab0da609b98560da/src/cmd/go/internal/imports/build.go#L121-L127
|
||||
//
|
||||
// // Tags must be letters, digits, underscores or dots.
|
||||
// // Unlike in Go identifiers, all digits are fine (e.g., "386").
|
||||
// for _, c := range name {
|
||||
// if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
for _, c := range t.Name() {
|
||||
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
|
||||
return fmt.Errorf("character '%c' disallowed in tags", c)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Evaluate the term under the given set of identifier values.
|
||||
func (t Term) Evaluate(v map[string]bool) bool {
|
||||
return (t.Validate() == nil) && (v[t.Name()] == !t.IsNegated())
|
||||
}
|
||||
|
||||
// GoString returns t.
|
||||
func (t Term) GoString() string { return string(t) }
|
||||
|
||||
// SetTags builds a set where the given list of identifiers are true.
|
||||
func SetTags(idents ...string) map[string]bool {
|
||||
v := map[string]bool{}
|
||||
for _, ident := range idents {
|
||||
v[ident] = true
|
||||
}
|
||||
return v
|
||||
}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
package gotypes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strconv"
|
||||
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
)
|
||||
|
||||
// Sizes provides type sizes used by the standard Go compiler on amd64.
|
||||
var Sizes = types.SizesFor("gc", "amd64")
|
||||
|
||||
// Basic represents a primitive/basic type at a given memory address.
|
||||
type Basic struct {
|
||||
Addr operand.Mem
|
||||
Type *types.Basic
|
||||
}
|
||||
|
||||
// Component provides access to sub-components of a Go type.
|
||||
type Component interface {
|
||||
// When the component has no further sub-components, Resolve will return a
|
||||
// reference to the components type and memory address. If there was an error
|
||||
// during any previous calls to Component methods, they will be returned at
|
||||
// resolution time.
|
||||
Resolve() (*Basic, error)
|
||||
|
||||
Dereference(r reg.Register) Component // dereference a pointer
|
||||
Base() Component // base pointer of a string or slice
|
||||
Len() Component // length of a string or slice
|
||||
Cap() Component // capacity of a slice
|
||||
Real() Component // real part of a complex value
|
||||
Imag() Component // imaginary part of a complex value
|
||||
Index(int) Component // index into an array
|
||||
Field(string) Component // access a struct field
|
||||
}
|
||||
|
||||
// componenterr is an error that also provides a null implementation of the
|
||||
// Component interface. This enables us to return an error from Component
|
||||
// methods whilst also allowing method chaining to continue.
|
||||
type componenterr string
|
||||
|
||||
func errorf(format string, args ...interface{}) Component {
|
||||
return componenterr(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (c componenterr) Error() string { return string(c) }
|
||||
func (c componenterr) Resolve() (*Basic, error) { return nil, c }
|
||||
func (c componenterr) Dereference(r reg.Register) Component { return c }
|
||||
func (c componenterr) Base() Component { return c }
|
||||
func (c componenterr) Len() Component { return c }
|
||||
func (c componenterr) Cap() Component { return c }
|
||||
func (c componenterr) Real() Component { return c }
|
||||
func (c componenterr) Imag() Component { return c }
|
||||
func (c componenterr) Index(int) Component { return c }
|
||||
func (c componenterr) Field(string) Component { return c }
|
||||
|
||||
type component struct {
|
||||
typ types.Type
|
||||
addr operand.Mem
|
||||
}
|
||||
|
||||
// NewComponent builds a component for the named type at the given address.
|
||||
func NewComponent(t types.Type, addr operand.Mem) Component {
|
||||
return &component{
|
||||
typ: t,
|
||||
addr: addr,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *component) Resolve() (*Basic, error) {
|
||||
b := toprimitive(c.typ)
|
||||
if b == nil {
|
||||
return nil, errors.New("component is not primitive")
|
||||
}
|
||||
return &Basic{
|
||||
Addr: c.addr,
|
||||
Type: b,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *component) Dereference(r reg.Register) Component {
|
||||
p, ok := c.typ.Underlying().(*types.Pointer)
|
||||
if !ok {
|
||||
return errorf("not pointer type")
|
||||
}
|
||||
return NewComponent(p.Elem(), operand.Mem{Base: r})
|
||||
}
|
||||
|
||||
// Reference: https://github.com/golang/go/blob/50bd1c4d4eb4fac8ddeb5f063c099daccfb71b26/src/reflect/value.go#L1800-L1804
|
||||
//
|
||||
// type SliceHeader struct {
|
||||
// Data uintptr
|
||||
// Len int
|
||||
// Cap int
|
||||
// }
|
||||
//
|
||||
var slicehdroffsets = Sizes.Offsetsof([]*types.Var{
|
||||
types.NewField(token.NoPos, nil, "Data", types.Typ[types.Uintptr], false),
|
||||
types.NewField(token.NoPos, nil, "Len", types.Typ[types.Int], false),
|
||||
types.NewField(token.NoPos, nil, "Cap", types.Typ[types.Int], false),
|
||||
})
|
||||
|
||||
func (c *component) Base() Component {
|
||||
if !isslice(c.typ) && !isstring(c.typ) {
|
||||
return errorf("only slices and strings have base pointers")
|
||||
}
|
||||
return c.sub("_base", int(slicehdroffsets[0]), types.Typ[types.Uintptr])
|
||||
}
|
||||
|
||||
func (c *component) Len() Component {
|
||||
if !isslice(c.typ) && !isstring(c.typ) {
|
||||
return errorf("only slices and strings have length fields")
|
||||
}
|
||||
return c.sub("_len", int(slicehdroffsets[1]), types.Typ[types.Int])
|
||||
}
|
||||
|
||||
func (c *component) Cap() Component {
|
||||
if !isslice(c.typ) {
|
||||
return errorf("only slices have capacity fields")
|
||||
}
|
||||
return c.sub("_cap", int(slicehdroffsets[2]), types.Typ[types.Int])
|
||||
}
|
||||
|
||||
func (c *component) Real() Component {
|
||||
if !iscomplex(c.typ) {
|
||||
return errorf("only complex types have real values")
|
||||
}
|
||||
f := complextofloat(c.typ)
|
||||
return c.sub("_real", 0, f)
|
||||
}
|
||||
|
||||
func (c *component) Imag() Component {
|
||||
if !iscomplex(c.typ) {
|
||||
return errorf("only complex types have imaginary values")
|
||||
}
|
||||
f := complextofloat(c.typ)
|
||||
return c.sub("_imag", int(Sizes.Sizeof(f)), f)
|
||||
}
|
||||
|
||||
func (c *component) Index(i int) Component {
|
||||
a, ok := c.typ.Underlying().(*types.Array)
|
||||
if !ok {
|
||||
return errorf("not array type")
|
||||
}
|
||||
if int64(i) >= a.Len() {
|
||||
return errorf("array index out of bounds")
|
||||
}
|
||||
// Reference: https://github.com/golang/tools/blob/bcd4e47d02889ebbc25c9f4bf3d27e4124b0bf9d/go/analysis/passes/asmdecl/asmdecl.go#L482-L494
|
||||
//
|
||||
// case asmArray:
|
||||
// tu := t.Underlying().(*types.Array)
|
||||
// elem := tu.Elem()
|
||||
// // Calculate offset of each element array.
|
||||
// fields := []*types.Var{
|
||||
// types.NewVar(token.NoPos, nil, "fake0", elem),
|
||||
// types.NewVar(token.NoPos, nil, "fake1", elem),
|
||||
// }
|
||||
// offsets := arch.sizes.Offsetsof(fields)
|
||||
// elemoff := int(offsets[1])
|
||||
// for i := 0; i < int(tu.Len()); i++ {
|
||||
// cc = appendComponentsRecursive(arch, elem, cc, suffix+"_"+strconv.Itoa(i), i*elemoff)
|
||||
// }
|
||||
//
|
||||
elem := a.Elem()
|
||||
elemsize := int(Sizes.Sizeof(types.NewArray(elem, 2)) - Sizes.Sizeof(types.NewArray(elem, 1)))
|
||||
return c.sub("_"+strconv.Itoa(i), i*elemsize, elem)
|
||||
}
|
||||
|
||||
func (c *component) Field(n string) Component {
|
||||
s, ok := c.typ.Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return errorf("not struct type")
|
||||
}
|
||||
// Reference: https://github.com/golang/tools/blob/13ba8ad772dfbf0f451b5dd0679e9c5605afc05d/go/analysis/passes/asmdecl/asmdecl.go#L471-L480
|
||||
//
|
||||
// case asmStruct:
|
||||
// tu := t.Underlying().(*types.Struct)
|
||||
// fields := make([]*types.Var, tu.NumFields())
|
||||
// for i := 0; i < tu.NumFields(); i++ {
|
||||
// fields[i] = tu.Field(i)
|
||||
// }
|
||||
// offsets := arch.sizes.Offsetsof(fields)
|
||||
// for i, f := range fields {
|
||||
// cc = appendComponentsRecursive(arch, f.Type(), cc, suffix+"_"+f.Name(), off+int(offsets[i]))
|
||||
// }
|
||||
//
|
||||
fields := make([]*types.Var, s.NumFields())
|
||||
for i := 0; i < s.NumFields(); i++ {
|
||||
fields[i] = s.Field(i)
|
||||
}
|
||||
offsets := Sizes.Offsetsof(fields)
|
||||
for i, f := range fields {
|
||||
if f.Name() == n {
|
||||
return c.sub("_"+n, int(offsets[i]), f.Type())
|
||||
}
|
||||
}
|
||||
return errorf("struct does not have field '%s'", n)
|
||||
}
|
||||
|
||||
func (c *component) sub(suffix string, offset int, t types.Type) *component {
|
||||
s := *c
|
||||
if s.addr.Symbol.Name != "" {
|
||||
s.addr.Symbol.Name += suffix
|
||||
}
|
||||
s.addr = s.addr.Offset(offset)
|
||||
s.typ = t
|
||||
return &s
|
||||
}
|
||||
|
||||
func isslice(t types.Type) bool {
|
||||
_, ok := t.Underlying().(*types.Slice)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isstring(t types.Type) bool {
|
||||
b, ok := t.Underlying().(*types.Basic)
|
||||
return ok && b.Kind() == types.String
|
||||
}
|
||||
|
||||
func iscomplex(t types.Type) bool {
|
||||
b, ok := t.Underlying().(*types.Basic)
|
||||
return ok && (b.Info()&types.IsComplex) != 0
|
||||
}
|
||||
|
||||
func complextofloat(t types.Type) types.Type {
|
||||
switch Sizes.Sizeof(t) {
|
||||
case 16:
|
||||
return types.Typ[types.Float64]
|
||||
case 8:
|
||||
return types.Typ[types.Float32]
|
||||
}
|
||||
panic("bad")
|
||||
}
|
||||
|
||||
// toprimitive determines whether t is primitive (cannot be reduced into
|
||||
// components). If it is, it returns the basic type for t, otherwise returns
|
||||
// nil.
|
||||
func toprimitive(t types.Type) *types.Basic {
|
||||
switch b := t.(type) {
|
||||
case *types.Basic:
|
||||
if (b.Info() & (types.IsString | types.IsComplex)) == 0 {
|
||||
return b
|
||||
}
|
||||
case *types.Pointer:
|
||||
return types.Typ[types.Uintptr]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package gotypes provides helpers for interacting with Go types within avo functions.
|
||||
package gotypes
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
package gotypes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strconv"
|
||||
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
)
|
||||
|
||||
// Signature represents a Go function signature.
|
||||
type Signature struct {
|
||||
pkg *types.Package
|
||||
sig *types.Signature
|
||||
params *Tuple
|
||||
results *Tuple
|
||||
}
|
||||
|
||||
// NewSignature constructs a Signature.
|
||||
func NewSignature(pkg *types.Package, sig *types.Signature) *Signature {
|
||||
s := &Signature{
|
||||
pkg: pkg,
|
||||
sig: sig,
|
||||
}
|
||||
s.init()
|
||||
return s
|
||||
}
|
||||
|
||||
// NewSignatureVoid builds the void signature "func()".
|
||||
func NewSignatureVoid() *Signature {
|
||||
return NewSignature(nil, types.NewSignature(nil, nil, nil, false))
|
||||
}
|
||||
|
||||
// LookupSignature returns the signature of the named function in the provided package.
|
||||
func LookupSignature(pkg *types.Package, name string) (*Signature, error) {
|
||||
scope := pkg.Scope()
|
||||
obj := scope.Lookup(name)
|
||||
if obj == nil {
|
||||
return nil, fmt.Errorf("could not find function \"%s\"", name)
|
||||
}
|
||||
s, ok := obj.Type().(*types.Signature)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("object \"%s\" does not have signature type", name)
|
||||
}
|
||||
return NewSignature(pkg, s), nil
|
||||
}
|
||||
|
||||
// ParseSignature builds a Signature by parsing a Go function type expression.
|
||||
// The function type must reference builtin types only; see
|
||||
// ParseSignatureInPackage if custom types are required.
|
||||
func ParseSignature(expr string) (*Signature, error) {
|
||||
return ParseSignatureInPackage(nil, expr)
|
||||
}
|
||||
|
||||
// ParseSignatureInPackage builds a Signature by parsing a Go function type
|
||||
// expression. The expression may reference types in the provided package.
|
||||
func ParseSignatureInPackage(pkg *types.Package, expr string) (*Signature, error) {
|
||||
tv, err := types.Eval(token.NewFileSet(), pkg, token.NoPos, expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tv.Value != nil {
|
||||
return nil, errors.New("signature expression should have nil value")
|
||||
}
|
||||
s, ok := tv.Type.(*types.Signature)
|
||||
if !ok {
|
||||
return nil, errors.New("provided type is not a function signature")
|
||||
}
|
||||
return NewSignature(pkg, s), nil
|
||||
}
|
||||
|
||||
// Params returns the function signature argument types.
|
||||
func (s *Signature) Params() *Tuple { return s.params }
|
||||
|
||||
// Results returns the function return types.
|
||||
func (s *Signature) Results() *Tuple { return s.results }
|
||||
|
||||
// Bytes returns the total size of the function arguments and return values.
|
||||
func (s *Signature) Bytes() int { return s.Params().Bytes() + s.Results().Bytes() }
|
||||
|
||||
// String writes Signature as a string. This does not include the "func" keyword.
|
||||
func (s *Signature) String() string {
|
||||
var buf bytes.Buffer
|
||||
types.WriteSignature(&buf, s.sig, func(pkg *types.Package) string {
|
||||
if pkg == s.pkg {
|
||||
return ""
|
||||
}
|
||||
return pkg.Name()
|
||||
})
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (s *Signature) init() {
|
||||
p := s.sig.Params()
|
||||
r := s.sig.Results()
|
||||
|
||||
// Compute parameter offsets.
|
||||
vs := tuplevars(p)
|
||||
vs = append(vs, types.NewParam(token.NoPos, nil, "sentinel", types.Typ[types.Uint64]))
|
||||
paramsoffsets := Sizes.Offsetsof(vs)
|
||||
paramssize := paramsoffsets[p.Len()]
|
||||
s.params = newTuple(p, paramsoffsets, paramssize, "arg")
|
||||
|
||||
// Result offsets.
|
||||
vs = tuplevars(r)
|
||||
resultsoffsets := Sizes.Offsetsof(vs)
|
||||
var resultssize int64
|
||||
if n := len(vs); n > 0 {
|
||||
resultssize = resultsoffsets[n-1] + Sizes.Sizeof(vs[n-1].Type())
|
||||
}
|
||||
for i := range resultsoffsets {
|
||||
resultsoffsets[i] += paramssize
|
||||
}
|
||||
s.results = newTuple(r, resultsoffsets, resultssize, "ret")
|
||||
}
|
||||
|
||||
// Tuple represents a tuple of variables, such as function arguments or results.
|
||||
type Tuple struct {
|
||||
components []Component
|
||||
byname map[string]Component
|
||||
size int
|
||||
}
|
||||
|
||||
func newTuple(t *types.Tuple, offsets []int64, size int64, defaultprefix string) *Tuple {
|
||||
tuple := &Tuple{
|
||||
byname: map[string]Component{},
|
||||
size: int(size),
|
||||
}
|
||||
for i := 0; i < t.Len(); i++ {
|
||||
v := t.At(i)
|
||||
name := v.Name()
|
||||
if name == "" {
|
||||
name = defaultprefix
|
||||
if i > 0 {
|
||||
name += strconv.Itoa(i)
|
||||
}
|
||||
}
|
||||
addr := operand.NewParamAddr(name, int(offsets[i]))
|
||||
c := NewComponent(v.Type(), addr)
|
||||
tuple.components = append(tuple.components, c)
|
||||
if v.Name() != "" {
|
||||
tuple.byname[v.Name()] = c
|
||||
}
|
||||
}
|
||||
return tuple
|
||||
}
|
||||
|
||||
// Lookup returns the variable with the given name.
|
||||
func (t *Tuple) Lookup(name string) Component {
|
||||
e := t.byname[name]
|
||||
if e == nil {
|
||||
return errorf("unknown variable \"%s\"", name)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// At returns the variable at index i.
|
||||
func (t *Tuple) At(i int) Component {
|
||||
if i >= len(t.components) {
|
||||
return errorf("index out of range")
|
||||
}
|
||||
return t.components[i]
|
||||
}
|
||||
|
||||
// Bytes returns the size of the Tuple. This may include additional padding.
|
||||
func (t *Tuple) Bytes() int { return t.size }
|
||||
|
||||
func tuplevars(t *types.Tuple) []*types.Var {
|
||||
vs := make([]*types.Var, t.Len())
|
||||
for i := 0; i < t.Len(); i++ {
|
||||
vs[i] = t.At(i)
|
||||
}
|
||||
return vs
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// Package prnt provides common functionality for code generators.
|
||||
package prnt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Generator provides convenience methods for code generators. In particular it
|
||||
// provides fmt-like methods which print to an internal buffer. It also allows
|
||||
// any errors to be stored so they can be checked at the end, rather than having
|
||||
// error checks obscuring the code generation.
|
||||
type Generator struct {
|
||||
buf bytes.Buffer
|
||||
err error
|
||||
}
|
||||
|
||||
// Raw provides direct access to the underlying output stream.
|
||||
func (g *Generator) Raw() io.Writer {
|
||||
return &g.buf
|
||||
}
|
||||
|
||||
// Printf prints to the internal buffer.
|
||||
func (g *Generator) Printf(format string, args ...interface{}) {
|
||||
if g.err != nil {
|
||||
return
|
||||
}
|
||||
_, err := fmt.Fprintf(&g.buf, format, args...)
|
||||
g.AddError(err)
|
||||
}
|
||||
|
||||
// NL prints a new line.
|
||||
func (g *Generator) NL() {
|
||||
g.Printf("\n")
|
||||
}
|
||||
|
||||
// Comment writes comment lines prefixed with "// ".
|
||||
func (g *Generator) Comment(lines ...string) {
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace("// " + line)
|
||||
g.Printf("%s\n", line)
|
||||
}
|
||||
}
|
||||
|
||||
// AddError records an error in code generation. The first non-nil error will
|
||||
// prevent printing operations from writing anything else, and the error will be
|
||||
// returned from Result().
|
||||
func (g *Generator) AddError(err error) {
|
||||
if err != nil && g.err == nil {
|
||||
g.err = err
|
||||
}
|
||||
}
|
||||
|
||||
// Result returns the printed bytes. If any error was recorded with AddError
|
||||
// during code generation, the first such error will be returned here.
|
||||
func (g *Generator) Result() ([]byte, error) {
|
||||
return g.buf.Bytes(), g.err
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
// Package stack provides helpers for querying the callstack.
|
||||
package stack
|
||||
|
||||
import (
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frames returns at most max callstack Frames, starting with its caller and
|
||||
// skipping skip Frames.
|
||||
func Frames(skip, max int) []runtime.Frame {
|
||||
pc := make([]uintptr, max)
|
||||
n := runtime.Callers(skip+2, pc)
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
pc = pc[:n]
|
||||
frames := runtime.CallersFrames(pc)
|
||||
var fs []runtime.Frame
|
||||
for {
|
||||
f, more := frames.Next()
|
||||
fs = append(fs, f)
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
// Match returns the first stack frame for which the predicate function returns
|
||||
// true. Returns nil if no match is found. Starts matching after skip frames,
|
||||
// starting with its caller.
|
||||
func Match(skip int, predicate func(runtime.Frame) bool) *runtime.Frame {
|
||||
i, n := skip+1, 16
|
||||
for {
|
||||
fs := Frames(i, n)
|
||||
for j, f := range fs {
|
||||
if predicate(f) {
|
||||
return &fs[j]
|
||||
}
|
||||
}
|
||||
if len(fs) < n {
|
||||
break
|
||||
}
|
||||
i += n
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Main returns the main() function Frame.
|
||||
func Main() *runtime.Frame {
|
||||
return Match(1, func(f runtime.Frame) bool {
|
||||
return f.Function == "main.main"
|
||||
})
|
||||
}
|
||||
|
||||
// ExternalCaller returns the first frame outside the callers package.
|
||||
func ExternalCaller() *runtime.Frame {
|
||||
var first *runtime.Frame
|
||||
return Match(1, func(f runtime.Frame) bool {
|
||||
if first == nil {
|
||||
first = &f
|
||||
}
|
||||
return pkg(first.Function) != pkg(f.Function)
|
||||
})
|
||||
}
|
||||
|
||||
func pkg(ident string) string {
|
||||
dir, name := path.Split(ident)
|
||||
parts := strings.Split(name, ".")
|
||||
return dir + parts[0]
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package ir provides the intermediate representation of avo programs.
|
||||
package ir
|
||||
+355
@@ -0,0 +1,355 @@
|
||||
package ir
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/mmcloughlin/avo/attr"
|
||||
"github.com/mmcloughlin/avo/buildtags"
|
||||
"github.com/mmcloughlin/avo/gotypes"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
// Node is a part of a Function.
|
||||
type Node interface {
|
||||
node()
|
||||
}
|
||||
|
||||
// Label within a function.
|
||||
type Label string
|
||||
|
||||
func (l Label) node() {}
|
||||
|
||||
// Comment represents a multi-line comment.
|
||||
type Comment struct {
|
||||
Lines []string
|
||||
}
|
||||
|
||||
func (c *Comment) node() {}
|
||||
|
||||
// NewComment builds a Comment consisting of the provided lines.
|
||||
func NewComment(lines ...string) *Comment {
|
||||
return &Comment{
|
||||
Lines: lines,
|
||||
}
|
||||
}
|
||||
|
||||
// Instruction is a single instruction in a function.
|
||||
type Instruction struct {
|
||||
Opcode string
|
||||
Operands []operand.Op
|
||||
|
||||
Inputs []operand.Op
|
||||
Outputs []operand.Op
|
||||
|
||||
IsTerminal bool
|
||||
IsBranch bool
|
||||
IsConditional bool
|
||||
CancellingInputs bool
|
||||
|
||||
// ISA is the list of required instruction set extensions.
|
||||
ISA []string
|
||||
|
||||
// CFG.
|
||||
Pred []*Instruction
|
||||
Succ []*Instruction
|
||||
|
||||
// LiveIn/LiveOut are sets of live register IDs pre/post execution.
|
||||
LiveIn reg.MaskSet
|
||||
LiveOut reg.MaskSet
|
||||
}
|
||||
|
||||
func (i *Instruction) node() {}
|
||||
|
||||
// IsUnconditionalBranch reports whether i is an unconditional branch.
|
||||
func (i Instruction) IsUnconditionalBranch() bool {
|
||||
return i.IsBranch && !i.IsConditional
|
||||
}
|
||||
|
||||
// TargetLabel returns the label referenced by this instruction. Returns nil if
|
||||
// no label is referenced.
|
||||
func (i Instruction) TargetLabel() *Label {
|
||||
if !i.IsBranch {
|
||||
return nil
|
||||
}
|
||||
if len(i.Operands) == 0 {
|
||||
return nil
|
||||
}
|
||||
if ref, ok := i.Operands[0].(operand.LabelRef); ok {
|
||||
lbl := Label(ref)
|
||||
return &lbl
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Registers returns all registers involved in the instruction.
|
||||
func (i Instruction) Registers() []reg.Register {
|
||||
var rs []reg.Register
|
||||
for _, op := range i.Operands {
|
||||
rs = append(rs, operand.Registers(op)...)
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
// InputRegisters returns all registers read by this instruction.
|
||||
func (i Instruction) InputRegisters() []reg.Register {
|
||||
var rs []reg.Register
|
||||
for _, op := range i.Inputs {
|
||||
rs = append(rs, operand.Registers(op)...)
|
||||
}
|
||||
if i.CancellingInputs && rs[0] == rs[1] {
|
||||
rs = []reg.Register{}
|
||||
}
|
||||
for _, op := range i.Outputs {
|
||||
if operand.IsMem(op) {
|
||||
rs = append(rs, operand.Registers(op)...)
|
||||
}
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
// OutputRegisters returns all registers written by this instruction.
|
||||
func (i Instruction) OutputRegisters() []reg.Register {
|
||||
var rs []reg.Register
|
||||
for _, op := range i.Outputs {
|
||||
if r, ok := op.(reg.Register); ok {
|
||||
rs = append(rs, r)
|
||||
}
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
// Section is a part of a file.
|
||||
type Section interface {
|
||||
section()
|
||||
}
|
||||
|
||||
// File represents an assembly file.
|
||||
type File struct {
|
||||
Constraints buildtags.Constraints
|
||||
Includes []string
|
||||
Sections []Section
|
||||
}
|
||||
|
||||
// NewFile initializes an empty file.
|
||||
func NewFile() *File {
|
||||
return &File{}
|
||||
}
|
||||
|
||||
// AddSection appends a Section to the file.
|
||||
func (f *File) AddSection(s Section) {
|
||||
f.Sections = append(f.Sections, s)
|
||||
}
|
||||
|
||||
// Functions returns all functions in the file.
|
||||
func (f *File) Functions() []*Function {
|
||||
var fns []*Function
|
||||
for _, s := range f.Sections {
|
||||
if fn, ok := s.(*Function); ok {
|
||||
fns = append(fns, fn)
|
||||
}
|
||||
}
|
||||
return fns
|
||||
}
|
||||
|
||||
// Pragma represents a function compiler directive.
|
||||
type Pragma struct {
|
||||
Directive string
|
||||
Arguments []string
|
||||
}
|
||||
|
||||
// Function represents an assembly function.
|
||||
type Function struct {
|
||||
Name string
|
||||
Attributes attr.Attribute
|
||||
Pragmas []Pragma
|
||||
Doc []string
|
||||
Signature *gotypes.Signature
|
||||
LocalSize int
|
||||
|
||||
Nodes []Node
|
||||
|
||||
// LabelTarget maps from label name to the following instruction.
|
||||
LabelTarget map[Label]*Instruction
|
||||
|
||||
// Register allocation.
|
||||
Allocation reg.Allocation
|
||||
|
||||
// ISA is the list of required instruction set extensions.
|
||||
ISA []string
|
||||
}
|
||||
|
||||
func (f *Function) section() {}
|
||||
|
||||
// NewFunction builds an empty function of the given name.
|
||||
func NewFunction(name string) *Function {
|
||||
return &Function{
|
||||
Name: name,
|
||||
Signature: gotypes.NewSignatureVoid(),
|
||||
}
|
||||
}
|
||||
|
||||
// AddPragma adds a pragma to this function.
|
||||
func (f *Function) AddPragma(directive string, args ...string) {
|
||||
f.Pragmas = append(f.Pragmas, Pragma{
|
||||
Directive: directive,
|
||||
Arguments: args,
|
||||
})
|
||||
}
|
||||
|
||||
// SetSignature sets the function signature.
|
||||
func (f *Function) SetSignature(s *gotypes.Signature) {
|
||||
f.Signature = s
|
||||
}
|
||||
|
||||
// AllocLocal allocates size bytes in this function's stack.
|
||||
// Returns a reference to the base pointer for the newly allocated region.
|
||||
func (f *Function) AllocLocal(size int) operand.Mem {
|
||||
ptr := operand.NewStackAddr(f.LocalSize)
|
||||
f.LocalSize += size
|
||||
return ptr
|
||||
}
|
||||
|
||||
// AddInstruction appends an instruction to f.
|
||||
func (f *Function) AddInstruction(i *Instruction) {
|
||||
f.AddNode(i)
|
||||
}
|
||||
|
||||
// AddLabel appends a label to f.
|
||||
func (f *Function) AddLabel(l Label) {
|
||||
f.AddNode(l)
|
||||
}
|
||||
|
||||
// AddComment adds comment lines to f.
|
||||
func (f *Function) AddComment(lines ...string) {
|
||||
f.AddNode(NewComment(lines...))
|
||||
}
|
||||
|
||||
// AddNode appends a Node to f.
|
||||
func (f *Function) AddNode(n Node) {
|
||||
f.Nodes = append(f.Nodes, n)
|
||||
}
|
||||
|
||||
// Instructions returns just the list of instruction nodes.
|
||||
func (f *Function) Instructions() []*Instruction {
|
||||
var is []*Instruction
|
||||
for _, n := range f.Nodes {
|
||||
i, ok := n.(*Instruction)
|
||||
if ok {
|
||||
is = append(is, i)
|
||||
}
|
||||
}
|
||||
return is
|
||||
}
|
||||
|
||||
// Labels returns just the list of label nodes.
|
||||
func (f *Function) Labels() []Label {
|
||||
var lbls []Label
|
||||
for _, n := range f.Nodes {
|
||||
lbl, ok := n.(Label)
|
||||
if ok {
|
||||
lbls = append(lbls, lbl)
|
||||
}
|
||||
}
|
||||
return lbls
|
||||
}
|
||||
|
||||
// Stub returns the Go function declaration.
|
||||
func (f *Function) Stub() string {
|
||||
return "func " + f.Name + f.Signature.String()
|
||||
}
|
||||
|
||||
// FrameBytes returns the size of the stack frame in bytes.
|
||||
func (f *Function) FrameBytes() int {
|
||||
return f.LocalSize
|
||||
}
|
||||
|
||||
// ArgumentBytes returns the size of the arguments in bytes.
|
||||
func (f *Function) ArgumentBytes() int {
|
||||
return f.Signature.Bytes()
|
||||
}
|
||||
|
||||
// Datum represents a data element at a particular offset of a data section.
|
||||
type Datum struct {
|
||||
Offset int
|
||||
Value operand.Constant
|
||||
}
|
||||
|
||||
// NewDatum builds a Datum from the given constant.
|
||||
func NewDatum(offset int, v operand.Constant) Datum {
|
||||
return Datum{
|
||||
Offset: offset,
|
||||
Value: v,
|
||||
}
|
||||
}
|
||||
|
||||
// Interval returns the range of bytes this datum will occupy within its section.
|
||||
func (d Datum) Interval() (int, int) {
|
||||
return d.Offset, d.Offset + d.Value.Bytes()
|
||||
}
|
||||
|
||||
// Overlaps returns true
|
||||
func (d Datum) Overlaps(other Datum) bool {
|
||||
s, e := d.Interval()
|
||||
so, eo := other.Interval()
|
||||
return !(eo <= s || e <= so)
|
||||
}
|
||||
|
||||
// Global represents a DATA section.
|
||||
type Global struct {
|
||||
Symbol operand.Symbol
|
||||
Attributes attr.Attribute
|
||||
Data []Datum
|
||||
Size int
|
||||
}
|
||||
|
||||
// NewGlobal constructs an empty DATA section.
|
||||
func NewGlobal(sym operand.Symbol) *Global {
|
||||
return &Global{
|
||||
Symbol: sym,
|
||||
}
|
||||
}
|
||||
|
||||
// NewStaticGlobal is a convenience for building a static DATA section.
|
||||
func NewStaticGlobal(name string) *Global {
|
||||
return NewGlobal(operand.NewStaticSymbol(name))
|
||||
}
|
||||
|
||||
func (g *Global) section() {}
|
||||
|
||||
// Base returns a pointer to the start of the data section.
|
||||
func (g *Global) Base() operand.Mem {
|
||||
return operand.NewDataAddr(g.Symbol, 0)
|
||||
}
|
||||
|
||||
// Grow ensures that the data section has at least the given size.
|
||||
func (g *Global) Grow(size int) {
|
||||
if g.Size < size {
|
||||
g.Size = size
|
||||
}
|
||||
}
|
||||
|
||||
// AddDatum adds d to this data section, growing it if necessary. Errors if the datum overlaps with existing data.
|
||||
func (g *Global) AddDatum(d Datum) error {
|
||||
for _, other := range g.Data {
|
||||
if d.Overlaps(other) {
|
||||
return errors.New("overlaps existing datum")
|
||||
}
|
||||
}
|
||||
g.add(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Append the constant to the end of the data section.
|
||||
func (g *Global) Append(v operand.Constant) {
|
||||
g.add(Datum{
|
||||
Offset: g.Size,
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Global) add(d Datum) {
|
||||
_, end := d.Interval()
|
||||
g.Grow(end)
|
||||
g.Data = append(g.Data, d)
|
||||
}
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
package operand
|
||||
|
||||
import "github.com/mmcloughlin/avo/reg"
|
||||
|
||||
// Pure type assertion checks:
|
||||
|
||||
// IsRegister returns whether op has type reg.Register.
|
||||
func IsRegister(op Op) bool { _, ok := op.(reg.Register); return ok }
|
||||
|
||||
// IsMem returns whether op has type Mem.
|
||||
func IsMem(op Op) bool { _, ok := op.(Mem); return ok }
|
||||
|
||||
// IsRel returns whether op has type Rel.
|
||||
func IsRel(op Op) bool { _, ok := op.(Rel); return ok }
|
||||
|
||||
// Checks corresponding to specific operand types in the Intel Manual:
|
||||
|
||||
// Is1 returns true if op is the immediate constant 1.
|
||||
func Is1(op Op) bool {
|
||||
i, ok := op.(U8)
|
||||
return ok && i == 1
|
||||
}
|
||||
|
||||
// Is3 returns true if op is the immediate constant 3.
|
||||
func Is3(op Op) bool {
|
||||
i, ok := op.(U8)
|
||||
return ok && i == 3
|
||||
}
|
||||
|
||||
// IsIMM2U returns true if op is a 2-bit unsigned immediate (less than 4).
|
||||
func IsIMM2U(op Op) bool {
|
||||
i, ok := op.(U8)
|
||||
return ok && i < 4
|
||||
}
|
||||
|
||||
// IsIMM8 returns true is op is an 8-bit immediate.
|
||||
func IsIMM8(op Op) bool {
|
||||
_, ok := op.(U8)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsIMM16 returns true is op is a 16-bit immediate.
|
||||
func IsIMM16(op Op) bool {
|
||||
_, ok := op.(U16)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsIMM32 returns true is op is a 32-bit immediate.
|
||||
func IsIMM32(op Op) bool {
|
||||
_, ok := op.(U32)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsIMM64 returns true is op is a 64-bit immediate.
|
||||
func IsIMM64(op Op) bool {
|
||||
_, ok := op.(U64)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsAL returns true if op is the AL register.
|
||||
func IsAL(op Op) bool {
|
||||
return op == reg.AL
|
||||
}
|
||||
|
||||
// IsCL returns true if op is the CL register.
|
||||
func IsCL(op Op) bool {
|
||||
return op == reg.CL
|
||||
}
|
||||
|
||||
// IsAX returns true if op is the 16-bit AX register.
|
||||
func IsAX(op Op) bool {
|
||||
return op == reg.AX
|
||||
}
|
||||
|
||||
// IsEAX returns true if op is the 32-bit EAX register.
|
||||
func IsEAX(op Op) bool {
|
||||
return op == reg.EAX
|
||||
}
|
||||
|
||||
// IsRAX returns true if op is the 64-bit RAX register.
|
||||
func IsRAX(op Op) bool {
|
||||
return op == reg.RAX
|
||||
}
|
||||
|
||||
// IsR8 returns true if op is an 8-bit general-purpose register.
|
||||
func IsR8(op Op) bool {
|
||||
return IsGP(op, 1)
|
||||
}
|
||||
|
||||
// IsR16 returns true if op is a 16-bit general-purpose register.
|
||||
func IsR16(op Op) bool {
|
||||
return IsGP(op, 2)
|
||||
}
|
||||
|
||||
// IsR32 returns true if op is a 32-bit general-purpose register.
|
||||
func IsR32(op Op) bool {
|
||||
return IsGP(op, 4)
|
||||
}
|
||||
|
||||
// IsR64 returns true if op is a 64-bit general-purpose register.
|
||||
func IsR64(op Op) bool {
|
||||
return IsGP(op, 8)
|
||||
}
|
||||
|
||||
// IsPseudo returns true if op is a pseudo register.
|
||||
func IsPseudo(op Op) bool {
|
||||
return IsRegisterKind(op, reg.KindPseudo)
|
||||
}
|
||||
|
||||
// IsGP returns true if op is a general-purpose register of size n bytes.
|
||||
func IsGP(op Op, n uint) bool {
|
||||
return IsRegisterKindSize(op, reg.KindGP, n)
|
||||
}
|
||||
|
||||
// IsXMM0 returns true if op is the X0 register.
|
||||
func IsXMM0(op Op) bool {
|
||||
return op == reg.X0
|
||||
}
|
||||
|
||||
// IsXMM returns true if op is a 128-bit XMM register.
|
||||
func IsXMM(op Op) bool {
|
||||
return IsRegisterKindSize(op, reg.KindVector, 16)
|
||||
}
|
||||
|
||||
// IsYMM returns true if op is a 256-bit YMM register.
|
||||
func IsYMM(op Op) bool {
|
||||
return IsRegisterKindSize(op, reg.KindVector, 32)
|
||||
}
|
||||
|
||||
// IsRegisterKindSize returns true if op is a register of the given kind and size in bytes.
|
||||
func IsRegisterKindSize(op Op, k reg.Kind, n uint) bool {
|
||||
r, ok := op.(reg.Register)
|
||||
return ok && r.Kind() == k && r.Size() == n
|
||||
}
|
||||
|
||||
// IsRegisterKind returns true if op is a register of the given kind.
|
||||
func IsRegisterKind(op Op, k reg.Kind) bool {
|
||||
r, ok := op.(reg.Register)
|
||||
return ok && r.Kind() == k
|
||||
}
|
||||
|
||||
// IsM returns true if op is a 16-, 32- or 64-bit memory operand.
|
||||
func IsM(op Op) bool {
|
||||
// TODO(mbm): confirm "m" check is defined correctly
|
||||
// Intel manual: "A 16-, 32- or 64-bit operand in memory."
|
||||
return IsM16(op) || IsM32(op) || IsM64(op)
|
||||
}
|
||||
|
||||
// IsM8 returns true if op is an 8-bit memory operand.
|
||||
func IsM8(op Op) bool {
|
||||
// TODO(mbm): confirm "m8" check is defined correctly
|
||||
// Intel manual: "A byte operand in memory, usually expressed as a variable or
|
||||
// array name, but pointed to by the DS:(E)SI or ES:(E)DI registers. In 64-bit
|
||||
// mode, it is pointed to by the RSI or RDI registers."
|
||||
return IsMSize(op, 1)
|
||||
}
|
||||
|
||||
// IsM16 returns true if op is a 16-bit memory operand.
|
||||
func IsM16(op Op) bool {
|
||||
return IsMSize(op, 2)
|
||||
}
|
||||
|
||||
// IsM32 returns true if op is a 16-bit memory operand.
|
||||
func IsM32(op Op) bool {
|
||||
return IsMSize(op, 4)
|
||||
}
|
||||
|
||||
// IsM64 returns true if op is a 64-bit memory operand.
|
||||
func IsM64(op Op) bool {
|
||||
return IsMSize(op, 8)
|
||||
}
|
||||
|
||||
// IsMSize returns true if op is a memory operand using general-purpose address
|
||||
// registers of the given size in bytes.
|
||||
func IsMSize(op Op, n uint) bool {
|
||||
// TODO(mbm): should memory operands have a size attribute as well?
|
||||
// TODO(mbm): m8,m16,m32,m64 checks do not actually check size
|
||||
m, ok := op.(Mem)
|
||||
return ok && IsMReg(m.Base) && (m.Index == nil || IsMReg(m.Index))
|
||||
}
|
||||
|
||||
// IsMReg returns true if op is a register that can be used in a memory operand.
|
||||
func IsMReg(op Op) bool {
|
||||
return IsPseudo(op) || IsRegisterKind(op, reg.KindGP)
|
||||
}
|
||||
|
||||
// IsM128 returns true if op is a 128-bit memory operand.
|
||||
func IsM128(op Op) bool {
|
||||
// TODO(mbm): should "m128" be the same as "m64"?
|
||||
return IsM64(op)
|
||||
}
|
||||
|
||||
// IsM256 returns true if op is a 256-bit memory operand.
|
||||
func IsM256(op Op) bool {
|
||||
// TODO(mbm): should "m256" be the same as "m64"?
|
||||
return IsM64(op)
|
||||
}
|
||||
|
||||
// IsVM32X returns true if op is a vector memory operand with 32-bit XMM index.
|
||||
func IsVM32X(op Op) bool {
|
||||
return IsVmx(op)
|
||||
}
|
||||
|
||||
// IsVM64X returns true if op is a vector memory operand with 64-bit XMM index.
|
||||
func IsVM64X(op Op) bool {
|
||||
return IsVmx(op)
|
||||
}
|
||||
|
||||
// IsVmx returns true if op is a vector memory operand with XMM index.
|
||||
func IsVmx(op Op) bool {
|
||||
return isvm(op, IsXMM)
|
||||
}
|
||||
|
||||
// IsVM32Y returns true if op is a vector memory operand with 32-bit YMM index.
|
||||
func IsVM32Y(op Op) bool {
|
||||
return IsVmy(op)
|
||||
}
|
||||
|
||||
// IsVM64Y returns true if op is a vector memory operand with 64-bit YMM index.
|
||||
func IsVM64Y(op Op) bool {
|
||||
return IsVmy(op)
|
||||
}
|
||||
|
||||
// IsVmy returns true if op is a vector memory operand with YMM index.
|
||||
func IsVmy(op Op) bool {
|
||||
return isvm(op, IsYMM)
|
||||
}
|
||||
|
||||
func isvm(op Op, idx func(Op) bool) bool {
|
||||
m, ok := op.(Mem)
|
||||
return ok && IsR64(m.Base) && idx(m.Index)
|
||||
}
|
||||
|
||||
// IsREL8 returns true if op is an 8-bit offset relative to instruction pointer.
|
||||
func IsREL8(op Op) bool {
|
||||
r, ok := op.(Rel)
|
||||
return ok && r == Rel(int8(r))
|
||||
}
|
||||
|
||||
// IsREL32 returns true if op is an offset relative to instruction pointer, or a
|
||||
// label reference.
|
||||
func IsREL32(op Op) bool {
|
||||
// TODO(mbm): should labels be considered separately?
|
||||
_, rel := op.(Rel)
|
||||
_, label := op.(LabelRef)
|
||||
return rel || label
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package operand
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Constant represents a constant literal.
|
||||
type Constant interface {
|
||||
Op
|
||||
Bytes() int
|
||||
constant()
|
||||
}
|
||||
|
||||
//go:generate go run make_const.go -output zconst.go
|
||||
|
||||
// String is a string constant.
|
||||
type String string
|
||||
|
||||
// Asm returns an assembly syntax representation of the string s.
|
||||
func (s String) Asm() string { return fmt.Sprintf("$%q", s) }
|
||||
|
||||
// Bytes returns the length of s.
|
||||
func (s String) Bytes() int { return len(s) }
|
||||
|
||||
func (s String) constant() {}
|
||||
|
||||
// Imm returns an unsigned integer constant with size guessed from x.
|
||||
func Imm(x uint64) Constant {
|
||||
switch {
|
||||
case uint64(uint8(x)) == x:
|
||||
return U8(x)
|
||||
case uint64(uint16(x)) == x:
|
||||
return U16(x)
|
||||
case uint64(uint32(x)) == x:
|
||||
return U32(x)
|
||||
}
|
||||
return U64(x)
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package operand provides types for instruction operands.
|
||||
package operand
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
package operand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
// Op is an operand.
|
||||
type Op interface {
|
||||
Asm() string
|
||||
}
|
||||
|
||||
// Symbol represents a symbol name.
|
||||
type Symbol struct {
|
||||
Name string
|
||||
Static bool // only visible in current source file
|
||||
}
|
||||
|
||||
// NewStaticSymbol builds a static Symbol. Static symbols are only visible in the current source file.
|
||||
func NewStaticSymbol(name string) Symbol {
|
||||
return Symbol{Name: name, Static: true}
|
||||
}
|
||||
|
||||
func (s Symbol) String() string {
|
||||
n := s.Name
|
||||
if s.Static {
|
||||
n += "<>"
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Mem represents a memory reference.
|
||||
type Mem struct {
|
||||
Symbol Symbol
|
||||
Disp int
|
||||
Base reg.Register
|
||||
Index reg.Register
|
||||
Scale uint8
|
||||
}
|
||||
|
||||
// NewParamAddr is a convenience to build a Mem operand pointing to a function
|
||||
// parameter, which is a named offset from the frame pointer pseudo register.
|
||||
func NewParamAddr(name string, offset int) Mem {
|
||||
return Mem{
|
||||
Symbol: Symbol{
|
||||
Name: name,
|
||||
Static: false,
|
||||
},
|
||||
Disp: offset,
|
||||
Base: reg.FramePointer,
|
||||
}
|
||||
}
|
||||
|
||||
// NewStackAddr returns a memory reference relative to the stack pointer.
|
||||
func NewStackAddr(offset int) Mem {
|
||||
return Mem{
|
||||
Disp: offset,
|
||||
Base: reg.StackPointer,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDataAddr returns a memory reference relative to the named data symbol.
|
||||
func NewDataAddr(sym Symbol, offset int) Mem {
|
||||
return Mem{
|
||||
Symbol: sym,
|
||||
Disp: offset,
|
||||
Base: reg.StaticBase,
|
||||
}
|
||||
}
|
||||
|
||||
// Offset returns a reference to m plus idx bytes.
|
||||
func (m Mem) Offset(idx int) Mem {
|
||||
a := m
|
||||
a.Disp += idx
|
||||
return a
|
||||
}
|
||||
|
||||
// Idx returns a new memory reference with (Index, Scale) set to (r, s).
|
||||
func (m Mem) Idx(r reg.Register, s uint8) Mem {
|
||||
a := m
|
||||
a.Index = r
|
||||
a.Scale = s
|
||||
return a
|
||||
}
|
||||
|
||||
// Asm returns an assembly syntax representation of m.
|
||||
func (m Mem) Asm() string {
|
||||
a := m.Symbol.String()
|
||||
if a != "" {
|
||||
a += fmt.Sprintf("%+d", m.Disp)
|
||||
} else if m.Disp != 0 {
|
||||
a += fmt.Sprintf("%d", m.Disp)
|
||||
}
|
||||
if m.Base != nil {
|
||||
a += fmt.Sprintf("(%s)", m.Base.Asm())
|
||||
}
|
||||
if m.Index != nil && m.Scale != 0 {
|
||||
a += fmt.Sprintf("(%s*%d)", m.Index.Asm(), m.Scale)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Rel is an offset relative to the instruction pointer.
|
||||
type Rel int32
|
||||
|
||||
// Asm returns an assembly syntax representation of r.
|
||||
func (r Rel) Asm() string {
|
||||
return fmt.Sprintf(".%+d", r)
|
||||
}
|
||||
|
||||
// LabelRef is a reference to a label.
|
||||
type LabelRef string
|
||||
|
||||
// Asm returns an assembly syntax representation of l.
|
||||
func (l LabelRef) Asm() string {
|
||||
return string(l)
|
||||
}
|
||||
|
||||
// Registers returns the list of all operands involved in the given operand.
|
||||
func Registers(op Op) []reg.Register {
|
||||
switch op := op.(type) {
|
||||
case reg.Register:
|
||||
return []reg.Register{op}
|
||||
case Mem:
|
||||
var r []reg.Register
|
||||
if op.Base != nil {
|
||||
r = append(r, op.Base)
|
||||
}
|
||||
if op.Index != nil {
|
||||
r = append(r, op.Index)
|
||||
}
|
||||
return r
|
||||
case Constant, Rel, LabelRef:
|
||||
return nil
|
||||
}
|
||||
panic("unknown operand type")
|
||||
}
|
||||
|
||||
// ApplyAllocation returns an operand with allocated registers replaced. Registers missing from the allocation are left alone.
|
||||
func ApplyAllocation(op Op, a reg.Allocation) Op {
|
||||
switch op := op.(type) {
|
||||
case reg.Register:
|
||||
return a.LookupRegisterDefault(op)
|
||||
case Mem:
|
||||
op.Base = a.LookupRegisterDefault(op.Base)
|
||||
op.Index = a.LookupRegisterDefault(op.Index)
|
||||
return op
|
||||
}
|
||||
return op
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// Code generated by make_const.go. DO NOT EDIT.
|
||||
|
||||
package operand
|
||||
|
||||
import "fmt"
|
||||
|
||||
// I8 is a 8-bit signed integer constant.
|
||||
type I8 int8
|
||||
|
||||
func (i I8) Asm() string { return fmt.Sprintf("$%+d", i) }
|
||||
func (i I8) Bytes() int { return 1 }
|
||||
func (i I8) constant() {}
|
||||
|
||||
// U8 is a 8-bit unsigned integer constant.
|
||||
type U8 uint8
|
||||
|
||||
func (u U8) Asm() string { return fmt.Sprintf("$%#02x", u) }
|
||||
func (u U8) Bytes() int { return 1 }
|
||||
func (u U8) constant() {}
|
||||
|
||||
// I16 is a 16-bit signed integer constant.
|
||||
type I16 int16
|
||||
|
||||
func (i I16) Asm() string { return fmt.Sprintf("$%+d", i) }
|
||||
func (i I16) Bytes() int { return 2 }
|
||||
func (i I16) constant() {}
|
||||
|
||||
// U16 is a 16-bit unsigned integer constant.
|
||||
type U16 uint16
|
||||
|
||||
func (u U16) Asm() string { return fmt.Sprintf("$%#04x", u) }
|
||||
func (u U16) Bytes() int { return 2 }
|
||||
func (u U16) constant() {}
|
||||
|
||||
// F32 is a 32-bit floating point constant.
|
||||
type F32 float32
|
||||
|
||||
func (f F32) Asm() string { return fmt.Sprintf("$(%#v)", f) }
|
||||
func (f F32) Bytes() int { return 4 }
|
||||
func (f F32) constant() {}
|
||||
|
||||
// I32 is a 32-bit signed integer constant.
|
||||
type I32 int32
|
||||
|
||||
func (i I32) Asm() string { return fmt.Sprintf("$%+d", i) }
|
||||
func (i I32) Bytes() int { return 4 }
|
||||
func (i I32) constant() {}
|
||||
|
||||
// U32 is a 32-bit unsigned integer constant.
|
||||
type U32 uint32
|
||||
|
||||
func (u U32) Asm() string { return fmt.Sprintf("$%#08x", u) }
|
||||
func (u U32) Bytes() int { return 4 }
|
||||
func (u U32) constant() {}
|
||||
|
||||
// F64 is a 64-bit floating point constant.
|
||||
type F64 float64
|
||||
|
||||
func (f F64) Asm() string { return fmt.Sprintf("$(%#v)", f) }
|
||||
func (f F64) Bytes() int { return 8 }
|
||||
func (f F64) constant() {}
|
||||
|
||||
// I64 is a 64-bit signed integer constant.
|
||||
type I64 int64
|
||||
|
||||
func (i I64) Asm() string { return fmt.Sprintf("$%+d", i) }
|
||||
func (i I64) Bytes() int { return 8 }
|
||||
func (i I64) constant() {}
|
||||
|
||||
// U64 is a 64-bit unsigned integer constant.
|
||||
type U64 uint64
|
||||
|
||||
func (u U64) Asm() string { return fmt.Sprintf("$%#016x", u) }
|
||||
func (u U64) Bytes() int { return 8 }
|
||||
func (u U64) constant() {}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
// edge is an edge of the interference graph, indicating that registers X and Y
|
||||
// must be in non-conflicting registers.
|
||||
type edge struct {
|
||||
X, Y reg.ID
|
||||
}
|
||||
|
||||
// Allocator is a graph-coloring register allocator.
|
||||
type Allocator struct {
|
||||
registers []reg.ID
|
||||
allocation reg.Allocation
|
||||
edges []*edge
|
||||
possible map[reg.ID][]reg.ID
|
||||
}
|
||||
|
||||
// NewAllocator builds an allocator for the given physical registers.
|
||||
func NewAllocator(rs []reg.Physical) (*Allocator, error) {
|
||||
// Set of IDs, excluding restricted registers.
|
||||
idset := map[reg.ID]bool{}
|
||||
for _, r := range rs {
|
||||
if (r.Info() & reg.Restricted) != 0 {
|
||||
continue
|
||||
}
|
||||
idset[r.ID()] = true
|
||||
}
|
||||
|
||||
if len(idset) == 0 {
|
||||
return nil, errors.New("no allocatable registers")
|
||||
}
|
||||
|
||||
// Produce slice of unique register IDs.
|
||||
var ids []reg.ID
|
||||
for id := range idset {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
||||
|
||||
return &Allocator{
|
||||
registers: ids,
|
||||
allocation: reg.NewEmptyAllocation(),
|
||||
possible: map[reg.ID][]reg.ID{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewAllocatorForKind builds an allocator for the given kind of registers.
|
||||
func NewAllocatorForKind(k reg.Kind) (*Allocator, error) {
|
||||
f := reg.FamilyOfKind(k)
|
||||
if f == nil {
|
||||
return nil, errors.New("unknown register family")
|
||||
}
|
||||
return NewAllocator(f.Registers())
|
||||
}
|
||||
|
||||
// AddInterferenceSet records that r interferes with every register in s. Convenience wrapper around AddInterference.
|
||||
func (a *Allocator) AddInterferenceSet(r reg.Register, s reg.MaskSet) {
|
||||
for id, mask := range s {
|
||||
if (r.Mask() & mask) != 0 {
|
||||
a.AddInterference(r.ID(), id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddInterference records that x and y must be assigned to non-conflicting physical registers.
|
||||
func (a *Allocator) AddInterference(x, y reg.ID) {
|
||||
a.Add(x)
|
||||
a.Add(y)
|
||||
a.edges = append(a.edges, &edge{X: x, Y: y})
|
||||
}
|
||||
|
||||
// Add adds a register to be allocated. Does nothing if the register has already been added.
|
||||
func (a *Allocator) Add(v reg.ID) {
|
||||
if !v.IsVirtual() {
|
||||
return
|
||||
}
|
||||
if _, found := a.possible[v]; found {
|
||||
return
|
||||
}
|
||||
a.possible[v] = a.possibleregisters(v)
|
||||
}
|
||||
|
||||
// Allocate allocates physical registers.
|
||||
func (a *Allocator) Allocate() (reg.Allocation, error) {
|
||||
for {
|
||||
if err := a.update(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if a.remaining() == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
v := a.mostrestricted()
|
||||
if err := a.alloc(v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return a.allocation, nil
|
||||
}
|
||||
|
||||
// update possible allocations based on edges.
|
||||
func (a *Allocator) update() error {
|
||||
var rem []*edge
|
||||
for _, e := range a.edges {
|
||||
x := a.allocation.LookupDefault(e.X)
|
||||
y := a.allocation.LookupDefault(e.Y)
|
||||
switch {
|
||||
case x.IsVirtual() && y.IsVirtual():
|
||||
rem = append(rem, e)
|
||||
continue
|
||||
case x.IsPhysical() && y.IsPhysical():
|
||||
if x == y {
|
||||
return errors.New("impossible register allocation")
|
||||
}
|
||||
case x.IsPhysical() && y.IsVirtual():
|
||||
a.discardconflicting(y, x)
|
||||
case x.IsVirtual() && y.IsPhysical():
|
||||
a.discardconflicting(x, y)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
a.edges = rem
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// mostrestricted returns the virtual register with the least possibilities.
|
||||
func (a *Allocator) mostrestricted() reg.ID {
|
||||
n := int(math.MaxInt32)
|
||||
var v reg.ID
|
||||
for w, p := range a.possible {
|
||||
// On a tie, choose the smallest ID in numeric order. This avoids
|
||||
// non-deterministic allocations due to map iteration order.
|
||||
if len(p) < n || (len(p) == n && w < v) {
|
||||
n = len(p)
|
||||
v = w
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// discardconflicting removes registers from vs possible list that conflict with p.
|
||||
func (a *Allocator) discardconflicting(v, p reg.ID) {
|
||||
a.possible[v] = filterregisters(a.possible[v], func(r reg.ID) bool {
|
||||
return r != p
|
||||
})
|
||||
}
|
||||
|
||||
// alloc attempts to allocate a register to v.
|
||||
func (a *Allocator) alloc(v reg.ID) error {
|
||||
ps := a.possible[v]
|
||||
if len(ps) == 0 {
|
||||
return errors.New("failed to allocate registers")
|
||||
}
|
||||
p := ps[0]
|
||||
a.allocation[v] = p
|
||||
delete(a.possible, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// remaining returns the number of unallocated registers.
|
||||
func (a *Allocator) remaining() int {
|
||||
return len(a.possible)
|
||||
}
|
||||
|
||||
// possibleregisters returns all allocate-able registers for the given virtual.
|
||||
func (a *Allocator) possibleregisters(v reg.ID) []reg.ID {
|
||||
return filterregisters(a.registers, func(r reg.ID) bool {
|
||||
return v.Kind() == r.Kind()
|
||||
})
|
||||
}
|
||||
|
||||
func filterregisters(in []reg.ID, predicate func(reg.ID) bool) []reg.ID {
|
||||
var rs []reg.ID
|
||||
for _, r := range in {
|
||||
if predicate(r) {
|
||||
rs = append(rs, r)
|
||||
}
|
||||
}
|
||||
return rs
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
)
|
||||
|
||||
// LabelTarget populates the LabelTarget of the given function. This maps from
|
||||
// label name to the following instruction.
|
||||
func LabelTarget(fn *ir.Function) error {
|
||||
target := map[ir.Label]*ir.Instruction{}
|
||||
var pending []ir.Label
|
||||
for _, node := range fn.Nodes {
|
||||
switch n := node.(type) {
|
||||
case ir.Label:
|
||||
if _, found := target[n]; found {
|
||||
return fmt.Errorf("duplicate label \"%s\"", n)
|
||||
}
|
||||
pending = append(pending, n)
|
||||
case *ir.Instruction:
|
||||
for _, label := range pending {
|
||||
target[label] = n
|
||||
}
|
||||
pending = nil
|
||||
}
|
||||
}
|
||||
if len(pending) != 0 {
|
||||
return errors.New("function ends with label")
|
||||
}
|
||||
fn.LabelTarget = target
|
||||
return nil
|
||||
}
|
||||
|
||||
// CFG constructs the call-flow-graph for the function.
|
||||
func CFG(fn *ir.Function) error {
|
||||
is := fn.Instructions()
|
||||
n := len(is)
|
||||
|
||||
// Populate successors.
|
||||
for i := 0; i < n; i++ {
|
||||
cur := is[i]
|
||||
var nxt *ir.Instruction
|
||||
if i+1 < n {
|
||||
nxt = is[i+1]
|
||||
}
|
||||
|
||||
// If it's a branch, locate the target.
|
||||
if cur.IsBranch {
|
||||
lbl := cur.TargetLabel()
|
||||
if lbl == nil {
|
||||
return errors.New("no label for branch instruction")
|
||||
}
|
||||
target, found := fn.LabelTarget[*lbl]
|
||||
if !found {
|
||||
return fmt.Errorf("unknown label %q", *lbl)
|
||||
}
|
||||
cur.Succ = append(cur.Succ, target)
|
||||
}
|
||||
|
||||
// Otherwise, could continue to the following instruction.
|
||||
switch {
|
||||
case cur.IsTerminal:
|
||||
case cur.IsUnconditionalBranch():
|
||||
default:
|
||||
cur.Succ = append(cur.Succ, nxt)
|
||||
}
|
||||
}
|
||||
|
||||
// Populate predecessors.
|
||||
for _, i := range is {
|
||||
for _, s := range i.Succ {
|
||||
if s != nil {
|
||||
s.Pred = append(s.Pred, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
)
|
||||
|
||||
// PruneJumpToFollowingLabel removes jump instructions that target an
|
||||
// immediately following label.
|
||||
func PruneJumpToFollowingLabel(fn *ir.Function) error {
|
||||
for i := 0; i+1 < len(fn.Nodes); i++ {
|
||||
node := fn.Nodes[i]
|
||||
next := fn.Nodes[i+1]
|
||||
|
||||
// This node is an unconditional jump.
|
||||
inst, ok := node.(*ir.Instruction)
|
||||
if !ok || !inst.IsBranch || inst.IsConditional {
|
||||
continue
|
||||
}
|
||||
|
||||
target := inst.TargetLabel()
|
||||
if target == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// And the jump target is the immediately following node.
|
||||
lbl, ok := next.(ir.Label)
|
||||
if !ok || lbl != *target {
|
||||
continue
|
||||
}
|
||||
|
||||
// Then the jump is unnecessary and can be removed.
|
||||
fn.Nodes = deletenode(fn.Nodes, i)
|
||||
i--
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PruneDanglingLabels removes labels that are not referenced by any branches.
|
||||
func PruneDanglingLabels(fn *ir.Function) error {
|
||||
// Count label references.
|
||||
count := map[ir.Label]int{}
|
||||
for _, n := range fn.Nodes {
|
||||
i, ok := n.(*ir.Instruction)
|
||||
if !ok || !i.IsBranch {
|
||||
continue
|
||||
}
|
||||
|
||||
target := i.TargetLabel()
|
||||
if target == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
count[*target]++
|
||||
}
|
||||
|
||||
// Look for labels with no references.
|
||||
for i := 0; i < len(fn.Nodes); i++ {
|
||||
node := fn.Nodes[i]
|
||||
lbl, ok := node.(ir.Label)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if count[lbl] == 0 {
|
||||
fn.Nodes = deletenode(fn.Nodes, i)
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PruneSelfMoves removes move instructions from one register to itself.
|
||||
func PruneSelfMoves(fn *ir.Function) error {
|
||||
return removeinstructions(fn, func(i *ir.Instruction) bool {
|
||||
switch i.Opcode {
|
||||
case "MOVB", "MOVW", "MOVL", "MOVQ":
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
return operand.IsRegister(i.Operands[0]) && operand.IsRegister(i.Operands[1]) && i.Operands[0] == i.Operands[1]
|
||||
})
|
||||
}
|
||||
|
||||
// removeinstructions deletes instructions from the given function which match predicate.
|
||||
func removeinstructions(fn *ir.Function, predicate func(*ir.Instruction) bool) error {
|
||||
// Removal of instructions has the potential to invalidate CFG structures.
|
||||
// Clear them to prevent accidental use of stale structures after this pass.
|
||||
invalidatecfg(fn)
|
||||
|
||||
for i := 0; i < len(fn.Nodes); i++ {
|
||||
n := fn.Nodes[i]
|
||||
|
||||
inst, ok := n.(*ir.Instruction)
|
||||
if !ok || !predicate(inst) {
|
||||
continue
|
||||
}
|
||||
|
||||
fn.Nodes = deletenode(fn.Nodes, i)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deletenode deletes node i from nodes and returns the resulting slice.
|
||||
func deletenode(nodes []ir.Node, i int) []ir.Node {
|
||||
n := len(nodes)
|
||||
copy(nodes[i:], nodes[i+1:])
|
||||
nodes[n-1] = nil
|
||||
return nodes[:n-1]
|
||||
}
|
||||
|
||||
// invalidatecfg clears CFG structures.
|
||||
func invalidatecfg(fn *ir.Function) {
|
||||
fn.LabelTarget = nil
|
||||
for _, i := range fn.Instructions() {
|
||||
i.Pred = nil
|
||||
i.Succ = nil
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
)
|
||||
|
||||
// RequiredISAExtensions determines ISA extensions required for the given
|
||||
// function. Populates the ISA field.
|
||||
func RequiredISAExtensions(fn *ir.Function) error {
|
||||
// Collect ISA set.
|
||||
set := map[string]bool{}
|
||||
for _, i := range fn.Instructions() {
|
||||
for _, isa := range i.ISA {
|
||||
set[isa] = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(set) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Populate the function's ISA field with the unique sorted list.
|
||||
for isa := range set {
|
||||
fn.ISA = append(fn.ISA, isa)
|
||||
}
|
||||
sort.Strings(fn.ISA)
|
||||
|
||||
return nil
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
// Package pass implements processing passes on avo Files.
|
||||
package pass
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
"github.com/mmcloughlin/avo/printer"
|
||||
)
|
||||
|
||||
// Compile pass compiles an avo file. Upon successful completion the avo file
|
||||
// may be printed to Go assembly.
|
||||
var Compile = Concat(
|
||||
Verify,
|
||||
FunctionPass(PruneJumpToFollowingLabel),
|
||||
FunctionPass(PruneDanglingLabels),
|
||||
FunctionPass(LabelTarget),
|
||||
FunctionPass(CFG),
|
||||
InstructionPass(ZeroExtend32BitOutputs),
|
||||
FunctionPass(Liveness),
|
||||
FunctionPass(AllocateRegisters),
|
||||
FunctionPass(BindRegisters),
|
||||
FunctionPass(VerifyAllocation),
|
||||
Func(IncludeTextFlagHeader),
|
||||
FunctionPass(PruneSelfMoves),
|
||||
FunctionPass(RequiredISAExtensions),
|
||||
)
|
||||
|
||||
// Interface for a processing pass.
|
||||
type Interface interface {
|
||||
Execute(*ir.File) error
|
||||
}
|
||||
|
||||
// Func adapts a function to the pass Interface.
|
||||
type Func func(*ir.File) error
|
||||
|
||||
// Execute calls p.
|
||||
func (p Func) Execute(f *ir.File) error {
|
||||
return p(f)
|
||||
}
|
||||
|
||||
// FunctionPass is a convenience for implementing a full file pass with a
|
||||
// function that operates on each avo Function independently.
|
||||
type FunctionPass func(*ir.Function) error
|
||||
|
||||
// Execute calls p on every function in the file. Exits on the first error.
|
||||
func (p FunctionPass) Execute(f *ir.File) error {
|
||||
for _, fn := range f.Functions() {
|
||||
if err := p(fn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InstructionPass is a convenience for implementing a full file pass with a
|
||||
// function that operates on each Instruction independently.
|
||||
type InstructionPass func(*ir.Instruction) error
|
||||
|
||||
// Execute calls p on every instruction in the file. Exits on the first error.
|
||||
func (p InstructionPass) Execute(f *ir.File) error {
|
||||
for _, fn := range f.Functions() {
|
||||
for _, i := range fn.Instructions() {
|
||||
if err := p(i); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Concat returns a pass that executes the given passes in order, stopping on the first error.
|
||||
func Concat(passes ...Interface) Interface {
|
||||
return Func(func(f *ir.File) error {
|
||||
for _, p := range passes {
|
||||
if err := p.Execute(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Output pass prints a file.
|
||||
type Output struct {
|
||||
Writer io.WriteCloser
|
||||
Printer printer.Printer
|
||||
}
|
||||
|
||||
// Execute prints f with the configured Printer and writes output to Writer.
|
||||
func (o *Output) Execute(f *ir.File) error {
|
||||
b, err := o.Printer.Print(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = o.Writer.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return o.Writer.Close()
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
// ZeroExtend32BitOutputs applies the rule that "32-bit operands generate a
|
||||
// 32-bit result, zero-extended to a 64-bit result in the destination
|
||||
// general-purpose register" (Intel Software Developer’s Manual, Volume 1,
|
||||
// 3.4.1.1).
|
||||
func ZeroExtend32BitOutputs(i *ir.Instruction) error {
|
||||
for j, op := range i.Outputs {
|
||||
if !operand.IsR32(op) {
|
||||
continue
|
||||
}
|
||||
r, ok := op.(reg.GP)
|
||||
if !ok {
|
||||
panic("r32 operand should satisfy reg.GP")
|
||||
}
|
||||
i.Outputs[j] = r.As64()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Liveness computes register liveness.
|
||||
func Liveness(fn *ir.Function) error {
|
||||
// Note this implementation is initially naive so as to be "obviously correct".
|
||||
// There are a well-known optimizations we can apply if necessary.
|
||||
|
||||
is := fn.Instructions()
|
||||
|
||||
// Process instructions in reverse: poor approximation to topological sort.
|
||||
// TODO(mbm): process instructions in topological sort order
|
||||
for l, r := 0, len(is)-1; l < r; l, r = l+1, r-1 {
|
||||
is[l], is[r] = is[r], is[l]
|
||||
}
|
||||
|
||||
// Initialize.
|
||||
for _, i := range is {
|
||||
i.LiveIn = reg.NewMaskSetFromRegisters(i.InputRegisters())
|
||||
i.LiveOut = reg.NewEmptyMaskSet()
|
||||
}
|
||||
|
||||
// Iterative dataflow analysis.
|
||||
for {
|
||||
changes := false
|
||||
|
||||
for _, i := range is {
|
||||
// out[n] = UNION[s IN succ[n]] in[s]
|
||||
for _, s := range i.Succ {
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
changes = i.LiveOut.Update(s.LiveIn) || changes
|
||||
}
|
||||
|
||||
// in[n] = use[n] UNION (out[n] - def[n])
|
||||
def := reg.NewMaskSetFromRegisters(i.OutputRegisters())
|
||||
changes = i.LiveIn.Update(i.LiveOut.Difference(def)) || changes
|
||||
}
|
||||
|
||||
if !changes {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllocateRegisters performs register allocation.
|
||||
func AllocateRegisters(fn *ir.Function) error {
|
||||
// Populate allocators (one per kind).
|
||||
as := map[reg.Kind]*Allocator{}
|
||||
for _, i := range fn.Instructions() {
|
||||
for _, r := range i.Registers() {
|
||||
k := r.Kind()
|
||||
if _, found := as[k]; !found {
|
||||
a, err := NewAllocatorForKind(k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
as[k] = a
|
||||
}
|
||||
as[k].Add(r.ID())
|
||||
}
|
||||
}
|
||||
|
||||
// Record register interferences.
|
||||
for _, i := range fn.Instructions() {
|
||||
for _, d := range i.OutputRegisters() {
|
||||
k := d.Kind()
|
||||
out := i.LiveOut.OfKind(k)
|
||||
out.DiscardRegister(d)
|
||||
as[k].AddInterferenceSet(d, out)
|
||||
}
|
||||
}
|
||||
|
||||
// Execute register allocation.
|
||||
fn.Allocation = reg.NewEmptyAllocation()
|
||||
for _, a := range as {
|
||||
al, err := a.Allocate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fn.Allocation.Merge(al); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BindRegisters applies the result of register allocation, replacing all virtual registers with their assigned physical registers.
|
||||
func BindRegisters(fn *ir.Function) error {
|
||||
for _, i := range fn.Instructions() {
|
||||
for idx := range i.Operands {
|
||||
i.Operands[idx] = operand.ApplyAllocation(i.Operands[idx], fn.Allocation)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyAllocation performs sanity checks following register allocation.
|
||||
func VerifyAllocation(fn *ir.Function) error {
|
||||
// All registers should be physical.
|
||||
for _, i := range fn.Instructions() {
|
||||
for _, r := range i.Registers() {
|
||||
if reg.ToPhysical(r) == nil {
|
||||
return errors.New("non physical register found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"github.com/mmcloughlin/avo/attr"
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
)
|
||||
|
||||
// IncludeTextFlagHeader includes textflag.h if necessary.
|
||||
func IncludeTextFlagHeader(f *ir.File) error {
|
||||
const textflagheader = "textflag.h"
|
||||
|
||||
// Check if we already have it.
|
||||
for _, path := range f.Includes {
|
||||
if path == textflagheader {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Add it if necessary.
|
||||
if requirestextflags(f) {
|
||||
f.Includes = append(f.Includes, textflagheader)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// requirestextflags returns whether the file uses flags in the textflags.h header.
|
||||
func requirestextflags(f *ir.File) bool {
|
||||
for _, s := range f.Sections {
|
||||
var a attr.Attribute
|
||||
switch s := s.(type) {
|
||||
case *ir.Function:
|
||||
a = s.Attributes
|
||||
case *ir.Global:
|
||||
a = s.Attributes
|
||||
}
|
||||
if a.ContainsTextFlags() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
)
|
||||
|
||||
// Verify pass validates an avo file.
|
||||
var Verify = Concat(
|
||||
InstructionPass(VerifyMemOperands),
|
||||
)
|
||||
|
||||
// VerifyMemOperands checks the instruction's memory operands.
|
||||
func VerifyMemOperands(i *ir.Instruction) error {
|
||||
for _, op := range i.Operands {
|
||||
m, ok := op.(operand.Mem)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if m.Base == nil {
|
||||
return errors.New("bad memory operand: missing base register")
|
||||
}
|
||||
|
||||
if m.Index != nil && m.Scale == 0 {
|
||||
return errors.New("bad memory operand: index register with scale 0")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mmcloughlin/avo/internal/prnt"
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
)
|
||||
|
||||
// dot is the pesky unicode dot used in Go assembly.
|
||||
const dot = "\u00b7"
|
||||
|
||||
type goasm struct {
|
||||
cfg Config
|
||||
prnt.Generator
|
||||
|
||||
instructions []*ir.Instruction
|
||||
clear bool
|
||||
}
|
||||
|
||||
// NewGoAsm constructs a printer for writing Go assembly files.
|
||||
func NewGoAsm(cfg Config) Printer {
|
||||
return &goasm{cfg: cfg}
|
||||
}
|
||||
|
||||
func (p *goasm) Print(f *ir.File) ([]byte, error) {
|
||||
p.header(f)
|
||||
for _, s := range f.Sections {
|
||||
switch s := s.(type) {
|
||||
case *ir.Function:
|
||||
p.function(s)
|
||||
case *ir.Global:
|
||||
p.global(s)
|
||||
default:
|
||||
panic("unknown section type")
|
||||
}
|
||||
}
|
||||
return p.Result()
|
||||
}
|
||||
|
||||
func (p *goasm) header(f *ir.File) {
|
||||
p.Comment(p.cfg.GeneratedWarning())
|
||||
|
||||
if len(f.Constraints) > 0 {
|
||||
p.NL()
|
||||
p.Printf(f.Constraints.GoString())
|
||||
}
|
||||
|
||||
if len(f.Includes) > 0 {
|
||||
p.NL()
|
||||
p.includes(f.Includes)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *goasm) includes(paths []string) {
|
||||
for _, path := range paths {
|
||||
p.Printf("#include \"%s\"\n", path)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *goasm) function(f *ir.Function) {
|
||||
p.NL()
|
||||
p.Comment(f.Stub())
|
||||
|
||||
if len(f.ISA) > 0 {
|
||||
p.Comment("Requires: " + strings.Join(f.ISA, ", "))
|
||||
}
|
||||
|
||||
// Reference: https://github.com/golang/go/blob/b115207baf6c2decc3820ada4574ef4e5ad940ec/src/cmd/internal/obj/util.go#L166-L176
|
||||
//
|
||||
// if p.As == ATEXT {
|
||||
// // If there are attributes, print them. Otherwise, skip the comma.
|
||||
// // In short, print one of these two:
|
||||
// // TEXT foo(SB), DUPOK|NOSPLIT, $0
|
||||
// // TEXT foo(SB), $0
|
||||
// s := p.From.Sym.Attribute.TextAttrString()
|
||||
// if s != "" {
|
||||
// fmt.Fprintf(&buf, "%s%s", sep, s)
|
||||
// sep = ", "
|
||||
// }
|
||||
// }
|
||||
//
|
||||
p.Printf("TEXT %s%s(SB)", dot, f.Name)
|
||||
if f.Attributes != 0 {
|
||||
p.Printf(", %s", f.Attributes.Asm())
|
||||
}
|
||||
p.Printf(", %s\n", textsize(f))
|
||||
|
||||
p.clear = true
|
||||
for _, node := range f.Nodes {
|
||||
switch n := node.(type) {
|
||||
case *ir.Instruction:
|
||||
p.instruction(n)
|
||||
if n.IsTerminal || n.IsUnconditionalBranch() {
|
||||
p.flush()
|
||||
}
|
||||
case ir.Label:
|
||||
p.flush()
|
||||
p.ensureclear()
|
||||
p.Printf("%s:\n", n)
|
||||
case *ir.Comment:
|
||||
p.flush()
|
||||
p.ensureclear()
|
||||
for _, line := range n.Lines {
|
||||
p.Printf("\t// %s\n", line)
|
||||
}
|
||||
default:
|
||||
panic("unexpected node type")
|
||||
}
|
||||
}
|
||||
p.flush()
|
||||
}
|
||||
|
||||
func (p *goasm) instruction(i *ir.Instruction) {
|
||||
p.instructions = append(p.instructions, i)
|
||||
p.clear = false
|
||||
}
|
||||
|
||||
func (p *goasm) flush() {
|
||||
if len(p.instructions) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine instruction width. Instructions with no operands are not
|
||||
// considered in this calculation.
|
||||
width := 0
|
||||
for _, i := range p.instructions {
|
||||
if len(i.Operands) > 0 && len(i.Opcode) > width {
|
||||
width = len(i.Opcode)
|
||||
}
|
||||
}
|
||||
|
||||
// Output instruction block.
|
||||
for _, i := range p.instructions {
|
||||
if len(i.Operands) > 0 {
|
||||
p.Printf("\t%-*s%s\n", width+1, i.Opcode, joinOperands(i.Operands))
|
||||
} else {
|
||||
p.Printf("\t%s\n", i.Opcode)
|
||||
}
|
||||
}
|
||||
|
||||
p.instructions = nil
|
||||
}
|
||||
|
||||
func (p *goasm) ensureclear() {
|
||||
if !p.clear {
|
||||
p.NL()
|
||||
p.clear = true
|
||||
}
|
||||
}
|
||||
|
||||
func (p *goasm) global(g *ir.Global) {
|
||||
p.NL()
|
||||
for _, d := range g.Data {
|
||||
a := operand.NewDataAddr(g.Symbol, d.Offset)
|
||||
p.Printf("DATA %s/%d, %s\n", a.Asm(), d.Value.Bytes(), d.Value.Asm())
|
||||
}
|
||||
p.Printf("GLOBL %s(SB), %s, $%d\n", g.Symbol, g.Attributes.Asm(), g.Size)
|
||||
}
|
||||
|
||||
func textsize(f *ir.Function) string {
|
||||
// Reference: https://github.com/golang/go/blob/b115207baf6c2decc3820ada4574ef4e5ad940ec/src/cmd/internal/obj/util.go#L260-L265
|
||||
//
|
||||
// case TYPE_TEXTSIZE:
|
||||
// if a.Val.(int32) == objabi.ArgsSizeUnknown {
|
||||
// str = fmt.Sprintf("$%d", a.Offset)
|
||||
// } else {
|
||||
// str = fmt.Sprintf("$%d-%d", a.Offset, a.Val.(int32))
|
||||
// }
|
||||
//
|
||||
s := "$" + strconv.Itoa(f.FrameBytes())
|
||||
if argsize := f.ArgumentBytes(); argsize > 0 {
|
||||
return s + "-" + strconv.Itoa(argsize)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func joinOperands(operands []operand.Op) string {
|
||||
asm := make([]string, len(operands))
|
||||
for i, op := range operands {
|
||||
asm[i] = op.Asm()
|
||||
}
|
||||
return strings.Join(asm, ", ")
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
// Package printer implements printing of avo files in various formats.
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mmcloughlin/avo/internal/stack"
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
)
|
||||
|
||||
// Printer can produce output for an avo File.
|
||||
type Printer interface {
|
||||
Print(*ir.File) ([]byte, error)
|
||||
}
|
||||
|
||||
// Builder can construct a printer.
|
||||
type Builder func(Config) Printer
|
||||
|
||||
// Config represents general printing configuration.
|
||||
type Config struct {
|
||||
// Command-line arguments passed to the generator. If provided, this will be
|
||||
// included in a code generation warning.
|
||||
Argv []string
|
||||
|
||||
// Name of the code generator.
|
||||
Name string
|
||||
|
||||
// Name of Go package the generated code will belong to.
|
||||
Pkg string
|
||||
}
|
||||
|
||||
// NewDefaultConfig produces a config with Name "avo".
|
||||
// The package name is guessed from the current directory.
|
||||
func NewDefaultConfig() Config {
|
||||
return Config{
|
||||
Name: "avo",
|
||||
Pkg: pkg(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewArgvConfig constructs a Config from os.Args.
|
||||
// The package name is guessed from the current directory.
|
||||
func NewArgvConfig() Config {
|
||||
return Config{
|
||||
Argv: os.Args,
|
||||
Pkg: pkg(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewGoRunConfig produces a Config for a generator that's expected to be
|
||||
// executed via "go run ...".
|
||||
func NewGoRunConfig() Config {
|
||||
path := mainfile()
|
||||
if path == "" {
|
||||
return NewDefaultConfig()
|
||||
}
|
||||
argv := []string{"go", "run", filepath.Base(path)}
|
||||
if len(os.Args) > 1 {
|
||||
argv = append(argv, os.Args[1:]...)
|
||||
}
|
||||
return Config{
|
||||
Argv: argv,
|
||||
Pkg: pkg(),
|
||||
}
|
||||
}
|
||||
|
||||
// GeneratedBy returns a description of the code generator.
|
||||
func (c Config) GeneratedBy() string {
|
||||
if c.Argv == nil {
|
||||
return c.Name
|
||||
}
|
||||
return fmt.Sprintf("command: %s", strings.Join(c.Argv, " "))
|
||||
}
|
||||
|
||||
// GeneratedWarning returns text for a code generation warning. Conforms to https://golang.org/s/generatedcode.
|
||||
func (c Config) GeneratedWarning() string {
|
||||
return fmt.Sprintf("Code generated by %s. DO NOT EDIT.", c.GeneratedBy())
|
||||
}
|
||||
|
||||
// mainfile attempts to determine the file path of the main function by
|
||||
// inspecting the stack. Returns empty string on failure.
|
||||
func mainfile() string {
|
||||
if m := stack.Main(); m != nil {
|
||||
return m.File
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// pkg guesses the name of the package from the working directory.
|
||||
func pkg() string {
|
||||
if cwd, err := os.Getwd(); err == nil {
|
||||
return filepath.Base(cwd)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"github.com/mmcloughlin/avo/internal/prnt"
|
||||
"github.com/mmcloughlin/avo/ir"
|
||||
)
|
||||
|
||||
type stubs struct {
|
||||
cfg Config
|
||||
prnt.Generator
|
||||
}
|
||||
|
||||
// NewStubs constructs a printer for writing stub function declarations.
|
||||
func NewStubs(cfg Config) Printer {
|
||||
return &stubs{cfg: cfg}
|
||||
}
|
||||
|
||||
func (s *stubs) Print(f *ir.File) ([]byte, error) {
|
||||
s.Comment(s.cfg.GeneratedWarning())
|
||||
|
||||
if len(f.Constraints) > 0 {
|
||||
s.NL()
|
||||
s.Printf(f.Constraints.GoString())
|
||||
}
|
||||
|
||||
s.NL()
|
||||
s.Printf("package %s\n", s.cfg.Pkg)
|
||||
for _, fn := range f.Functions() {
|
||||
s.NL()
|
||||
s.Comment(fn.Doc...)
|
||||
for _, pragma := range fn.Pragmas {
|
||||
s.pragma(pragma)
|
||||
}
|
||||
s.Printf("%s\n", fn.Stub())
|
||||
}
|
||||
return s.Result()
|
||||
}
|
||||
|
||||
func (s *stubs) pragma(p ir.Pragma) {
|
||||
s.Printf("//go:%s", p.Directive)
|
||||
for _, arg := range p.Arguments {
|
||||
s.Printf(" %s", arg)
|
||||
}
|
||||
s.NL()
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package reg
|
||||
|
||||
// Collection represents a collection of virtual registers. This is primarily
|
||||
// useful for allocating virtual registers with distinct IDs.
|
||||
type Collection struct {
|
||||
idx map[Kind]Index
|
||||
}
|
||||
|
||||
// NewCollection builds an empty register collection.
|
||||
func NewCollection() *Collection {
|
||||
return &Collection{
|
||||
idx: map[Kind]Index{},
|
||||
}
|
||||
}
|
||||
|
||||
// VirtualRegister allocates and returns a new virtual register of the given kind and width.
|
||||
func (c *Collection) VirtualRegister(k Kind, s Spec) Virtual {
|
||||
idx := c.idx[k]
|
||||
c.idx[k]++
|
||||
return NewVirtual(idx, k, s)
|
||||
}
|
||||
|
||||
// GP8L allocates and returns a general-purpose 8-bit register (low byte).
|
||||
func (c *Collection) GP8L() GPVirtual { return c.GP(S8L) }
|
||||
|
||||
// GP8H allocates and returns a general-purpose 8-bit register (high byte).
|
||||
func (c *Collection) GP8H() GPVirtual { return c.GP(S8H) }
|
||||
|
||||
// GP8 allocates and returns a general-purpose 8-bit register (low byte).
|
||||
func (c *Collection) GP8() GPVirtual { return c.GP8L() }
|
||||
|
||||
// GP16 allocates and returns a general-purpose 16-bit register.
|
||||
func (c *Collection) GP16() GPVirtual { return c.GP(S16) }
|
||||
|
||||
// GP32 allocates and returns a general-purpose 32-bit register.
|
||||
func (c *Collection) GP32() GPVirtual { return c.GP(S32) }
|
||||
|
||||
// GP64 allocates and returns a general-purpose 64-bit register.
|
||||
func (c *Collection) GP64() GPVirtual { return c.GP(S64) }
|
||||
|
||||
// GP allocates and returns a general-purpose register of the given width.
|
||||
func (c *Collection) GP(s Spec) GPVirtual { return newgpv(c.VirtualRegister(KindGP, s)) }
|
||||
|
||||
// XMM allocates and returns a 128-bit vector register.
|
||||
func (c *Collection) XMM() VecVirtual { return c.Vec(S128) }
|
||||
|
||||
// YMM allocates and returns a 256-bit vector register.
|
||||
func (c *Collection) YMM() VecVirtual { return c.Vec(S256) }
|
||||
|
||||
// ZMM allocates and returns a 512-bit vector register.
|
||||
func (c *Collection) ZMM() VecVirtual { return c.Vec(S512) }
|
||||
|
||||
// Vec allocates and returns a vector register of the given width.
|
||||
func (c *Collection) Vec(s Spec) VecVirtual { return newvecv(c.VirtualRegister(KindVector, s)) }
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package reg provides types for physical and virtual registers, and definitions of x86-64 register families.
|
||||
package reg
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package reg
|
||||
|
||||
// MaskSet maps register IDs to masks.
|
||||
type MaskSet map[ID]uint16
|
||||
|
||||
// NewEmptyMaskSet builds an empty register mask set.
|
||||
func NewEmptyMaskSet() MaskSet {
|
||||
return MaskSet{}
|
||||
}
|
||||
|
||||
// NewMaskSetFromRegisters forms a mask set from the given register list.
|
||||
func NewMaskSetFromRegisters(rs []Register) MaskSet {
|
||||
s := NewEmptyMaskSet()
|
||||
for _, r := range rs {
|
||||
s.AddRegister(r)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Clone returns a copy of s.
|
||||
func (s MaskSet) Clone() MaskSet {
|
||||
c := NewEmptyMaskSet()
|
||||
for id, mask := range s {
|
||||
c.Add(id, mask)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Add mask to the given register ID.
|
||||
// Reports whether this made any change to the set.
|
||||
func (s MaskSet) Add(id ID, mask uint16) bool {
|
||||
if (s[id] & mask) == mask {
|
||||
return false
|
||||
}
|
||||
s[id] |= mask
|
||||
return true
|
||||
}
|
||||
|
||||
// AddRegister is a convenience for adding the register's (ID, mask) to the set.
|
||||
// Reports whether this made any change to the set.
|
||||
func (s MaskSet) AddRegister(r Register) bool {
|
||||
return s.Add(r.ID(), r.Mask())
|
||||
}
|
||||
|
||||
// Discard clears masked bits from register ID.
|
||||
// Reports whether this made any change to the set.
|
||||
func (s MaskSet) Discard(id ID, mask uint16) bool {
|
||||
if curr, found := s[id]; !found || (curr&mask) == 0 {
|
||||
return false
|
||||
}
|
||||
s[id] &^= mask
|
||||
if s[id] == 0 {
|
||||
delete(s, id)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// DiscardRegister is a convenience for discarding the register's (ID, mask) from the set.
|
||||
// Reports whether this made any change to the set.
|
||||
func (s MaskSet) DiscardRegister(r Register) bool {
|
||||
return s.Discard(r.ID(), r.Mask())
|
||||
}
|
||||
|
||||
// Update adds masks in t to s.
|
||||
// Reports whether this made any change to the set.
|
||||
func (s MaskSet) Update(t MaskSet) bool {
|
||||
change := false
|
||||
for id, mask := range t {
|
||||
change = s.Add(id, mask) || change
|
||||
}
|
||||
return change
|
||||
}
|
||||
|
||||
// Difference returns the set of registers in s but not t.
|
||||
func (s MaskSet) Difference(t MaskSet) MaskSet {
|
||||
d := s.Clone()
|
||||
d.DifferenceUpdate(t)
|
||||
return d
|
||||
}
|
||||
|
||||
// DifferenceUpdate removes every element of t from s.
|
||||
func (s MaskSet) DifferenceUpdate(t MaskSet) bool {
|
||||
change := false
|
||||
for id, mask := range t {
|
||||
change = s.Discard(id, mask) || change
|
||||
}
|
||||
return change
|
||||
}
|
||||
|
||||
// Equals returns true if s and t contain the same masks.
|
||||
func (s MaskSet) Equals(t MaskSet) bool {
|
||||
if len(s) != len(t) {
|
||||
return false
|
||||
}
|
||||
for id, mask := range s {
|
||||
if _, found := t[id]; !found || mask != t[id] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// OfKind returns the set of elements of s with kind k.
|
||||
func (s MaskSet) OfKind(k Kind) MaskSet {
|
||||
t := NewEmptyMaskSet()
|
||||
for id, mask := range s {
|
||||
if id.Kind() == k {
|
||||
t.Add(id, mask)
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
package reg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Kind is a class of registers.
|
||||
type Kind uint8
|
||||
|
||||
// Index of a register within a kind.
|
||||
type Index uint16
|
||||
|
||||
// Family is a collection of Physical registers of a common kind.
|
||||
type Family struct {
|
||||
Kind Kind
|
||||
registers []Physical
|
||||
}
|
||||
|
||||
// define builds a register and adds it to the Family.
|
||||
func (f *Family) define(s Spec, idx Index, name string, flags ...Info) Physical {
|
||||
r := newregister(f, s, idx, name, flags...)
|
||||
f.add(r)
|
||||
return r
|
||||
}
|
||||
|
||||
// add r to the family.
|
||||
func (f *Family) add(r Physical) {
|
||||
if r.Kind() != f.Kind {
|
||||
panic("bad kind")
|
||||
}
|
||||
f.registers = append(f.registers, r)
|
||||
}
|
||||
|
||||
// Virtual returns a virtual register from this family's kind.
|
||||
func (f *Family) Virtual(idx Index, s Spec) Virtual {
|
||||
return NewVirtual(idx, f.Kind, s)
|
||||
}
|
||||
|
||||
// Registers returns the registers in this family.
|
||||
func (f *Family) Registers() []Physical {
|
||||
return append([]Physical(nil), f.registers...)
|
||||
}
|
||||
|
||||
// Lookup returns the register with given physical index and spec. Returns nil if no such register exists.
|
||||
func (f *Family) Lookup(idx Index, s Spec) Physical {
|
||||
for _, r := range f.registers {
|
||||
if r.PhysicalIndex() == idx && r.Mask() == s.Mask() {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID is a register identifier.
|
||||
type ID uint32
|
||||
|
||||
// newid builds a new register ID from the virtual flag v, kind and index.
|
||||
func newid(v uint8, kind Kind, idx Index) ID {
|
||||
return ID(v) | (ID(kind) << 8) | (ID(idx) << 16)
|
||||
}
|
||||
|
||||
// IsVirtual reports whether this is an ID for a virtual register.
|
||||
func (id ID) IsVirtual() bool { return (id & 1) == 1 }
|
||||
|
||||
// IsPhysical reports whether this is an ID for a physical register.
|
||||
func (id ID) IsPhysical() bool { return !id.IsVirtual() }
|
||||
|
||||
// Kind extracts the kind from the register ID.
|
||||
func (id ID) Kind() Kind { return Kind(id >> 8) }
|
||||
|
||||
// Index extracts the index from the register ID.
|
||||
func (id ID) Index() Index { return Index(id >> 16) }
|
||||
|
||||
// Register represents a virtual or physical register.
|
||||
type Register interface {
|
||||
ID() ID
|
||||
Kind() Kind
|
||||
Size() uint
|
||||
Mask() uint16
|
||||
Asm() string
|
||||
as(Spec) Register
|
||||
spec() Spec
|
||||
register()
|
||||
}
|
||||
|
||||
// Equal reports whether a and b are equal registers.
|
||||
func Equal(a, b Register) bool {
|
||||
return (a.ID() == b.ID()) && (a.Mask() == b.Mask())
|
||||
}
|
||||
|
||||
// Virtual is a register of a given type and size, not yet allocated to a physical register.
|
||||
type Virtual interface {
|
||||
VirtualIndex() Index
|
||||
Register
|
||||
}
|
||||
|
||||
// ToVirtual converts r to Virtual if possible, otherwise returns nil.
|
||||
func ToVirtual(r Register) Virtual {
|
||||
if v, ok := r.(Virtual); ok {
|
||||
return v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type virtual struct {
|
||||
idx Index
|
||||
kind Kind
|
||||
Spec
|
||||
}
|
||||
|
||||
// NewVirtual builds a Virtual register.
|
||||
func NewVirtual(idx Index, k Kind, s Spec) Virtual {
|
||||
return virtual{
|
||||
idx: idx,
|
||||
kind: k,
|
||||
Spec: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (v virtual) ID() ID { return newid(1, v.kind, v.idx) }
|
||||
func (v virtual) VirtualIndex() Index { return v.idx }
|
||||
func (v virtual) Kind() Kind { return v.kind }
|
||||
|
||||
func (v virtual) Asm() string {
|
||||
// TODO(mbm): decide on virtual register syntax
|
||||
return fmt.Sprintf("<virtual:%v:%v:%v>", v.idx, v.Kind(), v.Size())
|
||||
}
|
||||
|
||||
func (v virtual) as(s Spec) Register {
|
||||
return virtual{
|
||||
idx: v.idx,
|
||||
kind: v.kind,
|
||||
Spec: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (v virtual) spec() Spec { return v.Spec }
|
||||
func (v virtual) register() {}
|
||||
|
||||
// Info is a bitmask of register properties.
|
||||
type Info uint8
|
||||
|
||||
// Defined register Info flags.
|
||||
const (
|
||||
None Info = 0
|
||||
Restricted Info = 1 << iota
|
||||
)
|
||||
|
||||
// Physical is a concrete register.
|
||||
type Physical interface {
|
||||
PhysicalIndex() Index
|
||||
Info() Info
|
||||
Register
|
||||
}
|
||||
|
||||
// ToPhysical converts r to Physical if possible, otherwise returns nil.
|
||||
func ToPhysical(r Register) Physical {
|
||||
if p, ok := r.(Physical); ok {
|
||||
return p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// register implements Physical.
|
||||
type register struct {
|
||||
family *Family
|
||||
idx Index
|
||||
name string
|
||||
info Info
|
||||
Spec
|
||||
}
|
||||
|
||||
func newregister(f *Family, s Spec, idx Index, name string, flags ...Info) register {
|
||||
r := register{
|
||||
family: f,
|
||||
idx: idx,
|
||||
name: name,
|
||||
info: None,
|
||||
Spec: s,
|
||||
}
|
||||
for _, flag := range flags {
|
||||
r.info |= flag
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r register) ID() ID { return newid(0, r.Kind(), r.idx) }
|
||||
func (r register) PhysicalIndex() Index { return r.idx }
|
||||
func (r register) Kind() Kind { return r.family.Kind }
|
||||
func (r register) Asm() string { return r.name }
|
||||
func (r register) Info() Info { return r.info }
|
||||
|
||||
func (r register) as(s Spec) Register {
|
||||
return r.family.Lookup(r.PhysicalIndex(), s)
|
||||
}
|
||||
|
||||
func (r register) spec() Spec { return r.Spec }
|
||||
func (r register) register() {}
|
||||
|
||||
// Spec defines the size of a register as well as the bit ranges it occupies in
|
||||
// an underlying physical register.
|
||||
type Spec uint16
|
||||
|
||||
// Spec values required for x86-64.
|
||||
const (
|
||||
S0 Spec = 0x0 // zero value reserved for pseudo registers
|
||||
S8L Spec = 0x1
|
||||
S8H Spec = 0x2
|
||||
S8 = S8L
|
||||
S16 Spec = 0x3
|
||||
S32 Spec = 0x7
|
||||
S64 Spec = 0xf
|
||||
S128 Spec = 0x1f
|
||||
S256 Spec = 0x3f
|
||||
S512 Spec = 0x7f
|
||||
)
|
||||
|
||||
// Mask returns a mask representing which bytes of an underlying register are
|
||||
// used by this register. This is almost always the low bytes, except for the
|
||||
// case of the high-byte registers. If bit n of the mask is set, this means
|
||||
// bytes 2^(n-1) to 2^n-1 are used.
|
||||
func (s Spec) Mask() uint16 {
|
||||
return uint16(s)
|
||||
}
|
||||
|
||||
// Size returns the register width in bytes.
|
||||
func (s Spec) Size() uint {
|
||||
x := uint(s)
|
||||
return (x >> 1) + (x & 1)
|
||||
}
|
||||
|
||||
// LookupPhysical returns the physical register with the given parameters, or nil if not found.
|
||||
func LookupPhysical(k Kind, idx Index, s Spec) Physical {
|
||||
f := FamilyOfKind(k)
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
return f.Lookup(idx, s)
|
||||
}
|
||||
|
||||
// LookupID returns the physical register with the given id and spec, or nil if not found.
|
||||
func LookupID(id ID, s Spec) Physical {
|
||||
if id.IsVirtual() {
|
||||
return nil
|
||||
}
|
||||
return LookupPhysical(id.Kind(), id.Index(), s)
|
||||
}
|
||||
|
||||
// Allocation records a register allocation.
|
||||
type Allocation map[ID]ID
|
||||
|
||||
// NewEmptyAllocation builds an empty register allocation.
|
||||
func NewEmptyAllocation() Allocation {
|
||||
return Allocation{}
|
||||
}
|
||||
|
||||
// Merge allocations from b into a. Errors if there is disagreement on a common
|
||||
// register.
|
||||
func (a Allocation) Merge(b Allocation) error {
|
||||
for id, p := range b {
|
||||
if alt, found := a[id]; found && alt != p {
|
||||
return errors.New("disagreement on overlapping register")
|
||||
}
|
||||
a[id] = p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookupDefault returns the register ID assigned by this allocation, returning
|
||||
// id if none is found.
|
||||
func (a Allocation) LookupDefault(id ID) ID {
|
||||
if _, found := a[id]; found {
|
||||
return a[id]
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// LookupRegister the allocation for register r, or return nil if there is none.
|
||||
func (a Allocation) LookupRegister(r Register) Physical {
|
||||
// Return immediately if it is already a physical register.
|
||||
if p := ToPhysical(r); p != nil {
|
||||
return p
|
||||
}
|
||||
|
||||
// Lookup an allocation for this virtual ID.
|
||||
id, found := a[r.ID()]
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
return LookupID(id, r.spec())
|
||||
}
|
||||
|
||||
// LookupRegisterDefault returns the register assigned to r, or r itself if there is none.
|
||||
func (a Allocation) LookupRegisterDefault(r Register) Register {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
if p := a.LookupRegister(r); p != nil {
|
||||
return p
|
||||
}
|
||||
return r
|
||||
}
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
package reg
|
||||
|
||||
// Register kinds.
|
||||
const (
|
||||
KindPseudo Kind = iota
|
||||
KindGP
|
||||
KindVector
|
||||
)
|
||||
|
||||
// Declare register families.
|
||||
var (
|
||||
Pseudo = &Family{Kind: KindPseudo}
|
||||
GeneralPurpose = &Family{Kind: KindGP}
|
||||
Vector = &Family{Kind: KindVector}
|
||||
|
||||
Families = []*Family{
|
||||
Pseudo,
|
||||
GeneralPurpose,
|
||||
Vector,
|
||||
}
|
||||
)
|
||||
|
||||
var familiesByKind = map[Kind]*Family{}
|
||||
|
||||
func init() {
|
||||
for _, f := range Families {
|
||||
familiesByKind[f.Kind] = f
|
||||
}
|
||||
}
|
||||
|
||||
// FamilyOfKind returns the Family of registers of the given kind, or nil if not found.
|
||||
func FamilyOfKind(k Kind) *Family {
|
||||
return familiesByKind[k]
|
||||
}
|
||||
|
||||
// Pseudo registers.
|
||||
var (
|
||||
FramePointer = Pseudo.define(S0, 0, "FP")
|
||||
ProgramCounter = Pseudo.define(S0, 0, "PC")
|
||||
StaticBase = Pseudo.define(S0, 0, "SB")
|
||||
StackPointer = Pseudo.define(S0, 0, "SP")
|
||||
)
|
||||
|
||||
// GP provides additional methods for general purpose registers.
|
||||
type GP interface {
|
||||
As8() Register
|
||||
As8L() Register
|
||||
As8H() Register
|
||||
As16() Register
|
||||
As32() Register
|
||||
As64() Register
|
||||
}
|
||||
|
||||
// GPPhysical is a general-purpose physical register.
|
||||
type GPPhysical interface {
|
||||
Physical
|
||||
GP
|
||||
}
|
||||
|
||||
type gpp struct {
|
||||
Physical
|
||||
}
|
||||
|
||||
func newgpp(r Physical) GPPhysical { return gpp{Physical: r} }
|
||||
|
||||
func (p gpp) As8() Register { return newgpp(p.as(S8).(Physical)) }
|
||||
func (p gpp) As8L() Register { return newgpp(p.as(S8L).(Physical)) }
|
||||
func (p gpp) As8H() Register { return newgpp(p.as(S8H).(Physical)) }
|
||||
func (p gpp) As16() Register { return newgpp(p.as(S16).(Physical)) }
|
||||
func (p gpp) As32() Register { return newgpp(p.as(S32).(Physical)) }
|
||||
func (p gpp) As64() Register { return newgpp(p.as(S64).(Physical)) }
|
||||
|
||||
// GPVirtual is a general-purpose virtual register.
|
||||
type GPVirtual interface {
|
||||
Virtual
|
||||
GP
|
||||
}
|
||||
|
||||
type gpv struct {
|
||||
Virtual
|
||||
}
|
||||
|
||||
func newgpv(v Virtual) GPVirtual { return gpv{Virtual: v} }
|
||||
|
||||
func (v gpv) As8() Register { return newgpv(v.as(S8).(Virtual)) }
|
||||
func (v gpv) As8L() Register { return newgpv(v.as(S8L).(Virtual)) }
|
||||
func (v gpv) As8H() Register { return newgpv(v.as(S8H).(Virtual)) }
|
||||
func (v gpv) As16() Register { return newgpv(v.as(S16).(Virtual)) }
|
||||
func (v gpv) As32() Register { return newgpv(v.as(S32).(Virtual)) }
|
||||
func (v gpv) As64() Register { return newgpv(v.as(S64).(Virtual)) }
|
||||
|
||||
func gp(s Spec, id Index, name string, flags ...Info) GPPhysical {
|
||||
r := newgpp(newregister(GeneralPurpose, s, id, name, flags...))
|
||||
GeneralPurpose.add(r)
|
||||
return r
|
||||
}
|
||||
|
||||
// General purpose registers.
|
||||
var (
|
||||
// Low byte
|
||||
AL = gp(S8L, 0, "AL")
|
||||
CL = gp(S8L, 1, "CL")
|
||||
DL = gp(S8L, 2, "DL")
|
||||
BL = gp(S8L, 3, "BL")
|
||||
|
||||
// High byte
|
||||
AH = gp(S8H, 0, "AH")
|
||||
CH = gp(S8H, 1, "CH")
|
||||
DH = gp(S8H, 2, "DH")
|
||||
BH = gp(S8H, 3, "BH")
|
||||
|
||||
// 8-bit
|
||||
SPB = gp(S8, 4, "SP", Restricted)
|
||||
BPB = gp(S8, 5, "BP")
|
||||
SIB = gp(S8, 6, "SI")
|
||||
DIB = gp(S8, 7, "DI")
|
||||
R8B = gp(S8, 8, "R8")
|
||||
R9B = gp(S8, 9, "R9")
|
||||
R10B = gp(S8, 10, "R10")
|
||||
R11B = gp(S8, 11, "R11")
|
||||
R12B = gp(S8, 12, "R12")
|
||||
R13B = gp(S8, 13, "R13")
|
||||
R14B = gp(S8, 14, "R14")
|
||||
R15B = gp(S8, 15, "R15")
|
||||
|
||||
// 16-bit
|
||||
AX = gp(S16, 0, "AX")
|
||||
CX = gp(S16, 1, "CX")
|
||||
DX = gp(S16, 2, "DX")
|
||||
BX = gp(S16, 3, "BX")
|
||||
SP = gp(S16, 4, "SP", Restricted)
|
||||
BP = gp(S16, 5, "BP")
|
||||
SI = gp(S16, 6, "SI")
|
||||
DI = gp(S16, 7, "DI")
|
||||
R8W = gp(S16, 8, "R8")
|
||||
R9W = gp(S16, 9, "R9")
|
||||
R10W = gp(S16, 10, "R10")
|
||||
R11W = gp(S16, 11, "R11")
|
||||
R12W = gp(S16, 12, "R12")
|
||||
R13W = gp(S16, 13, "R13")
|
||||
R14W = gp(S16, 14, "R14")
|
||||
R15W = gp(S16, 15, "R15")
|
||||
|
||||
// 32-bit
|
||||
EAX = gp(S32, 0, "AX")
|
||||
ECX = gp(S32, 1, "CX")
|
||||
EDX = gp(S32, 2, "DX")
|
||||
EBX = gp(S32, 3, "BX")
|
||||
ESP = gp(S32, 4, "SP", Restricted)
|
||||
EBP = gp(S32, 5, "BP")
|
||||
ESI = gp(S32, 6, "SI")
|
||||
EDI = gp(S32, 7, "DI")
|
||||
R8L = gp(S32, 8, "R8")
|
||||
R9L = gp(S32, 9, "R9")
|
||||
R10L = gp(S32, 10, "R10")
|
||||
R11L = gp(S32, 11, "R11")
|
||||
R12L = gp(S32, 12, "R12")
|
||||
R13L = gp(S32, 13, "R13")
|
||||
R14L = gp(S32, 14, "R14")
|
||||
R15L = gp(S32, 15, "R15")
|
||||
|
||||
// 64-bit
|
||||
RAX = gp(S64, 0, "AX")
|
||||
RCX = gp(S64, 1, "CX")
|
||||
RDX = gp(S64, 2, "DX")
|
||||
RBX = gp(S64, 3, "BX")
|
||||
RSP = gp(S64, 4, "SP", Restricted)
|
||||
RBP = gp(S64, 5, "BP")
|
||||
RSI = gp(S64, 6, "SI")
|
||||
RDI = gp(S64, 7, "DI")
|
||||
R8 = gp(S64, 8, "R8")
|
||||
R9 = gp(S64, 9, "R9")
|
||||
R10 = gp(S64, 10, "R10")
|
||||
R11 = gp(S64, 11, "R11")
|
||||
R12 = gp(S64, 12, "R12")
|
||||
R13 = gp(S64, 13, "R13")
|
||||
R14 = gp(S64, 14, "R14")
|
||||
R15 = gp(S64, 15, "R15")
|
||||
)
|
||||
|
||||
// Vec provides methods for vector registers.
|
||||
type Vec interface {
|
||||
AsX() Register
|
||||
AsY() Register
|
||||
AsZ() Register
|
||||
}
|
||||
|
||||
// VecPhysical is a physical vector register.
|
||||
type VecPhysical interface {
|
||||
Physical
|
||||
Vec
|
||||
}
|
||||
|
||||
type vecp struct {
|
||||
Physical
|
||||
Vec
|
||||
}
|
||||
|
||||
func newvecp(r Physical) VecPhysical { return vecp{Physical: r} }
|
||||
|
||||
func (p vecp) AsX() Register { return newvecp(p.as(S128).(Physical)) }
|
||||
func (p vecp) AsY() Register { return newvecp(p.as(S256).(Physical)) }
|
||||
func (p vecp) AsZ() Register { return newvecp(p.as(S512).(Physical)) }
|
||||
|
||||
// VecVirtual is a virtual vector register.
|
||||
type VecVirtual interface {
|
||||
Virtual
|
||||
Vec
|
||||
}
|
||||
|
||||
type vecv struct {
|
||||
Virtual
|
||||
Vec
|
||||
}
|
||||
|
||||
func newvecv(v Virtual) VecVirtual { return vecv{Virtual: v} }
|
||||
|
||||
func (v vecv) AsX() Register { return newvecv(v.as(S128).(Virtual)) }
|
||||
func (v vecv) AsY() Register { return newvecv(v.as(S256).(Virtual)) }
|
||||
func (v vecv) AsZ() Register { return newvecv(v.as(S512).(Virtual)) }
|
||||
|
||||
func vec(s Spec, id Index, name string, flags ...Info) VecPhysical {
|
||||
r := newvecp(newregister(Vector, s, id, name, flags...))
|
||||
Vector.add(r)
|
||||
return r
|
||||
}
|
||||
|
||||
// Vector registers.
|
||||
var (
|
||||
// 128-bit
|
||||
X0 = vec(S128, 0, "X0")
|
||||
X1 = vec(S128, 1, "X1")
|
||||
X2 = vec(S128, 2, "X2")
|
||||
X3 = vec(S128, 3, "X3")
|
||||
X4 = vec(S128, 4, "X4")
|
||||
X5 = vec(S128, 5, "X5")
|
||||
X6 = vec(S128, 6, "X6")
|
||||
X7 = vec(S128, 7, "X7")
|
||||
X8 = vec(S128, 8, "X8")
|
||||
X9 = vec(S128, 9, "X9")
|
||||
X10 = vec(S128, 10, "X10")
|
||||
X11 = vec(S128, 11, "X11")
|
||||
X12 = vec(S128, 12, "X12")
|
||||
X13 = vec(S128, 13, "X13")
|
||||
X14 = vec(S128, 14, "X14")
|
||||
X15 = vec(S128, 15, "X15")
|
||||
X16 = vec(S128, 16, "X16")
|
||||
X17 = vec(S128, 17, "X17")
|
||||
X18 = vec(S128, 18, "X18")
|
||||
X19 = vec(S128, 19, "X19")
|
||||
X20 = vec(S128, 20, "X20")
|
||||
X21 = vec(S128, 21, "X21")
|
||||
X22 = vec(S128, 22, "X22")
|
||||
X23 = vec(S128, 23, "X23")
|
||||
X24 = vec(S128, 24, "X24")
|
||||
X25 = vec(S128, 25, "X25")
|
||||
X26 = vec(S128, 26, "X26")
|
||||
X27 = vec(S128, 27, "X27")
|
||||
X28 = vec(S128, 28, "X28")
|
||||
X29 = vec(S128, 29, "X29")
|
||||
X30 = vec(S128, 30, "X30")
|
||||
X31 = vec(S128, 31, "X31")
|
||||
|
||||
// 256-bit
|
||||
Y0 = vec(S256, 0, "Y0")
|
||||
Y1 = vec(S256, 1, "Y1")
|
||||
Y2 = vec(S256, 2, "Y2")
|
||||
Y3 = vec(S256, 3, "Y3")
|
||||
Y4 = vec(S256, 4, "Y4")
|
||||
Y5 = vec(S256, 5, "Y5")
|
||||
Y6 = vec(S256, 6, "Y6")
|
||||
Y7 = vec(S256, 7, "Y7")
|
||||
Y8 = vec(S256, 8, "Y8")
|
||||
Y9 = vec(S256, 9, "Y9")
|
||||
Y10 = vec(S256, 10, "Y10")
|
||||
Y11 = vec(S256, 11, "Y11")
|
||||
Y12 = vec(S256, 12, "Y12")
|
||||
Y13 = vec(S256, 13, "Y13")
|
||||
Y14 = vec(S256, 14, "Y14")
|
||||
Y15 = vec(S256, 15, "Y15")
|
||||
Y16 = vec(S256, 16, "Y16")
|
||||
Y17 = vec(S256, 17, "Y17")
|
||||
Y18 = vec(S256, 18, "Y18")
|
||||
Y19 = vec(S256, 19, "Y19")
|
||||
Y20 = vec(S256, 20, "Y20")
|
||||
Y21 = vec(S256, 21, "Y21")
|
||||
Y22 = vec(S256, 22, "Y22")
|
||||
Y23 = vec(S256, 23, "Y23")
|
||||
Y24 = vec(S256, 24, "Y24")
|
||||
Y25 = vec(S256, 25, "Y25")
|
||||
Y26 = vec(S256, 26, "Y26")
|
||||
Y27 = vec(S256, 27, "Y27")
|
||||
Y28 = vec(S256, 28, "Y28")
|
||||
Y29 = vec(S256, 29, "Y29")
|
||||
Y30 = vec(S256, 30, "Y30")
|
||||
Y31 = vec(S256, 31, "Y31")
|
||||
|
||||
// 512-bit
|
||||
Z0 = vec(S512, 0, "Z0")
|
||||
Z1 = vec(S512, 1, "Z1")
|
||||
Z2 = vec(S512, 2, "Z2")
|
||||
Z3 = vec(S512, 3, "Z3")
|
||||
Z4 = vec(S512, 4, "Z4")
|
||||
Z5 = vec(S512, 5, "Z5")
|
||||
Z6 = vec(S512, 6, "Z6")
|
||||
Z7 = vec(S512, 7, "Z7")
|
||||
Z8 = vec(S512, 8, "Z8")
|
||||
Z9 = vec(S512, 9, "Z9")
|
||||
Z10 = vec(S512, 10, "Z10")
|
||||
Z11 = vec(S512, 11, "Z11")
|
||||
Z12 = vec(S512, 12, "Z12")
|
||||
Z13 = vec(S512, 13, "Z13")
|
||||
Z14 = vec(S512, 14, "Z14")
|
||||
Z15 = vec(S512, 15, "Z15")
|
||||
Z16 = vec(S512, 16, "Z16")
|
||||
Z17 = vec(S512, 17, "Z17")
|
||||
Z18 = vec(S512, 18, "Z18")
|
||||
Z19 = vec(S512, 19, "Z19")
|
||||
Z20 = vec(S512, 20, "Z20")
|
||||
Z21 = vec(S512, 21, "Z21")
|
||||
Z22 = vec(S512, 22, "Z22")
|
||||
Z23 = vec(S512, 23, "Z23")
|
||||
Z24 = vec(S512, 24, "Z24")
|
||||
Z25 = vec(S512, 25, "Z25")
|
||||
Z26 = vec(S512, 26, "Z26")
|
||||
Z27 = vec(S512, 27, "Z27")
|
||||
Z28 = vec(S512, 28, "Z28")
|
||||
Z29 = vec(S512, 29, "Z29")
|
||||
Z30 = vec(S512, 30, "Z30")
|
||||
Z31 = vec(S512, 31, "Z31")
|
||||
)
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
// Package src provides types for working with source files.
|
||||
package src
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Position represents a position in a source file.
|
||||
type Position struct {
|
||||
Filename string
|
||||
Line int // 1-up
|
||||
}
|
||||
|
||||
// FramePosition returns the Position of the given stack frame.
|
||||
func FramePosition(f runtime.Frame) Position {
|
||||
return Position{
|
||||
Filename: f.File,
|
||||
Line: f.Line,
|
||||
}
|
||||
}
|
||||
|
||||
// IsValid reports whether the position is valid: Line must be positive, but
|
||||
// Filename may be empty.
|
||||
func (p Position) IsValid() bool {
|
||||
return p.Line > 0
|
||||
}
|
||||
|
||||
// String represents Position as a string.
|
||||
func (p Position) String() string {
|
||||
if !p.IsValid() {
|
||||
return "-"
|
||||
}
|
||||
var s string
|
||||
if p.Filename != "" {
|
||||
s += p.Filename + ":"
|
||||
}
|
||||
s += strconv.Itoa(p.Line)
|
||||
return s
|
||||
}
|
||||
|
||||
// Rel returns Position relative to basepath. If the given filename cannot be
|
||||
// expressed relative to basepath the position will be returned unchanged.
|
||||
func (p Position) Rel(basepath string) Position {
|
||||
q := p
|
||||
if rel, err := filepath.Rel(basepath, q.Filename); err == nil {
|
||||
q.Filename = rel
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// Relwd returns Position relative to the current working directory. Returns p
|
||||
// unchanged if the working directory cannot be determined, or the filename
|
||||
// cannot be expressed relative to the working directory.
|
||||
func (p Position) Relwd() Position {
|
||||
if wd, err := os.Getwd(); err == nil {
|
||||
return p.Rel(wd)
|
||||
}
|
||||
return p
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package x86 provides constructors for all x86-64 instructions.
|
||||
package x86
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package x86
|
||||
|
||||
//go:generate avogen -output zctors.go ctors
|
||||
//go:generate avogen -output zctors_test.go ctorstest
|
||||
+34629
File diff suppressed because it is too large
Load Diff
+14
@@ -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
@@ -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
@@ -0,0 +1,82 @@
|
||||
[](http://godoc.org/github.com/tarm/serial)
|
||||
[](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
@@ -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
@@ -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
@@ -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
@@ -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(¶ms)), 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,3 +1,6 @@
|
||||
arch:
|
||||
- amd64
|
||||
- ppc64le
|
||||
language: go
|
||||
go:
|
||||
- 1.9.x
|
||||
|
||||
+2
-2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
Generated
Vendored
+2
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user