forked from mirror/ledisdb
use glide and refactor (#281)
This commit is contained in:
parent
380835a0ca
commit
3970f8e4f4
|
@ -1,7 +1,7 @@
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.5
|
|
||||||
- 1.6
|
- 1.6
|
||||||
|
- 1.7
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- make travis
|
- make test
|
||||||
|
|
55
Makefile
55
Makefile
|
@ -11,44 +11,39 @@ export LD_LIBRARY_PATH
|
||||||
export DYLD_LIBRARY_PATH
|
export DYLD_LIBRARY_PATH
|
||||||
export GO_BUILD_TAGS
|
export GO_BUILD_TAGS
|
||||||
|
|
||||||
GO=GO15VENDOREXPERIMENT="1" go
|
|
||||||
|
|
||||||
PKGS=$(shell $(GO) list ./... | grep -v "cmd")
|
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
$(GO) build -o bin/ledis-server -tags '$(GO_BUILD_TAGS)' cmd/ledis-server/*
|
rm -rf vendor && ln -s _vendor/vendor vendor
|
||||||
$(GO) build -o bin/ledis-cli -tags '$(GO_BUILD_TAGS)' cmd/ledis-cli/*
|
go build -o bin/ledis-benchmark -tags '$(GO_BUILD_TAGS)' cmd/ledis-benchmark/*
|
||||||
|
go build -o bin/ledis-dump -tags '$(GO_BUILD_TAGS)' cmd/ledis-dump/*
|
||||||
build_all: build
|
go build -o bin/ledis-load -tags '$(GO_BUILD_TAGS)' cmd/ledis-load/*
|
||||||
$(GO) build -o bin/ledis-benchmark -tags '$(GO_BUILD_TAGS)' cmd/ledis-benchmark/*
|
go build -o bin/ledis-repair -tags '$(GO_BUILD_TAGS)' cmd/ledis-repair/*
|
||||||
$(GO) build -o bin/ledis-dump -tags '$(GO_BUILD_TAGS)' cmd/ledis-dump/*
|
rm -rf vendor
|
||||||
$(GO) build -o bin/ledis-load -tags '$(GO_BUILD_TAGS)' cmd/ledis-load/*
|
|
||||||
$(GO) build -o bin/ledis-repair -tags '$(GO_BUILD_TAGS)' cmd/ledis-repair/*
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
# use vendor for test
|
rm -rf vendor && ln -s _vendor/vendor vendor
|
||||||
@rm -rf vendor && ln -s cmd/vendor vendor
|
go test --race -tags '$(GO_BUILD_TAGS)' -timeout 2m ./...
|
||||||
@$(GO) test --race -tags '$(GO_BUILD_TAGS)' $(PKGS)
|
rm -rf vendor
|
||||||
@rm -rf vendor
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(GO) clean -i ./...
|
go clean -i ./...
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
gofmt -w -s . 2>&1 | grep -vE 'vendor' | awk '{print} END{if(NR>0) {exit 1}}'
|
gofmt -w -s . 2>&1 | grep -vE 'vendor' | awk '{print} END{if(NR>0) {exit 1}}'
|
||||||
|
|
||||||
deps:
|
update_vendor:
|
||||||
# see https://github.com/coreos/etcd/blob/master/scripts/updatedep.sh
|
which glide >/dev/null || curl https://glide.sh/get | sh
|
||||||
rm -rf Godeps vendor cmd/vendor
|
which glide-vc || go get -v -u github.com/sgotti/glide-vc
|
||||||
mkdir -p cmd/vendor
|
rm -r vendor && mv _vendor/vendor vendor || true
|
||||||
ln -s cmd/vendor vendor
|
rm -rf _vendor
|
||||||
godep save ./...
|
ifdef PKG
|
||||||
rm -rf cmd/Godeps
|
glide get --strip-vendor --skip-test ${PKG}
|
||||||
rm vendor
|
else
|
||||||
mv Godeps cmd/
|
glide update --strip-vendor --skip-test
|
||||||
|
endif
|
||||||
travis:
|
@echo "removing test files"
|
||||||
@rm -rf vendor && ln -s cmd/vendor vendor
|
glide vc --only-code --no-tests
|
||||||
@$(GO) test --race -tags '$(GO_BUILD_TAGS)' $(PKGS)
|
mkdir -p _vendor
|
||||||
|
mv vendor _vendor/vendor
|
|
@ -34,9 +34,6 @@ Create a workspace and checkout ledisdb source
|
||||||
|
|
||||||
cd src/github.com/siddontang/ledisdb
|
cd src/github.com/siddontang/ledisdb
|
||||||
|
|
||||||
#install godep and be sure godep command can be found in $PATH
|
|
||||||
go get github.com/tools/godep
|
|
||||||
|
|
||||||
#set build and run environment
|
#set build and run environment
|
||||||
source dev.sh
|
source dev.sh
|
||||||
|
|
||||||
|
@ -180,7 +177,7 @@ See [Clients](https://github.com/siddontang/ledisdb/wiki/Clients) to find or con
|
||||||
|
|
||||||
## Requirement
|
## Requirement
|
||||||
|
|
||||||
+ Go version >= 1.5
|
+ Go version >= 1.6
|
||||||
|
|
||||||
## Related Repos
|
## Related Repos
|
||||||
|
|
||||||
|
|
|
@ -225,6 +225,9 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
||||||
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
||||||
tmap, ok := mapping.(map[string]interface{})
|
tmap, ok := mapping.(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
|
if mapping == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return mismatch(rv, "map", mapping)
|
return mismatch(rv, "map", mapping)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,6 +270,9 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
||||||
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
||||||
tmap, ok := mapping.(map[string]interface{})
|
tmap, ok := mapping.(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
|
if tmap == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return badtype("map", mapping)
|
return badtype("map", mapping)
|
||||||
}
|
}
|
||||||
if rv.IsNil() {
|
if rv.IsNil() {
|
||||||
|
@ -292,6 +298,9 @@ func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
||||||
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
||||||
datav := reflect.ValueOf(data)
|
datav := reflect.ValueOf(data)
|
||||||
if datav.Kind() != reflect.Slice {
|
if datav.Kind() != reflect.Slice {
|
||||||
|
if !datav.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return badtype("slice", data)
|
return badtype("slice", data)
|
||||||
}
|
}
|
||||||
sliceLen := datav.Len()
|
sliceLen := datav.Len()
|
||||||
|
@ -305,12 +314,16 @@ func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
||||||
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
||||||
datav := reflect.ValueOf(data)
|
datav := reflect.ValueOf(data)
|
||||||
if datav.Kind() != reflect.Slice {
|
if datav.Kind() != reflect.Slice {
|
||||||
|
if !datav.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return badtype("slice", data)
|
return badtype("slice", data)
|
||||||
}
|
}
|
||||||
sliceLen := datav.Len()
|
n := datav.Len()
|
||||||
if rv.IsNil() {
|
if rv.IsNil() || rv.Cap() < n {
|
||||||
rv.Set(reflect.MakeSlice(rv.Type(), sliceLen, sliceLen))
|
rv.Set(reflect.MakeSlice(rv.Type(), n, n))
|
||||||
}
|
}
|
||||||
|
rv.SetLen(n)
|
||||||
return md.unifySliceArray(datav, rv)
|
return md.unifySliceArray(datav, rv)
|
||||||
}
|
}
|
||||||
|
|
|
@ -306,19 +306,30 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
|
||||||
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
|
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
|
||||||
for i := 0; i < rt.NumField(); i++ {
|
for i := 0; i < rt.NumField(); i++ {
|
||||||
f := rt.Field(i)
|
f := rt.Field(i)
|
||||||
// skip unexporded fields
|
// skip unexported fields
|
||||||
if f.PkgPath != "" {
|
if f.PkgPath != "" && !f.Anonymous {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
frv := rv.Field(i)
|
frv := rv.Field(i)
|
||||||
if f.Anonymous {
|
if f.Anonymous {
|
||||||
frv := eindirect(frv)
|
t := f.Type
|
||||||
t := frv.Type()
|
switch t.Kind() {
|
||||||
if t.Kind() != reflect.Struct {
|
case reflect.Struct:
|
||||||
encPanic(errAnonNonStruct)
|
addFields(t, frv, f.Index)
|
||||||
|
continue
|
||||||
|
case reflect.Ptr:
|
||||||
|
if t.Elem().Kind() == reflect.Struct {
|
||||||
|
if !frv.IsNil() {
|
||||||
|
addFields(t.Elem(), frv.Elem(), f.Index)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Fall through to the normal field encoding logic below
|
||||||
|
// for non-struct anonymous fields.
|
||||||
}
|
}
|
||||||
addFields(t, frv, f.Index)
|
}
|
||||||
} else if typeIsHash(tomlTypeOfGo(frv)) {
|
|
||||||
|
if typeIsHash(tomlTypeOfGo(frv)) {
|
||||||
fieldsSub = append(fieldsSub, append(start, f.Index...))
|
fieldsSub = append(fieldsSub, append(start, f.Index...))
|
||||||
} else {
|
} else {
|
||||||
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
|
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
|
||||||
|
@ -336,15 +347,14 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
keyName := sft.Tag.Get("toml")
|
tag := sft.Tag.Get("toml")
|
||||||
if keyName == "-" {
|
if tag == "-" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
keyName, opts := getOptions(tag)
|
||||||
if keyName == "" {
|
if keyName == "" {
|
||||||
keyName = sft.Name
|
keyName = sft.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
keyName, opts := getOptions(keyName)
|
|
||||||
if _, ok := opts["omitempty"]; ok && isEmpty(sf) {
|
if _, ok := opts["omitempty"]; ok && isEmpty(sf) {
|
||||||
continue
|
continue
|
||||||
} else if _, ok := opts["omitzero"]; ok && isZero(sf) {
|
} else if _, ok := opts["omitzero"]; ok && isZero(sf) {
|
||||||
|
@ -457,34 +467,22 @@ func getOptions(keyName string) (string, map[string]struct{}) {
|
||||||
func isZero(rv reflect.Value) bool {
|
func isZero(rv reflect.Value) bool {
|
||||||
switch rv.Kind() {
|
switch rv.Kind() {
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
if rv.Int() == 0 {
|
return rv.Int() == 0
|
||||||
return true
|
|
||||||
}
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
if rv.Uint() == 0 {
|
return rv.Uint() == 0
|
||||||
return true
|
|
||||||
}
|
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
if rv.Float() == 0.0 {
|
return rv.Float() == 0.0
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func isEmpty(rv reflect.Value) bool {
|
func isEmpty(rv reflect.Value) bool {
|
||||||
switch rv.Kind() {
|
switch rv.Kind() {
|
||||||
case reflect.String:
|
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||||
if len(strings.TrimSpace(rv.String())) == 0 {
|
return rv.Len() == 0
|
||||||
return true
|
case reflect.Bool:
|
||||||
}
|
return !rv.Bool()
|
||||||
case reflect.Array, reflect.Slice, reflect.Map:
|
|
||||||
if rv.Len() == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -272,8 +272,6 @@ func lexTableNameStart(lx *lexer) stateFn {
|
||||||
lx.ignore()
|
lx.ignore()
|
||||||
lx.push(lexTableNameEnd)
|
lx.push(lexTableNameEnd)
|
||||||
return lexValue // reuse string lexing
|
return lexValue // reuse string lexing
|
||||||
case isWhitespace(r):
|
|
||||||
return lexTableNameStart
|
|
||||||
default:
|
default:
|
||||||
return lexBareTableName
|
return lexBareTableName
|
||||||
}
|
}
|
||||||
|
@ -560,7 +558,6 @@ func lexMultilineRawString(lx *lexer) stateFn {
|
||||||
func lexMultilineStringEscape(lx *lexer) stateFn {
|
func lexMultilineStringEscape(lx *lexer) stateFn {
|
||||||
// Handle the special case first:
|
// Handle the special case first:
|
||||||
if isNL(lx.next()) {
|
if isNL(lx.next()) {
|
||||||
lx.next()
|
|
||||||
return lexMultilineString
|
return lexMultilineString
|
||||||
} else {
|
} else {
|
||||||
lx.backup()
|
lx.backup()
|
|
@ -81,7 +81,7 @@ func (p *parser) next() item {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) bug(format string, v ...interface{}) {
|
func (p *parser) bug(format string, v ...interface{}) {
|
||||||
log.Fatalf("BUG: %s\n\n", fmt.Sprintf(format, v...))
|
log.Panicf("BUG: %s\n\n", fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) expect(typ itemType) item {
|
func (p *parser) expect(typ itemType) item {
|
||||||
|
@ -216,7 +216,7 @@ func (p *parser) value(it item) (interface{}, tomlType) {
|
||||||
case itemDatetime:
|
case itemDatetime:
|
||||||
t, err := time.Parse("2006-01-02T15:04:05Z", it.val)
|
t, err := time.Parse("2006-01-02T15:04:05Z", it.val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.bug("Expected Zulu formatted DateTime, but got '%s'.", it.val)
|
p.panicf("Invalid RFC3339 Zulu DateTime: '%s'.", it.val)
|
||||||
}
|
}
|
||||||
return t, p.typeOfPrimitive(it)
|
return t, p.typeOfPrimitive(it)
|
||||||
case itemArray:
|
case itemArray:
|
||||||
|
@ -401,7 +401,7 @@ func stripFirstNewline(s string) string {
|
||||||
if len(s) == 0 || s[0] != '\n' {
|
if len(s) == 0 || s[0] != '\n' {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
return s[1:len(s)]
|
return s[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func stripEscapedWhitespace(s string) string {
|
func stripEscapedWhitespace(s string) string {
|
||||||
|
@ -481,12 +481,7 @@ func (p *parser) asciiEscapeToUnicode(bs []byte) rune {
|
||||||
p.bug("Could not parse '%s' as a hexadecimal number, but the "+
|
p.bug("Could not parse '%s' as a hexadecimal number, but the "+
|
||||||
"lexer claims it's OK: %s", s, err)
|
"lexer claims it's OK: %s", s, err)
|
||||||
}
|
}
|
||||||
|
if !utf8.ValidRune(rune(hex)) {
|
||||||
// BUG(burntsushi)
|
|
||||||
// I honestly don't understand how this works. I can't seem
|
|
||||||
// to find a way to make this fail. I figured this would fail on invalid
|
|
||||||
// UTF-8 characters like U+DCFF, but it doesn't.
|
|
||||||
if !utf8.ValidString(string(rune(hex))) {
|
|
||||||
p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s)
|
p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s)
|
||||||
}
|
}
|
||||||
return rune(hex)
|
return rune(hex)
|
|
@ -92,10 +92,10 @@ func typeFields(t reflect.Type) []field {
|
||||||
// Scan f.typ for fields to include.
|
// Scan f.typ for fields to include.
|
||||||
for i := 0; i < f.typ.NumField(); i++ {
|
for i := 0; i < f.typ.NumField(); i++ {
|
||||||
sf := f.typ.Field(i)
|
sf := f.typ.Field(i)
|
||||||
if sf.PkgPath != "" { // unexported
|
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
name := sf.Tag.Get("toml")
|
name, _ := getOptions(sf.Tag.Get("toml"))
|
||||||
if name == "-" {
|
if name == "-" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
|
@ -35,7 +35,7 @@ type digest struct {
|
||||||
crc uint64
|
crc uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() hash.Hash {
|
func New() hash.Hash64 {
|
||||||
return &digest{}
|
return &digest{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,10 @@ type Decoder interface {
|
||||||
// StartDatabase is called when database n starts.
|
// StartDatabase is called when database n starts.
|
||||||
// Once a database starts, another database will not start until EndDatabase is called.
|
// Once a database starts, another database will not start until EndDatabase is called.
|
||||||
StartDatabase(n int)
|
StartDatabase(n int)
|
||||||
|
// AUX field
|
||||||
|
Aux(key, value []byte)
|
||||||
|
// ResizeDB hint
|
||||||
|
ResizeDatabase(dbSize, expiresSize uint32)
|
||||||
// Set is called once for each string key.
|
// Set is called once for each string key.
|
||||||
Set(key, value []byte, expiry int64)
|
Set(key, value []byte, expiry int64)
|
||||||
// StartHash is called at the beginning of a hash.
|
// StartHash is called at the beginning of a hash.
|
||||||
|
@ -38,6 +42,7 @@ type Decoder interface {
|
||||||
EndSet(key []byte)
|
EndSet(key []byte)
|
||||||
// StartList is called at the beginning of a list.
|
// StartList is called at the beginning of a list.
|
||||||
// Rpush will be called exactly length times before EndList.
|
// Rpush will be called exactly length times before EndList.
|
||||||
|
// If length of the list is not known, then length is -1
|
||||||
StartList(key []byte, length, expiry int64)
|
StartList(key []byte, length, expiry int64)
|
||||||
// Rpush is called once for each value in a list.
|
// Rpush is called once for each value in a list.
|
||||||
Rpush(key, value []byte)
|
Rpush(key, value []byte)
|
||||||
|
@ -102,11 +107,12 @@ const (
|
||||||
TypeZSet ValueType = 3
|
TypeZSet ValueType = 3
|
||||||
TypeHash ValueType = 4
|
TypeHash ValueType = 4
|
||||||
|
|
||||||
TypeHashZipmap ValueType = 9
|
TypeHashZipmap ValueType = 9
|
||||||
TypeListZiplist ValueType = 10
|
TypeListZiplist ValueType = 10
|
||||||
TypeSetIntset ValueType = 11
|
TypeSetIntset ValueType = 11
|
||||||
TypeZSetZiplist ValueType = 12
|
TypeZSetZiplist ValueType = 12
|
||||||
TypeHashZiplist ValueType = 13
|
TypeHashZiplist ValueType = 13
|
||||||
|
TypeListQuicklist ValueType = 14
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -115,6 +121,8 @@ const (
|
||||||
rdb32bitLen = 2
|
rdb32bitLen = 2
|
||||||
rdbEncVal = 3
|
rdbEncVal = 3
|
||||||
|
|
||||||
|
rdbFlagAux = 0xfa
|
||||||
|
rdbFlagResizeDB = 0xfb
|
||||||
rdbFlagExpiryMS = 0xfc
|
rdbFlagExpiryMS = 0xfc
|
||||||
rdbFlagExpiry = 0xfd
|
rdbFlagExpiry = 0xfd
|
||||||
rdbFlagSelectDB = 0xfe
|
rdbFlagSelectDB = 0xfe
|
||||||
|
@ -154,6 +162,26 @@ func (d *decode) decode() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
switch objType {
|
switch objType {
|
||||||
|
case rdbFlagAux:
|
||||||
|
auxKey, err := d.readString()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
auxVal, err := d.readString()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.event.Aux(auxKey, auxVal)
|
||||||
|
case rdbFlagResizeDB:
|
||||||
|
dbSize, _, err := d.readLength()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
expiresSize, _, err := d.readLength()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.event.ResizeDatabase(dbSize, expiresSize)
|
||||||
case rdbFlagExpiryMS:
|
case rdbFlagExpiryMS:
|
||||||
_, err := io.ReadFull(d.r, d.intBuf)
|
_, err := io.ReadFull(d.r, d.intBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -188,6 +216,7 @@ func (d *decode) decode() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
expiry = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,6 +245,16 @@ func (d *decode) readObject(key []byte, typ ValueType, expiry int64) error {
|
||||||
d.event.Rpush(key, value)
|
d.event.Rpush(key, value)
|
||||||
}
|
}
|
||||||
d.event.EndList(key)
|
d.event.EndList(key)
|
||||||
|
case TypeListQuicklist:
|
||||||
|
length, _, err := d.readLength()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.event.StartList(key, int64(-1), expiry)
|
||||||
|
for i := uint32(0); i < length; i++ {
|
||||||
|
d.readZiplist(key, 0, false)
|
||||||
|
}
|
||||||
|
d.event.EndList(key)
|
||||||
case TypeSet:
|
case TypeSet:
|
||||||
cardinality, _, err := d.readLength()
|
cardinality, _, err := d.readLength()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -269,7 +308,7 @@ func (d *decode) readObject(key []byte, typ ValueType, expiry int64) error {
|
||||||
case TypeHashZipmap:
|
case TypeHashZipmap:
|
||||||
return d.readZipmap(key, expiry)
|
return d.readZipmap(key, expiry)
|
||||||
case TypeListZiplist:
|
case TypeListZiplist:
|
||||||
return d.readZiplist(key, expiry)
|
return d.readZiplist(key, expiry, true)
|
||||||
case TypeSetIntset:
|
case TypeSetIntset:
|
||||||
return d.readIntset(key, expiry)
|
return d.readIntset(key, expiry)
|
||||||
case TypeZSetZiplist:
|
case TypeZSetZiplist:
|
||||||
|
@ -378,7 +417,7 @@ func readZipmapItemLength(buf *sliceBuffer, readFree bool) (int, int, error) {
|
||||||
return int(b), int(free), err
|
return int(b), int(free), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *decode) readZiplist(key []byte, expiry int64) error {
|
func (d *decode) readZiplist(key []byte, expiry int64, addListEvents bool) error {
|
||||||
ziplist, err := d.readString()
|
ziplist, err := d.readString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -388,7 +427,9 @@ func (d *decode) readZiplist(key []byte, expiry int64) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.event.StartList(key, length, expiry)
|
if addListEvents {
|
||||||
|
d.event.StartList(key, length, expiry)
|
||||||
|
}
|
||||||
for i := int64(0); i < length; i++ {
|
for i := int64(0); i < length; i++ {
|
||||||
entry, err := readZiplistEntry(buf)
|
entry, err := readZiplistEntry(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -396,7 +437,9 @@ func (d *decode) readZiplist(key []byte, expiry int64) error {
|
||||||
}
|
}
|
||||||
d.event.Rpush(key, entry)
|
d.event.Rpush(key, entry)
|
||||||
}
|
}
|
||||||
d.event.EndList(key)
|
if addListEvents {
|
||||||
|
d.event.EndList(key)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -585,7 +628,7 @@ func (d *decode) checkHeader() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
version, _ := strconv.ParseInt(string(header[5:]), 10, 64)
|
version, _ := strconv.ParseInt(string(header[5:]), 10, 64)
|
||||||
if version < 1 || version > 6 {
|
if version < 1 || version > 7 {
|
||||||
return fmt.Errorf("rdb: invalid RDB version number %d", version)
|
return fmt.Errorf("rdb: invalid RDB version number %d", version)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ type NopDecoder struct{}
|
||||||
|
|
||||||
func (d NopDecoder) StartRDB() {}
|
func (d NopDecoder) StartRDB() {}
|
||||||
func (d NopDecoder) StartDatabase(n int) {}
|
func (d NopDecoder) StartDatabase(n int) {}
|
||||||
|
func (d NopDecoder) Aux(key, value []byte) {}
|
||||||
|
func (d NopDecoder) ResizeDatabase(dbSize, expiresSize uint32) {}
|
||||||
func (d NopDecoder) EndDatabase(n int) {}
|
func (d NopDecoder) EndDatabase(n int) {}
|
||||||
func (d NopDecoder) EndRDB() {}
|
func (d NopDecoder) EndRDB() {}
|
||||||
func (d NopDecoder) Set(key, value []byte, expiry int64) {}
|
func (d NopDecoder) Set(key, value []byte, expiry int64) {}
|
|
@ -2,7 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build darwin dragonfly freebsd linux openbsd solaris
|
// +build darwin dragonfly freebsd linux openbsd solaris netbsd
|
||||||
|
|
||||||
package mmap
|
package mmap
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ func mmap(len int, inprot, inflags, fd uintptr, off int64) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func flush(addr, len uintptr) error {
|
func flush(addr, len uintptr) error {
|
||||||
_, _, errno := syscall.Syscall(syscall.SYS_MSYNC, addr, len, syscall.MS_SYNC)
|
_, _, errno := syscall.Syscall(_SYS_MSYNC, addr, len, _MS_SYNC)
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
return syscall.Errno(errno)
|
return syscall.Errno(errno)
|
||||||
}
|
}
|
|
@ -73,7 +73,20 @@ func mmap(len int, prot, flags, hfile uintptr, off int64) ([]byte, error) {
|
||||||
|
|
||||||
func flush(addr, len uintptr) error {
|
func flush(addr, len uintptr) error {
|
||||||
errno := syscall.FlushViewOfFile(addr, len)
|
errno := syscall.FlushViewOfFile(addr, len)
|
||||||
return os.NewSyscallError("FlushViewOfFile", errno)
|
if errno != nil {
|
||||||
|
return os.NewSyscallError("FlushViewOfFile", errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLock.Lock()
|
||||||
|
defer handleLock.Unlock()
|
||||||
|
handle, ok := handleMap[addr]
|
||||||
|
if !ok {
|
||||||
|
// should be impossible; we would've errored above
|
||||||
|
return errors.New("unknown base address")
|
||||||
|
}
|
||||||
|
|
||||||
|
errno = syscall.FlushFileBuffers(handle)
|
||||||
|
return os.NewSyscallError("FlushFileBuffers", errno)
|
||||||
}
|
}
|
||||||
|
|
||||||
func lock(addr, len uintptr) error {
|
func lock(addr, len uintptr) error {
|
||||||
|
@ -88,13 +101,18 @@ func unlock(addr, len uintptr) error {
|
||||||
|
|
||||||
func unmap(addr, len uintptr) error {
|
func unmap(addr, len uintptr) error {
|
||||||
flush(addr, len)
|
flush(addr, len)
|
||||||
|
// Lock the UnmapViewOfFile along with the handleMap deletion.
|
||||||
|
// As soon as we unmap the view, the OS is free to give the
|
||||||
|
// same addr to another new map. We don't want another goroutine
|
||||||
|
// to insert and remove the same addr into handleMap while
|
||||||
|
// we're trying to remove our old addr/handle pair.
|
||||||
|
handleLock.Lock()
|
||||||
|
defer handleLock.Unlock()
|
||||||
err := syscall.UnmapViewOfFile(addr)
|
err := syscall.UnmapViewOfFile(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLock.Lock()
|
|
||||||
defer handleLock.Unlock()
|
|
||||||
handle, ok := handleMap[addr]
|
handle, ok := handleMap[addr]
|
||||||
if !ok {
|
if !ok {
|
||||||
// should be impossible; we would've errored above
|
// should be impossible; we would've errored above
|
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright 2011 Evan Shaw. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mmap
|
||||||
|
|
||||||
|
const _SYS_MSYNC = 277
|
||||||
|
const _MS_SYNC = 0x04
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright 2011 Evan Shaw. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin dragonfly freebsd linux openbsd solaris
|
||||||
|
|
||||||
|
package mmap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const _SYS_MSYNC = syscall.SYS_MSYNC
|
||||||
|
const _MS_SYNC = syscall.MS_SYNC
|
|
@ -17,6 +17,8 @@ var (
|
||||||
ErrTooLarge = errors.New("snappy: decoded block is too large")
|
ErrTooLarge = errors.New("snappy: decoded block is too large")
|
||||||
// ErrUnsupported reports that the input isn't supported.
|
// ErrUnsupported reports that the input isn't supported.
|
||||||
ErrUnsupported = errors.New("snappy: unsupported input")
|
ErrUnsupported = errors.New("snappy: unsupported input")
|
||||||
|
|
||||||
|
errUnsupportedLiteralLength = errors.New("snappy: unsupported literal length")
|
||||||
)
|
)
|
||||||
|
|
||||||
// DecodedLen returns the length of the decoded block.
|
// DecodedLen returns the length of the decoded block.
|
||||||
|
@ -40,96 +42,33 @@ func decodedLen(src []byte) (blockLen, headerLen int, err error) {
|
||||||
return int(v), n, nil
|
return int(v), n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
decodeErrCodeCorrupt = 1
|
||||||
|
decodeErrCodeUnsupportedLiteralLength = 2
|
||||||
|
)
|
||||||
|
|
||||||
// Decode returns the decoded form of src. The returned slice may be a sub-
|
// Decode returns the decoded form of src. The returned slice may be a sub-
|
||||||
// slice of dst if dst was large enough to hold the entire decoded block.
|
// slice of dst if dst was large enough to hold the entire decoded block.
|
||||||
// Otherwise, a newly allocated slice will be returned.
|
// Otherwise, a newly allocated slice will be returned.
|
||||||
// It is valid to pass a nil dst.
|
//
|
||||||
|
// The dst and src must not overlap. It is valid to pass a nil dst.
|
||||||
func Decode(dst, src []byte) ([]byte, error) {
|
func Decode(dst, src []byte) ([]byte, error) {
|
||||||
dLen, s, err := decodedLen(src)
|
dLen, s, err := decodedLen(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(dst) < dLen {
|
if dLen <= len(dst) {
|
||||||
|
dst = dst[:dLen]
|
||||||
|
} else {
|
||||||
dst = make([]byte, dLen)
|
dst = make([]byte, dLen)
|
||||||
}
|
}
|
||||||
|
switch decode(dst, src[s:]) {
|
||||||
var d, offset, length int
|
case 0:
|
||||||
for s < len(src) {
|
return dst, nil
|
||||||
switch src[s] & 0x03 {
|
case decodeErrCodeUnsupportedLiteralLength:
|
||||||
case tagLiteral:
|
return nil, errUnsupportedLiteralLength
|
||||||
x := uint(src[s] >> 2)
|
|
||||||
switch {
|
|
||||||
case x < 60:
|
|
||||||
s++
|
|
||||||
case x == 60:
|
|
||||||
s += 2
|
|
||||||
if s > len(src) {
|
|
||||||
return nil, ErrCorrupt
|
|
||||||
}
|
|
||||||
x = uint(src[s-1])
|
|
||||||
case x == 61:
|
|
||||||
s += 3
|
|
||||||
if s > len(src) {
|
|
||||||
return nil, ErrCorrupt
|
|
||||||
}
|
|
||||||
x = uint(src[s-2]) | uint(src[s-1])<<8
|
|
||||||
case x == 62:
|
|
||||||
s += 4
|
|
||||||
if s > len(src) {
|
|
||||||
return nil, ErrCorrupt
|
|
||||||
}
|
|
||||||
x = uint(src[s-3]) | uint(src[s-2])<<8 | uint(src[s-1])<<16
|
|
||||||
case x == 63:
|
|
||||||
s += 5
|
|
||||||
if s > len(src) {
|
|
||||||
return nil, ErrCorrupt
|
|
||||||
}
|
|
||||||
x = uint(src[s-4]) | uint(src[s-3])<<8 | uint(src[s-2])<<16 | uint(src[s-1])<<24
|
|
||||||
}
|
|
||||||
length = int(x + 1)
|
|
||||||
if length <= 0 {
|
|
||||||
return nil, errors.New("snappy: unsupported literal length")
|
|
||||||
}
|
|
||||||
if length > len(dst)-d || length > len(src)-s {
|
|
||||||
return nil, ErrCorrupt
|
|
||||||
}
|
|
||||||
copy(dst[d:], src[s:s+length])
|
|
||||||
d += length
|
|
||||||
s += length
|
|
||||||
continue
|
|
||||||
|
|
||||||
case tagCopy1:
|
|
||||||
s += 2
|
|
||||||
if s > len(src) {
|
|
||||||
return nil, ErrCorrupt
|
|
||||||
}
|
|
||||||
length = 4 + int(src[s-2])>>2&0x7
|
|
||||||
offset = int(src[s-2])&0xe0<<3 | int(src[s-1])
|
|
||||||
|
|
||||||
case tagCopy2:
|
|
||||||
s += 3
|
|
||||||
if s > len(src) {
|
|
||||||
return nil, ErrCorrupt
|
|
||||||
}
|
|
||||||
length = 1 + int(src[s-3])>>2
|
|
||||||
offset = int(src[s-2]) | int(src[s-1])<<8
|
|
||||||
|
|
||||||
case tagCopy4:
|
|
||||||
return nil, errors.New("snappy: unsupported COPY_4 tag")
|
|
||||||
}
|
|
||||||
|
|
||||||
end := d + length
|
|
||||||
if offset > d || end > len(dst) {
|
|
||||||
return nil, ErrCorrupt
|
|
||||||
}
|
|
||||||
for ; d < end; d++ {
|
|
||||||
dst[d] = dst[d-offset]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if d != dLen {
|
return nil, ErrCorrupt
|
||||||
return nil, ErrCorrupt
|
|
||||||
}
|
|
||||||
return dst[:d], nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReader returns a new Reader that decompresses from r, using the framing
|
// NewReader returns a new Reader that decompresses from r, using the framing
|
||||||
|
@ -138,12 +77,12 @@ func Decode(dst, src []byte) ([]byte, error) {
|
||||||
func NewReader(r io.Reader) *Reader {
|
func NewReader(r io.Reader) *Reader {
|
||||||
return &Reader{
|
return &Reader{
|
||||||
r: r,
|
r: r,
|
||||||
decoded: make([]byte, maxUncompressedChunkLen),
|
decoded: make([]byte, maxBlockSize),
|
||||||
buf: make([]byte, MaxEncodedLen(maxUncompressedChunkLen)+checksumSize),
|
buf: make([]byte, maxEncodedLenOfMaxBlockSize+checksumSize),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reader is an io.Reader than can read Snappy-compressed bytes.
|
// Reader is an io.Reader that can read Snappy-compressed bytes.
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
r io.Reader
|
r io.Reader
|
||||||
err error
|
err error
|
||||||
|
@ -165,9 +104,9 @@ func (r *Reader) Reset(reader io.Reader) {
|
||||||
r.readHeader = false
|
r.readHeader = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readFull(p []byte) (ok bool) {
|
func (r *Reader) readFull(p []byte, allowEOF bool) (ok bool) {
|
||||||
if _, r.err = io.ReadFull(r.r, p); r.err != nil {
|
if _, r.err = io.ReadFull(r.r, p); r.err != nil {
|
||||||
if r.err == io.ErrUnexpectedEOF {
|
if r.err == io.ErrUnexpectedEOF || (r.err == io.EOF && !allowEOF) {
|
||||||
r.err = ErrCorrupt
|
r.err = ErrCorrupt
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -186,7 +125,7 @@ func (r *Reader) Read(p []byte) (int, error) {
|
||||||
r.i += n
|
r.i += n
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
if !r.readFull(r.buf[:4]) {
|
if !r.readFull(r.buf[:4], true) {
|
||||||
return 0, r.err
|
return 0, r.err
|
||||||
}
|
}
|
||||||
chunkType := r.buf[0]
|
chunkType := r.buf[0]
|
||||||
|
@ -213,7 +152,7 @@ func (r *Reader) Read(p []byte) (int, error) {
|
||||||
return 0, r.err
|
return 0, r.err
|
||||||
}
|
}
|
||||||
buf := r.buf[:chunkLen]
|
buf := r.buf[:chunkLen]
|
||||||
if !r.readFull(buf) {
|
if !r.readFull(buf, false) {
|
||||||
return 0, r.err
|
return 0, r.err
|
||||||
}
|
}
|
||||||
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
|
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
|
||||||
|
@ -246,13 +185,17 @@ func (r *Reader) Read(p []byte) (int, error) {
|
||||||
return 0, r.err
|
return 0, r.err
|
||||||
}
|
}
|
||||||
buf := r.buf[:checksumSize]
|
buf := r.buf[:checksumSize]
|
||||||
if !r.readFull(buf) {
|
if !r.readFull(buf, false) {
|
||||||
return 0, r.err
|
return 0, r.err
|
||||||
}
|
}
|
||||||
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
|
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
|
||||||
// Read directly into r.decoded instead of via r.buf.
|
// Read directly into r.decoded instead of via r.buf.
|
||||||
n := chunkLen - checksumSize
|
n := chunkLen - checksumSize
|
||||||
if !r.readFull(r.decoded[:n]) {
|
if n > len(r.decoded) {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
if !r.readFull(r.decoded[:n], false) {
|
||||||
return 0, r.err
|
return 0, r.err
|
||||||
}
|
}
|
||||||
if crc(r.decoded[:n]) != checksum {
|
if crc(r.decoded[:n]) != checksum {
|
||||||
|
@ -268,7 +211,7 @@ func (r *Reader) Read(p []byte) (int, error) {
|
||||||
r.err = ErrCorrupt
|
r.err = ErrCorrupt
|
||||||
return 0, r.err
|
return 0, r.err
|
||||||
}
|
}
|
||||||
if !r.readFull(r.buf[:len(magicBody)]) {
|
if !r.readFull(r.buf[:len(magicBody)], false) {
|
||||||
return 0, r.err
|
return 0, r.err
|
||||||
}
|
}
|
||||||
for i := 0; i < len(magicBody); i++ {
|
for i := 0; i < len(magicBody); i++ {
|
||||||
|
@ -287,7 +230,7 @@ func (r *Reader) Read(p []byte) (int, error) {
|
||||||
}
|
}
|
||||||
// Section 4.4 Padding (chunk type 0xfe).
|
// Section 4.4 Padding (chunk type 0xfe).
|
||||||
// Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd).
|
// Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd).
|
||||||
if !r.readFull(r.buf[:chunkLen]) {
|
if !r.readFull(r.buf[:chunkLen], false) {
|
||||||
return 0, r.err
|
return 0, r.err
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !noasm
|
||||||
|
|
||||||
|
package snappy
|
||||||
|
|
||||||
|
// decode has the same semantics as in decode_other.go.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func decode(dst, src []byte) int
|
|
@ -0,0 +1,490 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !noasm
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// The asm code generally follows the pure Go code in decode_other.go, except
|
||||||
|
// where marked with a "!!!".
|
||||||
|
|
||||||
|
// func decode(dst, src []byte) int
|
||||||
|
//
|
||||||
|
// All local variables fit into registers. The non-zero stack size is only to
|
||||||
|
// spill registers and push args when issuing a CALL. The register allocation:
|
||||||
|
// - AX scratch
|
||||||
|
// - BX scratch
|
||||||
|
// - CX length or x
|
||||||
|
// - DX offset
|
||||||
|
// - SI &src[s]
|
||||||
|
// - DI &dst[d]
|
||||||
|
// + R8 dst_base
|
||||||
|
// + R9 dst_len
|
||||||
|
// + R10 dst_base + dst_len
|
||||||
|
// + R11 src_base
|
||||||
|
// + R12 src_len
|
||||||
|
// + R13 src_base + src_len
|
||||||
|
// - R14 used by doCopy
|
||||||
|
// - R15 used by doCopy
|
||||||
|
//
|
||||||
|
// The registers R8-R13 (marked with a "+") are set at the start of the
|
||||||
|
// function, and after a CALL returns, and are not otherwise modified.
|
||||||
|
//
|
||||||
|
// The d variable is implicitly DI - R8, and len(dst)-d is R10 - DI.
|
||||||
|
// The s variable is implicitly SI - R11, and len(src)-s is R13 - SI.
|
||||||
|
TEXT ·decode(SB), NOSPLIT, $48-56
|
||||||
|
// Initialize SI, DI and R8-R13.
|
||||||
|
MOVQ dst_base+0(FP), R8
|
||||||
|
MOVQ dst_len+8(FP), R9
|
||||||
|
MOVQ R8, DI
|
||||||
|
MOVQ R8, R10
|
||||||
|
ADDQ R9, R10
|
||||||
|
MOVQ src_base+24(FP), R11
|
||||||
|
MOVQ src_len+32(FP), R12
|
||||||
|
MOVQ R11, SI
|
||||||
|
MOVQ R11, R13
|
||||||
|
ADDQ R12, R13
|
||||||
|
|
||||||
|
loop:
|
||||||
|
// for s < len(src)
|
||||||
|
CMPQ SI, R13
|
||||||
|
JEQ end
|
||||||
|
|
||||||
|
// CX = uint32(src[s])
|
||||||
|
//
|
||||||
|
// switch src[s] & 0x03
|
||||||
|
MOVBLZX (SI), CX
|
||||||
|
MOVL CX, BX
|
||||||
|
ANDL $3, BX
|
||||||
|
CMPL BX, $1
|
||||||
|
JAE tagCopy
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// The code below handles literal tags.
|
||||||
|
|
||||||
|
// case tagLiteral:
|
||||||
|
// x := uint32(src[s] >> 2)
|
||||||
|
// switch
|
||||||
|
SHRL $2, CX
|
||||||
|
CMPL CX, $60
|
||||||
|
JAE tagLit60Plus
|
||||||
|
|
||||||
|
// case x < 60:
|
||||||
|
// s++
|
||||||
|
INCQ SI
|
||||||
|
|
||||||
|
doLit:
|
||||||
|
// This is the end of the inner "switch", when we have a literal tag.
|
||||||
|
//
|
||||||
|
// We assume that CX == x and x fits in a uint32, where x is the variable
|
||||||
|
// used in the pure Go decode_other.go code.
|
||||||
|
|
||||||
|
// length = int(x) + 1
|
||||||
|
//
|
||||||
|
// Unlike the pure Go code, we don't need to check if length <= 0 because
|
||||||
|
// CX can hold 64 bits, so the increment cannot overflow.
|
||||||
|
INCQ CX
|
||||||
|
|
||||||
|
// Prepare to check if copying length bytes will run past the end of dst or
|
||||||
|
// src.
|
||||||
|
//
|
||||||
|
// AX = len(dst) - d
|
||||||
|
// BX = len(src) - s
|
||||||
|
MOVQ R10, AX
|
||||||
|
SUBQ DI, AX
|
||||||
|
MOVQ R13, BX
|
||||||
|
SUBQ SI, BX
|
||||||
|
|
||||||
|
// !!! Try a faster technique for short (16 or fewer bytes) copies.
|
||||||
|
//
|
||||||
|
// if length > 16 || len(dst)-d < 16 || len(src)-s < 16 {
|
||||||
|
// goto callMemmove // Fall back on calling runtime·memmove.
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The C++ snappy code calls this TryFastAppend. It also checks len(src)-s
|
||||||
|
// against 21 instead of 16, because it cannot assume that all of its input
|
||||||
|
// is contiguous in memory and so it needs to leave enough source bytes to
|
||||||
|
// read the next tag without refilling buffers, but Go's Decode assumes
|
||||||
|
// contiguousness (the src argument is a []byte).
|
||||||
|
CMPQ CX, $16
|
||||||
|
JGT callMemmove
|
||||||
|
CMPQ AX, $16
|
||||||
|
JLT callMemmove
|
||||||
|
CMPQ BX, $16
|
||||||
|
JLT callMemmove
|
||||||
|
|
||||||
|
// !!! Implement the copy from src to dst as a 16-byte load and store.
|
||||||
|
// (Decode's documentation says that dst and src must not overlap.)
|
||||||
|
//
|
||||||
|
// This always copies 16 bytes, instead of only length bytes, but that's
|
||||||
|
// OK. If the input is a valid Snappy encoding then subsequent iterations
|
||||||
|
// will fix up the overrun. Otherwise, Decode returns a nil []byte (and a
|
||||||
|
// non-nil error), so the overrun will be ignored.
|
||||||
|
//
|
||||||
|
// Note that on amd64, it is legal and cheap to issue unaligned 8-byte or
|
||||||
|
// 16-byte loads and stores. This technique probably wouldn't be as
|
||||||
|
// effective on architectures that are fussier about alignment.
|
||||||
|
MOVOU 0(SI), X0
|
||||||
|
MOVOU X0, 0(DI)
|
||||||
|
|
||||||
|
// d += length
|
||||||
|
// s += length
|
||||||
|
ADDQ CX, DI
|
||||||
|
ADDQ CX, SI
|
||||||
|
JMP loop
|
||||||
|
|
||||||
|
callMemmove:
|
||||||
|
// if length > len(dst)-d || length > len(src)-s { etc }
|
||||||
|
CMPQ CX, AX
|
||||||
|
JGT errCorrupt
|
||||||
|
CMPQ CX, BX
|
||||||
|
JGT errCorrupt
|
||||||
|
|
||||||
|
// copy(dst[d:], src[s:s+length])
|
||||||
|
//
|
||||||
|
// This means calling runtime·memmove(&dst[d], &src[s], length), so we push
|
||||||
|
// DI, SI and CX as arguments. Coincidentally, we also need to spill those
|
||||||
|
// three registers to the stack, to save local variables across the CALL.
|
||||||
|
MOVQ DI, 0(SP)
|
||||||
|
MOVQ SI, 8(SP)
|
||||||
|
MOVQ CX, 16(SP)
|
||||||
|
MOVQ DI, 24(SP)
|
||||||
|
MOVQ SI, 32(SP)
|
||||||
|
MOVQ CX, 40(SP)
|
||||||
|
CALL runtime·memmove(SB)
|
||||||
|
|
||||||
|
// Restore local variables: unspill registers from the stack and
|
||||||
|
// re-calculate R8-R13.
|
||||||
|
MOVQ 24(SP), DI
|
||||||
|
MOVQ 32(SP), SI
|
||||||
|
MOVQ 40(SP), CX
|
||||||
|
MOVQ dst_base+0(FP), R8
|
||||||
|
MOVQ dst_len+8(FP), R9
|
||||||
|
MOVQ R8, R10
|
||||||
|
ADDQ R9, R10
|
||||||
|
MOVQ src_base+24(FP), R11
|
||||||
|
MOVQ src_len+32(FP), R12
|
||||||
|
MOVQ R11, R13
|
||||||
|
ADDQ R12, R13
|
||||||
|
|
||||||
|
// d += length
|
||||||
|
// s += length
|
||||||
|
ADDQ CX, DI
|
||||||
|
ADDQ CX, SI
|
||||||
|
JMP loop
|
||||||
|
|
||||||
|
tagLit60Plus:
|
||||||
|
// !!! This fragment does the
|
||||||
|
//
|
||||||
|
// s += x - 58; if uint(s) > uint(len(src)) { etc }
|
||||||
|
//
|
||||||
|
// checks. In the asm version, we code it once instead of once per switch case.
|
||||||
|
ADDQ CX, SI
|
||||||
|
SUBQ $58, SI
|
||||||
|
MOVQ SI, BX
|
||||||
|
SUBQ R11, BX
|
||||||
|
CMPQ BX, R12
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// case x == 60:
|
||||||
|
CMPL CX, $61
|
||||||
|
JEQ tagLit61
|
||||||
|
JA tagLit62Plus
|
||||||
|
|
||||||
|
// x = uint32(src[s-1])
|
||||||
|
MOVBLZX -1(SI), CX
|
||||||
|
JMP doLit
|
||||||
|
|
||||||
|
tagLit61:
|
||||||
|
// case x == 61:
|
||||||
|
// x = uint32(src[s-2]) | uint32(src[s-1])<<8
|
||||||
|
MOVWLZX -2(SI), CX
|
||||||
|
JMP doLit
|
||||||
|
|
||||||
|
tagLit62Plus:
|
||||||
|
CMPL CX, $62
|
||||||
|
JA tagLit63
|
||||||
|
|
||||||
|
// case x == 62:
|
||||||
|
// x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16
|
||||||
|
MOVWLZX -3(SI), CX
|
||||||
|
MOVBLZX -1(SI), BX
|
||||||
|
SHLL $16, BX
|
||||||
|
ORL BX, CX
|
||||||
|
JMP doLit
|
||||||
|
|
||||||
|
tagLit63:
|
||||||
|
// case x == 63:
|
||||||
|
// x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
|
||||||
|
MOVL -4(SI), CX
|
||||||
|
JMP doLit
|
||||||
|
|
||||||
|
// The code above handles literal tags.
|
||||||
|
// ----------------------------------------
|
||||||
|
// The code below handles copy tags.
|
||||||
|
|
||||||
|
tagCopy4:
|
||||||
|
// case tagCopy4:
|
||||||
|
// s += 5
|
||||||
|
ADDQ $5, SI
|
||||||
|
|
||||||
|
// if uint(s) > uint(len(src)) { etc }
|
||||||
|
MOVQ SI, BX
|
||||||
|
SUBQ R11, BX
|
||||||
|
CMPQ BX, R12
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// length = 1 + int(src[s-5])>>2
|
||||||
|
SHRQ $2, CX
|
||||||
|
INCQ CX
|
||||||
|
|
||||||
|
// offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24)
|
||||||
|
MOVLQZX -4(SI), DX
|
||||||
|
JMP doCopy
|
||||||
|
|
||||||
|
tagCopy2:
|
||||||
|
// case tagCopy2:
|
||||||
|
// s += 3
|
||||||
|
ADDQ $3, SI
|
||||||
|
|
||||||
|
// if uint(s) > uint(len(src)) { etc }
|
||||||
|
MOVQ SI, BX
|
||||||
|
SUBQ R11, BX
|
||||||
|
CMPQ BX, R12
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// length = 1 + int(src[s-3])>>2
|
||||||
|
SHRQ $2, CX
|
||||||
|
INCQ CX
|
||||||
|
|
||||||
|
// offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8)
|
||||||
|
MOVWQZX -2(SI), DX
|
||||||
|
JMP doCopy
|
||||||
|
|
||||||
|
tagCopy:
|
||||||
|
// We have a copy tag. We assume that:
|
||||||
|
// - BX == src[s] & 0x03
|
||||||
|
// - CX == src[s]
|
||||||
|
CMPQ BX, $2
|
||||||
|
JEQ tagCopy2
|
||||||
|
JA tagCopy4
|
||||||
|
|
||||||
|
// case tagCopy1:
|
||||||
|
// s += 2
|
||||||
|
ADDQ $2, SI
|
||||||
|
|
||||||
|
// if uint(s) > uint(len(src)) { etc }
|
||||||
|
MOVQ SI, BX
|
||||||
|
SUBQ R11, BX
|
||||||
|
CMPQ BX, R12
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1]))
|
||||||
|
MOVQ CX, DX
|
||||||
|
ANDQ $0xe0, DX
|
||||||
|
SHLQ $3, DX
|
||||||
|
MOVBQZX -1(SI), BX
|
||||||
|
ORQ BX, DX
|
||||||
|
|
||||||
|
// length = 4 + int(src[s-2])>>2&0x7
|
||||||
|
SHRQ $2, CX
|
||||||
|
ANDQ $7, CX
|
||||||
|
ADDQ $4, CX
|
||||||
|
|
||||||
|
doCopy:
|
||||||
|
// This is the end of the outer "switch", when we have a copy tag.
|
||||||
|
//
|
||||||
|
// We assume that:
|
||||||
|
// - CX == length && CX > 0
|
||||||
|
// - DX == offset
|
||||||
|
|
||||||
|
// if offset <= 0 { etc }
|
||||||
|
CMPQ DX, $0
|
||||||
|
JLE errCorrupt
|
||||||
|
|
||||||
|
// if d < offset { etc }
|
||||||
|
MOVQ DI, BX
|
||||||
|
SUBQ R8, BX
|
||||||
|
CMPQ BX, DX
|
||||||
|
JLT errCorrupt
|
||||||
|
|
||||||
|
// if length > len(dst)-d { etc }
|
||||||
|
MOVQ R10, BX
|
||||||
|
SUBQ DI, BX
|
||||||
|
CMPQ CX, BX
|
||||||
|
JGT errCorrupt
|
||||||
|
|
||||||
|
// forwardCopy(dst[d:d+length], dst[d-offset:]); d += length
|
||||||
|
//
|
||||||
|
// Set:
|
||||||
|
// - R14 = len(dst)-d
|
||||||
|
// - R15 = &dst[d-offset]
|
||||||
|
MOVQ R10, R14
|
||||||
|
SUBQ DI, R14
|
||||||
|
MOVQ DI, R15
|
||||||
|
SUBQ DX, R15
|
||||||
|
|
||||||
|
// !!! Try a faster technique for short (16 or fewer bytes) forward copies.
|
||||||
|
//
|
||||||
|
// First, try using two 8-byte load/stores, similar to the doLit technique
|
||||||
|
// above. Even if dst[d:d+length] and dst[d-offset:] can overlap, this is
|
||||||
|
// still OK if offset >= 8. Note that this has to be two 8-byte load/stores
|
||||||
|
// and not one 16-byte load/store, and the first store has to be before the
|
||||||
|
// second load, due to the overlap if offset is in the range [8, 16).
|
||||||
|
//
|
||||||
|
// if length > 16 || offset < 8 || len(dst)-d < 16 {
|
||||||
|
// goto slowForwardCopy
|
||||||
|
// }
|
||||||
|
// copy 16 bytes
|
||||||
|
// d += length
|
||||||
|
CMPQ CX, $16
|
||||||
|
JGT slowForwardCopy
|
||||||
|
CMPQ DX, $8
|
||||||
|
JLT slowForwardCopy
|
||||||
|
CMPQ R14, $16
|
||||||
|
JLT slowForwardCopy
|
||||||
|
MOVQ 0(R15), AX
|
||||||
|
MOVQ AX, 0(DI)
|
||||||
|
MOVQ 8(R15), BX
|
||||||
|
MOVQ BX, 8(DI)
|
||||||
|
ADDQ CX, DI
|
||||||
|
JMP loop
|
||||||
|
|
||||||
|
slowForwardCopy:
|
||||||
|
// !!! If the forward copy is longer than 16 bytes, or if offset < 8, we
|
||||||
|
// can still try 8-byte load stores, provided we can overrun up to 10 extra
|
||||||
|
// bytes. As above, the overrun will be fixed up by subsequent iterations
|
||||||
|
// of the outermost loop.
|
||||||
|
//
|
||||||
|
// The C++ snappy code calls this technique IncrementalCopyFastPath. Its
|
||||||
|
// commentary says:
|
||||||
|
//
|
||||||
|
// ----
|
||||||
|
//
|
||||||
|
// The main part of this loop is a simple copy of eight bytes at a time
|
||||||
|
// until we've copied (at least) the requested amount of bytes. However,
|
||||||
|
// if d and d-offset are less than eight bytes apart (indicating a
|
||||||
|
// repeating pattern of length < 8), we first need to expand the pattern in
|
||||||
|
// order to get the correct results. For instance, if the buffer looks like
|
||||||
|
// this, with the eight-byte <d-offset> and <d> patterns marked as
|
||||||
|
// intervals:
|
||||||
|
//
|
||||||
|
// abxxxxxxxxxxxx
|
||||||
|
// [------] d-offset
|
||||||
|
// [------] d
|
||||||
|
//
|
||||||
|
// a single eight-byte copy from <d-offset> to <d> will repeat the pattern
|
||||||
|
// once, after which we can move <d> two bytes without moving <d-offset>:
|
||||||
|
//
|
||||||
|
// ababxxxxxxxxxx
|
||||||
|
// [------] d-offset
|
||||||
|
// [------] d
|
||||||
|
//
|
||||||
|
// and repeat the exercise until the two no longer overlap.
|
||||||
|
//
|
||||||
|
// This allows us to do very well in the special case of one single byte
|
||||||
|
// repeated many times, without taking a big hit for more general cases.
|
||||||
|
//
|
||||||
|
// The worst case of extra writing past the end of the match occurs when
|
||||||
|
// offset == 1 and length == 1; the last copy will read from byte positions
|
||||||
|
// [0..7] and write to [4..11], whereas it was only supposed to write to
|
||||||
|
// position 1. Thus, ten excess bytes.
|
||||||
|
//
|
||||||
|
// ----
|
||||||
|
//
|
||||||
|
// That "10 byte overrun" worst case is confirmed by Go's
|
||||||
|
// TestSlowForwardCopyOverrun, which also tests the fixUpSlowForwardCopy
|
||||||
|
// and finishSlowForwardCopy algorithm.
|
||||||
|
//
|
||||||
|
// if length > len(dst)-d-10 {
|
||||||
|
// goto verySlowForwardCopy
|
||||||
|
// }
|
||||||
|
SUBQ $10, R14
|
||||||
|
CMPQ CX, R14
|
||||||
|
JGT verySlowForwardCopy
|
||||||
|
|
||||||
|
makeOffsetAtLeast8:
|
||||||
|
// !!! As above, expand the pattern so that offset >= 8 and we can use
|
||||||
|
// 8-byte load/stores.
|
||||||
|
//
|
||||||
|
// for offset < 8 {
|
||||||
|
// copy 8 bytes from dst[d-offset:] to dst[d:]
|
||||||
|
// length -= offset
|
||||||
|
// d += offset
|
||||||
|
// offset += offset
|
||||||
|
// // The two previous lines together means that d-offset, and therefore
|
||||||
|
// // R15, is unchanged.
|
||||||
|
// }
|
||||||
|
CMPQ DX, $8
|
||||||
|
JGE fixUpSlowForwardCopy
|
||||||
|
MOVQ (R15), BX
|
||||||
|
MOVQ BX, (DI)
|
||||||
|
SUBQ DX, CX
|
||||||
|
ADDQ DX, DI
|
||||||
|
ADDQ DX, DX
|
||||||
|
JMP makeOffsetAtLeast8
|
||||||
|
|
||||||
|
fixUpSlowForwardCopy:
|
||||||
|
// !!! Add length (which might be negative now) to d (implied by DI being
|
||||||
|
// &dst[d]) so that d ends up at the right place when we jump back to the
|
||||||
|
// top of the loop. Before we do that, though, we save DI to AX so that, if
|
||||||
|
// length is positive, copying the remaining length bytes will write to the
|
||||||
|
// right place.
|
||||||
|
MOVQ DI, AX
|
||||||
|
ADDQ CX, DI
|
||||||
|
|
||||||
|
finishSlowForwardCopy:
|
||||||
|
// !!! Repeat 8-byte load/stores until length <= 0. Ending with a negative
|
||||||
|
// length means that we overrun, but as above, that will be fixed up by
|
||||||
|
// subsequent iterations of the outermost loop.
|
||||||
|
CMPQ CX, $0
|
||||||
|
JLE loop
|
||||||
|
MOVQ (R15), BX
|
||||||
|
MOVQ BX, (AX)
|
||||||
|
ADDQ $8, R15
|
||||||
|
ADDQ $8, AX
|
||||||
|
SUBQ $8, CX
|
||||||
|
JMP finishSlowForwardCopy
|
||||||
|
|
||||||
|
verySlowForwardCopy:
|
||||||
|
// verySlowForwardCopy is a simple implementation of forward copy. In C
|
||||||
|
// parlance, this is a do/while loop instead of a while loop, since we know
|
||||||
|
// that length > 0. In Go syntax:
|
||||||
|
//
|
||||||
|
// for {
|
||||||
|
// dst[d] = dst[d - offset]
|
||||||
|
// d++
|
||||||
|
// length--
|
||||||
|
// if length == 0 {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
MOVB (R15), BX
|
||||||
|
MOVB BX, (DI)
|
||||||
|
INCQ R15
|
||||||
|
INCQ DI
|
||||||
|
DECQ CX
|
||||||
|
JNZ verySlowForwardCopy
|
||||||
|
JMP loop
|
||||||
|
|
||||||
|
// The code above handles copy tags.
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
end:
|
||||||
|
// This is the end of the "for s < len(src)".
|
||||||
|
//
|
||||||
|
// if d != len(dst) { etc }
|
||||||
|
CMPQ DI, R10
|
||||||
|
JNE errCorrupt
|
||||||
|
|
||||||
|
// return 0
|
||||||
|
MOVQ $0, ret+48(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
errCorrupt:
|
||||||
|
// return decodeErrCodeCorrupt
|
||||||
|
MOVQ $1, ret+48(FP)
|
||||||
|
RET
|
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !amd64 appengine !gc noasm
|
||||||
|
|
||||||
|
package snappy
|
||||||
|
|
||||||
|
// decode writes the decoding of src to dst. It assumes that the varint-encoded
|
||||||
|
// length of the decompressed bytes has already been read, and that len(dst)
|
||||||
|
// equals that length.
|
||||||
|
//
|
||||||
|
// It returns 0 on success or a decodeErrCodeXxx error code on failure.
|
||||||
|
func decode(dst, src []byte) int {
|
||||||
|
var d, s, offset, length int
|
||||||
|
for s < len(src) {
|
||||||
|
switch src[s] & 0x03 {
|
||||||
|
case tagLiteral:
|
||||||
|
x := uint32(src[s] >> 2)
|
||||||
|
switch {
|
||||||
|
case x < 60:
|
||||||
|
s++
|
||||||
|
case x == 60:
|
||||||
|
s += 2
|
||||||
|
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
x = uint32(src[s-1])
|
||||||
|
case x == 61:
|
||||||
|
s += 3
|
||||||
|
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
x = uint32(src[s-2]) | uint32(src[s-1])<<8
|
||||||
|
case x == 62:
|
||||||
|
s += 4
|
||||||
|
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16
|
||||||
|
case x == 63:
|
||||||
|
s += 5
|
||||||
|
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
|
||||||
|
}
|
||||||
|
length = int(x) + 1
|
||||||
|
if length <= 0 {
|
||||||
|
return decodeErrCodeUnsupportedLiteralLength
|
||||||
|
}
|
||||||
|
if length > len(dst)-d || length > len(src)-s {
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
copy(dst[d:], src[s:s+length])
|
||||||
|
d += length
|
||||||
|
s += length
|
||||||
|
continue
|
||||||
|
|
||||||
|
case tagCopy1:
|
||||||
|
s += 2
|
||||||
|
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
length = 4 + int(src[s-2])>>2&0x7
|
||||||
|
offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1]))
|
||||||
|
|
||||||
|
case tagCopy2:
|
||||||
|
s += 3
|
||||||
|
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
length = 1 + int(src[s-3])>>2
|
||||||
|
offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8)
|
||||||
|
|
||||||
|
case tagCopy4:
|
||||||
|
s += 5
|
||||||
|
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
length = 1 + int(src[s-5])>>2
|
||||||
|
offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24)
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset <= 0 || d < offset || length > len(dst)-d {
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
// Copy from an earlier sub-slice of dst to a later sub-slice. Unlike
|
||||||
|
// the built-in copy function, this byte-by-byte copy always runs
|
||||||
|
// forwards, even if the slices overlap. Conceptually, this is:
|
||||||
|
//
|
||||||
|
// d += forwardCopy(dst[d:d+length], dst[d-offset:])
|
||||||
|
for end := d + length; d != end; d++ {
|
||||||
|
dst[d] = dst[d-offset]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d != len(dst) {
|
||||||
|
return decodeErrCodeCorrupt
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
|
@ -0,0 +1,285 @@
|
||||||
|
// Copyright 2011 The Snappy-Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package snappy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encode returns the encoded form of src. The returned slice may be a sub-
|
||||||
|
// slice of dst if dst was large enough to hold the entire encoded block.
|
||||||
|
// Otherwise, a newly allocated slice will be returned.
|
||||||
|
//
|
||||||
|
// The dst and src must not overlap. It is valid to pass a nil dst.
|
||||||
|
func Encode(dst, src []byte) []byte {
|
||||||
|
if n := MaxEncodedLen(len(src)); n < 0 {
|
||||||
|
panic(ErrTooLarge)
|
||||||
|
} else if len(dst) < n {
|
||||||
|
dst = make([]byte, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The block starts with the varint-encoded length of the decompressed bytes.
|
||||||
|
d := binary.PutUvarint(dst, uint64(len(src)))
|
||||||
|
|
||||||
|
for len(src) > 0 {
|
||||||
|
p := src
|
||||||
|
src = nil
|
||||||
|
if len(p) > maxBlockSize {
|
||||||
|
p, src = p[:maxBlockSize], p[maxBlockSize:]
|
||||||
|
}
|
||||||
|
if len(p) < minNonLiteralBlockSize {
|
||||||
|
d += emitLiteral(dst[d:], p)
|
||||||
|
} else {
|
||||||
|
d += encodeBlock(dst[d:], p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst[:d]
|
||||||
|
}
|
||||||
|
|
||||||
|
// inputMargin is the minimum number of extra input bytes to keep, inside
|
||||||
|
// encodeBlock's inner loop. On some architectures, this margin lets us
|
||||||
|
// implement a fast path for emitLiteral, where the copy of short (<= 16 byte)
|
||||||
|
// literals can be implemented as a single load to and store from a 16-byte
|
||||||
|
// register. That literal's actual length can be as short as 1 byte, so this
|
||||||
|
// can copy up to 15 bytes too much, but that's OK as subsequent iterations of
|
||||||
|
// the encoding loop will fix up the copy overrun, and this inputMargin ensures
|
||||||
|
// that we don't overrun the dst and src buffers.
|
||||||
|
const inputMargin = 16 - 1
|
||||||
|
|
||||||
|
// minNonLiteralBlockSize is the minimum size of the input to encodeBlock that
|
||||||
|
// could be encoded with a copy tag. This is the minimum with respect to the
|
||||||
|
// algorithm used by encodeBlock, not a minimum enforced by the file format.
|
||||||
|
//
|
||||||
|
// The encoded output must start with at least a 1 byte literal, as there are
|
||||||
|
// no previous bytes to copy. A minimal (1 byte) copy after that, generated
|
||||||
|
// from an emitCopy call in encodeBlock's main loop, would require at least
|
||||||
|
// another inputMargin bytes, for the reason above: we want any emitLiteral
|
||||||
|
// calls inside encodeBlock's main loop to use the fast path if possible, which
|
||||||
|
// requires being able to overrun by inputMargin bytes. Thus,
|
||||||
|
// minNonLiteralBlockSize equals 1 + 1 + inputMargin.
|
||||||
|
//
|
||||||
|
// The C++ code doesn't use this exact threshold, but it could, as discussed at
|
||||||
|
// https://groups.google.com/d/topic/snappy-compression/oGbhsdIJSJ8/discussion
|
||||||
|
// The difference between Go (2+inputMargin) and C++ (inputMargin) is purely an
|
||||||
|
// optimization. It should not affect the encoded form. This is tested by
|
||||||
|
// TestSameEncodingAsCppShortCopies.
|
||||||
|
const minNonLiteralBlockSize = 1 + 1 + inputMargin
|
||||||
|
|
||||||
|
// MaxEncodedLen returns the maximum length of a snappy block, given its
|
||||||
|
// uncompressed length.
|
||||||
|
//
|
||||||
|
// It will return a negative value if srcLen is too large to encode.
|
||||||
|
func MaxEncodedLen(srcLen int) int {
|
||||||
|
n := uint64(srcLen)
|
||||||
|
if n > 0xffffffff {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
// Compressed data can be defined as:
|
||||||
|
// compressed := item* literal*
|
||||||
|
// item := literal* copy
|
||||||
|
//
|
||||||
|
// The trailing literal sequence has a space blowup of at most 62/60
|
||||||
|
// since a literal of length 60 needs one tag byte + one extra byte
|
||||||
|
// for length information.
|
||||||
|
//
|
||||||
|
// Item blowup is trickier to measure. Suppose the "copy" op copies
|
||||||
|
// 4 bytes of data. Because of a special check in the encoding code,
|
||||||
|
// we produce a 4-byte copy only if the offset is < 65536. Therefore
|
||||||
|
// the copy op takes 3 bytes to encode, and this type of item leads
|
||||||
|
// to at most the 62/60 blowup for representing literals.
|
||||||
|
//
|
||||||
|
// Suppose the "copy" op copies 5 bytes of data. If the offset is big
|
||||||
|
// enough, it will take 5 bytes to encode the copy op. Therefore the
|
||||||
|
// worst case here is a one-byte literal followed by a five-byte copy.
|
||||||
|
// That is, 6 bytes of input turn into 7 bytes of "compressed" data.
|
||||||
|
//
|
||||||
|
// This last factor dominates the blowup, so the final estimate is:
|
||||||
|
n = 32 + n + n/6
|
||||||
|
if n > 0xffffffff {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return int(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errClosed = errors.New("snappy: Writer is closed")
|
||||||
|
|
||||||
|
// NewWriter returns a new Writer that compresses to w.
|
||||||
|
//
|
||||||
|
// The Writer returned does not buffer writes. There is no need to Flush or
|
||||||
|
// Close such a Writer.
|
||||||
|
//
|
||||||
|
// Deprecated: the Writer returned is not suitable for many small writes, only
|
||||||
|
// for few large writes. Use NewBufferedWriter instead, which is efficient
|
||||||
|
// regardless of the frequency and shape of the writes, and remember to Close
|
||||||
|
// that Writer when done.
|
||||||
|
func NewWriter(w io.Writer) *Writer {
|
||||||
|
return &Writer{
|
||||||
|
w: w,
|
||||||
|
obuf: make([]byte, obufLen),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBufferedWriter returns a new Writer that compresses to w, using the
|
||||||
|
// framing format described at
|
||||||
|
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||||
|
//
|
||||||
|
// The Writer returned buffers writes. Users must call Close to guarantee all
|
||||||
|
// data has been forwarded to the underlying io.Writer. They may also call
|
||||||
|
// Flush zero or more times before calling Close.
|
||||||
|
func NewBufferedWriter(w io.Writer) *Writer {
|
||||||
|
return &Writer{
|
||||||
|
w: w,
|
||||||
|
ibuf: make([]byte, 0, maxBlockSize),
|
||||||
|
obuf: make([]byte, obufLen),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer is an io.Writer that can write Snappy-compressed bytes.
|
||||||
|
type Writer struct {
|
||||||
|
w io.Writer
|
||||||
|
err error
|
||||||
|
|
||||||
|
// ibuf is a buffer for the incoming (uncompressed) bytes.
|
||||||
|
//
|
||||||
|
// Its use is optional. For backwards compatibility, Writers created by the
|
||||||
|
// NewWriter function have ibuf == nil, do not buffer incoming bytes, and
|
||||||
|
// therefore do not need to be Flush'ed or Close'd.
|
||||||
|
ibuf []byte
|
||||||
|
|
||||||
|
// obuf is a buffer for the outgoing (compressed) bytes.
|
||||||
|
obuf []byte
|
||||||
|
|
||||||
|
// wroteStreamHeader is whether we have written the stream header.
|
||||||
|
wroteStreamHeader bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset discards the writer's state and switches the Snappy writer to write to
|
||||||
|
// w. This permits reusing a Writer rather than allocating a new one.
|
||||||
|
func (w *Writer) Reset(writer io.Writer) {
|
||||||
|
w.w = writer
|
||||||
|
w.err = nil
|
||||||
|
if w.ibuf != nil {
|
||||||
|
w.ibuf = w.ibuf[:0]
|
||||||
|
}
|
||||||
|
w.wroteStreamHeader = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write satisfies the io.Writer interface.
|
||||||
|
func (w *Writer) Write(p []byte) (nRet int, errRet error) {
|
||||||
|
if w.ibuf == nil {
|
||||||
|
// Do not buffer incoming bytes. This does not perform or compress well
|
||||||
|
// if the caller of Writer.Write writes many small slices. This
|
||||||
|
// behavior is therefore deprecated, but still supported for backwards
|
||||||
|
// compatibility with code that doesn't explicitly Flush or Close.
|
||||||
|
return w.write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The remainder of this method is based on bufio.Writer.Write from the
|
||||||
|
// standard library.
|
||||||
|
|
||||||
|
for len(p) > (cap(w.ibuf)-len(w.ibuf)) && w.err == nil {
|
||||||
|
var n int
|
||||||
|
if len(w.ibuf) == 0 {
|
||||||
|
// Large write, empty buffer.
|
||||||
|
// Write directly from p to avoid copy.
|
||||||
|
n, _ = w.write(p)
|
||||||
|
} else {
|
||||||
|
n = copy(w.ibuf[len(w.ibuf):cap(w.ibuf)], p)
|
||||||
|
w.ibuf = w.ibuf[:len(w.ibuf)+n]
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
|
nRet += n
|
||||||
|
p = p[n:]
|
||||||
|
}
|
||||||
|
if w.err != nil {
|
||||||
|
return nRet, w.err
|
||||||
|
}
|
||||||
|
n := copy(w.ibuf[len(w.ibuf):cap(w.ibuf)], p)
|
||||||
|
w.ibuf = w.ibuf[:len(w.ibuf)+n]
|
||||||
|
nRet += n
|
||||||
|
return nRet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) write(p []byte) (nRet int, errRet error) {
|
||||||
|
if w.err != nil {
|
||||||
|
return 0, w.err
|
||||||
|
}
|
||||||
|
for len(p) > 0 {
|
||||||
|
obufStart := len(magicChunk)
|
||||||
|
if !w.wroteStreamHeader {
|
||||||
|
w.wroteStreamHeader = true
|
||||||
|
copy(w.obuf, magicChunk)
|
||||||
|
obufStart = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var uncompressed []byte
|
||||||
|
if len(p) > maxBlockSize {
|
||||||
|
uncompressed, p = p[:maxBlockSize], p[maxBlockSize:]
|
||||||
|
} else {
|
||||||
|
uncompressed, p = p, nil
|
||||||
|
}
|
||||||
|
checksum := crc(uncompressed)
|
||||||
|
|
||||||
|
// Compress the buffer, discarding the result if the improvement
|
||||||
|
// isn't at least 12.5%.
|
||||||
|
compressed := Encode(w.obuf[obufHeaderLen:], uncompressed)
|
||||||
|
chunkType := uint8(chunkTypeCompressedData)
|
||||||
|
chunkLen := 4 + len(compressed)
|
||||||
|
obufEnd := obufHeaderLen + len(compressed)
|
||||||
|
if len(compressed) >= len(uncompressed)-len(uncompressed)/8 {
|
||||||
|
chunkType = chunkTypeUncompressedData
|
||||||
|
chunkLen = 4 + len(uncompressed)
|
||||||
|
obufEnd = obufHeaderLen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in the per-chunk header that comes before the body.
|
||||||
|
w.obuf[len(magicChunk)+0] = chunkType
|
||||||
|
w.obuf[len(magicChunk)+1] = uint8(chunkLen >> 0)
|
||||||
|
w.obuf[len(magicChunk)+2] = uint8(chunkLen >> 8)
|
||||||
|
w.obuf[len(magicChunk)+3] = uint8(chunkLen >> 16)
|
||||||
|
w.obuf[len(magicChunk)+4] = uint8(checksum >> 0)
|
||||||
|
w.obuf[len(magicChunk)+5] = uint8(checksum >> 8)
|
||||||
|
w.obuf[len(magicChunk)+6] = uint8(checksum >> 16)
|
||||||
|
w.obuf[len(magicChunk)+7] = uint8(checksum >> 24)
|
||||||
|
|
||||||
|
if _, err := w.w.Write(w.obuf[obufStart:obufEnd]); err != nil {
|
||||||
|
w.err = err
|
||||||
|
return nRet, err
|
||||||
|
}
|
||||||
|
if chunkType == chunkTypeUncompressedData {
|
||||||
|
if _, err := w.w.Write(uncompressed); err != nil {
|
||||||
|
w.err = err
|
||||||
|
return nRet, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nRet += len(uncompressed)
|
||||||
|
}
|
||||||
|
return nRet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush flushes the Writer to its underlying io.Writer.
|
||||||
|
func (w *Writer) Flush() error {
|
||||||
|
if w.err != nil {
|
||||||
|
return w.err
|
||||||
|
}
|
||||||
|
if len(w.ibuf) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.write(w.ibuf)
|
||||||
|
w.ibuf = w.ibuf[:0]
|
||||||
|
return w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close calls Flush and then closes the Writer.
|
||||||
|
func (w *Writer) Close() error {
|
||||||
|
w.Flush()
|
||||||
|
ret := w.err
|
||||||
|
if w.err == nil {
|
||||||
|
w.err = errClosed
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !noasm
|
||||||
|
|
||||||
|
package snappy
|
||||||
|
|
||||||
|
// emitLiteral has the same semantics as in encode_other.go.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func emitLiteral(dst, lit []byte) int
|
||||||
|
|
||||||
|
// emitCopy has the same semantics as in encode_other.go.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func emitCopy(dst []byte, offset, length int) int
|
||||||
|
|
||||||
|
// extendMatch has the same semantics as in encode_other.go.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func extendMatch(src []byte, i, j int) int
|
||||||
|
|
||||||
|
// encodeBlock has the same semantics as in encode_other.go.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func encodeBlock(dst, src []byte) (d int)
|
|
@ -0,0 +1,730 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !noasm
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// The XXX lines assemble on Go 1.4, 1.5 and 1.7, but not 1.6, due to a
|
||||||
|
// Go toolchain regression. See https://github.com/golang/go/issues/15426 and
|
||||||
|
// https://github.com/golang/snappy/issues/29
|
||||||
|
//
|
||||||
|
// As a workaround, the package was built with a known good assembler, and
|
||||||
|
// those instructions were disassembled by "objdump -d" to yield the
|
||||||
|
// 4e 0f b7 7c 5c 78 movzwq 0x78(%rsp,%r11,2),%r15
|
||||||
|
// style comments, in AT&T asm syntax. Note that rsp here is a physical
|
||||||
|
// register, not Go/asm's SP pseudo-register (see https://golang.org/doc/asm).
|
||||||
|
// The instructions were then encoded as "BYTE $0x.." sequences, which assemble
|
||||||
|
// fine on Go 1.6.
|
||||||
|
|
||||||
|
// The asm code generally follows the pure Go code in encode_other.go, except
|
||||||
|
// where marked with a "!!!".
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// func emitLiteral(dst, lit []byte) int
|
||||||
|
//
|
||||||
|
// All local variables fit into registers. The register allocation:
|
||||||
|
// - AX len(lit)
|
||||||
|
// - BX n
|
||||||
|
// - DX return value
|
||||||
|
// - DI &dst[i]
|
||||||
|
// - R10 &lit[0]
|
||||||
|
//
|
||||||
|
// The 24 bytes of stack space is to call runtime·memmove.
|
||||||
|
//
|
||||||
|
// The unusual register allocation of local variables, such as R10 for the
|
||||||
|
// source pointer, matches the allocation used at the call site in encodeBlock,
|
||||||
|
// which makes it easier to manually inline this function.
|
||||||
|
TEXT ·emitLiteral(SB), NOSPLIT, $24-56
|
||||||
|
MOVQ dst_base+0(FP), DI
|
||||||
|
MOVQ lit_base+24(FP), R10
|
||||||
|
MOVQ lit_len+32(FP), AX
|
||||||
|
MOVQ AX, DX
|
||||||
|
MOVL AX, BX
|
||||||
|
SUBL $1, BX
|
||||||
|
|
||||||
|
CMPL BX, $60
|
||||||
|
JLT oneByte
|
||||||
|
CMPL BX, $256
|
||||||
|
JLT twoBytes
|
||||||
|
|
||||||
|
threeBytes:
|
||||||
|
MOVB $0xf4, 0(DI)
|
||||||
|
MOVW BX, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
ADDQ $3, DX
|
||||||
|
JMP memmove
|
||||||
|
|
||||||
|
twoBytes:
|
||||||
|
MOVB $0xf0, 0(DI)
|
||||||
|
MOVB BX, 1(DI)
|
||||||
|
ADDQ $2, DI
|
||||||
|
ADDQ $2, DX
|
||||||
|
JMP memmove
|
||||||
|
|
||||||
|
oneByte:
|
||||||
|
SHLB $2, BX
|
||||||
|
MOVB BX, 0(DI)
|
||||||
|
ADDQ $1, DI
|
||||||
|
ADDQ $1, DX
|
||||||
|
|
||||||
|
memmove:
|
||||||
|
MOVQ DX, ret+48(FP)
|
||||||
|
|
||||||
|
// copy(dst[i:], lit)
|
||||||
|
//
|
||||||
|
// This means calling runtime·memmove(&dst[i], &lit[0], len(lit)), so we push
|
||||||
|
// DI, R10 and AX as arguments.
|
||||||
|
MOVQ DI, 0(SP)
|
||||||
|
MOVQ R10, 8(SP)
|
||||||
|
MOVQ AX, 16(SP)
|
||||||
|
CALL runtime·memmove(SB)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// func emitCopy(dst []byte, offset, length int) int
|
||||||
|
//
|
||||||
|
// All local variables fit into registers. The register allocation:
|
||||||
|
// - AX length
|
||||||
|
// - SI &dst[0]
|
||||||
|
// - DI &dst[i]
|
||||||
|
// - R11 offset
|
||||||
|
//
|
||||||
|
// The unusual register allocation of local variables, such as R11 for the
|
||||||
|
// offset, matches the allocation used at the call site in encodeBlock, which
|
||||||
|
// makes it easier to manually inline this function.
|
||||||
|
TEXT ·emitCopy(SB), NOSPLIT, $0-48
|
||||||
|
MOVQ dst_base+0(FP), DI
|
||||||
|
MOVQ DI, SI
|
||||||
|
MOVQ offset+24(FP), R11
|
||||||
|
MOVQ length+32(FP), AX
|
||||||
|
|
||||||
|
loop0:
|
||||||
|
// for length >= 68 { etc }
|
||||||
|
CMPL AX, $68
|
||||||
|
JLT step1
|
||||||
|
|
||||||
|
// Emit a length 64 copy, encoded as 3 bytes.
|
||||||
|
MOVB $0xfe, 0(DI)
|
||||||
|
MOVW R11, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
SUBL $64, AX
|
||||||
|
JMP loop0
|
||||||
|
|
||||||
|
step1:
|
||||||
|
// if length > 64 { etc }
|
||||||
|
CMPL AX, $64
|
||||||
|
JLE step2
|
||||||
|
|
||||||
|
// Emit a length 60 copy, encoded as 3 bytes.
|
||||||
|
MOVB $0xee, 0(DI)
|
||||||
|
MOVW R11, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
SUBL $60, AX
|
||||||
|
|
||||||
|
step2:
|
||||||
|
// if length >= 12 || offset >= 2048 { goto step3 }
|
||||||
|
CMPL AX, $12
|
||||||
|
JGE step3
|
||||||
|
CMPL R11, $2048
|
||||||
|
JGE step3
|
||||||
|
|
||||||
|
// Emit the remaining copy, encoded as 2 bytes.
|
||||||
|
MOVB R11, 1(DI)
|
||||||
|
SHRL $8, R11
|
||||||
|
SHLB $5, R11
|
||||||
|
SUBB $4, AX
|
||||||
|
SHLB $2, AX
|
||||||
|
ORB AX, R11
|
||||||
|
ORB $1, R11
|
||||||
|
MOVB R11, 0(DI)
|
||||||
|
ADDQ $2, DI
|
||||||
|
|
||||||
|
// Return the number of bytes written.
|
||||||
|
SUBQ SI, DI
|
||||||
|
MOVQ DI, ret+40(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
step3:
|
||||||
|
// Emit the remaining copy, encoded as 3 bytes.
|
||||||
|
SUBL $1, AX
|
||||||
|
SHLB $2, AX
|
||||||
|
ORB $2, AX
|
||||||
|
MOVB AX, 0(DI)
|
||||||
|
MOVW R11, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
|
||||||
|
// Return the number of bytes written.
|
||||||
|
SUBQ SI, DI
|
||||||
|
MOVQ DI, ret+40(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// func extendMatch(src []byte, i, j int) int
|
||||||
|
//
|
||||||
|
// All local variables fit into registers. The register allocation:
|
||||||
|
// - DX &src[0]
|
||||||
|
// - SI &src[j]
|
||||||
|
// - R13 &src[len(src) - 8]
|
||||||
|
// - R14 &src[len(src)]
|
||||||
|
// - R15 &src[i]
|
||||||
|
//
|
||||||
|
// The unusual register allocation of local variables, such as R15 for a source
|
||||||
|
// pointer, matches the allocation used at the call site in encodeBlock, which
|
||||||
|
// makes it easier to manually inline this function.
|
||||||
|
TEXT ·extendMatch(SB), NOSPLIT, $0-48
|
||||||
|
MOVQ src_base+0(FP), DX
|
||||||
|
MOVQ src_len+8(FP), R14
|
||||||
|
MOVQ i+24(FP), R15
|
||||||
|
MOVQ j+32(FP), SI
|
||||||
|
ADDQ DX, R14
|
||||||
|
ADDQ DX, R15
|
||||||
|
ADDQ DX, SI
|
||||||
|
MOVQ R14, R13
|
||||||
|
SUBQ $8, R13
|
||||||
|
|
||||||
|
cmp8:
|
||||||
|
// As long as we are 8 or more bytes before the end of src, we can load and
|
||||||
|
// compare 8 bytes at a time. If those 8 bytes are equal, repeat.
|
||||||
|
CMPQ SI, R13
|
||||||
|
JA cmp1
|
||||||
|
MOVQ (R15), AX
|
||||||
|
MOVQ (SI), BX
|
||||||
|
CMPQ AX, BX
|
||||||
|
JNE bsf
|
||||||
|
ADDQ $8, R15
|
||||||
|
ADDQ $8, SI
|
||||||
|
JMP cmp8
|
||||||
|
|
||||||
|
bsf:
|
||||||
|
// If those 8 bytes were not equal, XOR the two 8 byte values, and return
|
||||||
|
// the index of the first byte that differs. The BSF instruction finds the
|
||||||
|
// least significant 1 bit, the amd64 architecture is little-endian, and
|
||||||
|
// the shift by 3 converts a bit index to a byte index.
|
||||||
|
XORQ AX, BX
|
||||||
|
BSFQ BX, BX
|
||||||
|
SHRQ $3, BX
|
||||||
|
ADDQ BX, SI
|
||||||
|
|
||||||
|
// Convert from &src[ret] to ret.
|
||||||
|
SUBQ DX, SI
|
||||||
|
MOVQ SI, ret+40(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
cmp1:
|
||||||
|
// In src's tail, compare 1 byte at a time.
|
||||||
|
CMPQ SI, R14
|
||||||
|
JAE extendMatchEnd
|
||||||
|
MOVB (R15), AX
|
||||||
|
MOVB (SI), BX
|
||||||
|
CMPB AX, BX
|
||||||
|
JNE extendMatchEnd
|
||||||
|
ADDQ $1, R15
|
||||||
|
ADDQ $1, SI
|
||||||
|
JMP cmp1
|
||||||
|
|
||||||
|
extendMatchEnd:
|
||||||
|
// Convert from &src[ret] to ret.
|
||||||
|
SUBQ DX, SI
|
||||||
|
MOVQ SI, ret+40(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// func encodeBlock(dst, src []byte) (d int)
|
||||||
|
//
|
||||||
|
// All local variables fit into registers, other than "var table". The register
|
||||||
|
// allocation:
|
||||||
|
// - AX . .
|
||||||
|
// - BX . .
|
||||||
|
// - CX 56 shift (note that amd64 shifts by non-immediates must use CX).
|
||||||
|
// - DX 64 &src[0], tableSize
|
||||||
|
// - SI 72 &src[s]
|
||||||
|
// - DI 80 &dst[d]
|
||||||
|
// - R9 88 sLimit
|
||||||
|
// - R10 . &src[nextEmit]
|
||||||
|
// - R11 96 prevHash, currHash, nextHash, offset
|
||||||
|
// - R12 104 &src[base], skip
|
||||||
|
// - R13 . &src[nextS], &src[len(src) - 8]
|
||||||
|
// - R14 . len(src), bytesBetweenHashLookups, &src[len(src)], x
|
||||||
|
// - R15 112 candidate
|
||||||
|
//
|
||||||
|
// The second column (56, 64, etc) is the stack offset to spill the registers
|
||||||
|
// when calling other functions. We could pack this slightly tighter, but it's
|
||||||
|
// simpler to have a dedicated spill map independent of the function called.
|
||||||
|
//
|
||||||
|
// "var table [maxTableSize]uint16" takes up 32768 bytes of stack space. An
|
||||||
|
// extra 56 bytes, to call other functions, and an extra 64 bytes, to spill
|
||||||
|
// local variables (registers) during calls gives 32768 + 56 + 64 = 32888.
|
||||||
|
TEXT ·encodeBlock(SB), 0, $32888-56
|
||||||
|
MOVQ dst_base+0(FP), DI
|
||||||
|
MOVQ src_base+24(FP), SI
|
||||||
|
MOVQ src_len+32(FP), R14
|
||||||
|
|
||||||
|
// shift, tableSize := uint32(32-8), 1<<8
|
||||||
|
MOVQ $24, CX
|
||||||
|
MOVQ $256, DX
|
||||||
|
|
||||||
|
calcShift:
|
||||||
|
// for ; tableSize < maxTableSize && tableSize < len(src); tableSize *= 2 {
|
||||||
|
// shift--
|
||||||
|
// }
|
||||||
|
CMPQ DX, $16384
|
||||||
|
JGE varTable
|
||||||
|
CMPQ DX, R14
|
||||||
|
JGE varTable
|
||||||
|
SUBQ $1, CX
|
||||||
|
SHLQ $1, DX
|
||||||
|
JMP calcShift
|
||||||
|
|
||||||
|
varTable:
|
||||||
|
// var table [maxTableSize]uint16
|
||||||
|
//
|
||||||
|
// In the asm code, unlike the Go code, we can zero-initialize only the
|
||||||
|
// first tableSize elements. Each uint16 element is 2 bytes and each MOVOU
|
||||||
|
// writes 16 bytes, so we can do only tableSize/8 writes instead of the
|
||||||
|
// 2048 writes that would zero-initialize all of table's 32768 bytes.
|
||||||
|
SHRQ $3, DX
|
||||||
|
LEAQ table-32768(SP), BX
|
||||||
|
PXOR X0, X0
|
||||||
|
|
||||||
|
memclr:
|
||||||
|
MOVOU X0, 0(BX)
|
||||||
|
ADDQ $16, BX
|
||||||
|
SUBQ $1, DX
|
||||||
|
JNZ memclr
|
||||||
|
|
||||||
|
// !!! DX = &src[0]
|
||||||
|
MOVQ SI, DX
|
||||||
|
|
||||||
|
// sLimit := len(src) - inputMargin
|
||||||
|
MOVQ R14, R9
|
||||||
|
SUBQ $15, R9
|
||||||
|
|
||||||
|
// !!! Pre-emptively spill CX, DX and R9 to the stack. Their values don't
|
||||||
|
// change for the rest of the function.
|
||||||
|
MOVQ CX, 56(SP)
|
||||||
|
MOVQ DX, 64(SP)
|
||||||
|
MOVQ R9, 88(SP)
|
||||||
|
|
||||||
|
// nextEmit := 0
|
||||||
|
MOVQ DX, R10
|
||||||
|
|
||||||
|
// s := 1
|
||||||
|
ADDQ $1, SI
|
||||||
|
|
||||||
|
// nextHash := hash(load32(src, s), shift)
|
||||||
|
MOVL 0(SI), R11
|
||||||
|
IMULL $0x1e35a7bd, R11
|
||||||
|
SHRL CX, R11
|
||||||
|
|
||||||
|
outer:
|
||||||
|
// for { etc }
|
||||||
|
|
||||||
|
// skip := 32
|
||||||
|
MOVQ $32, R12
|
||||||
|
|
||||||
|
// nextS := s
|
||||||
|
MOVQ SI, R13
|
||||||
|
|
||||||
|
// candidate := 0
|
||||||
|
MOVQ $0, R15
|
||||||
|
|
||||||
|
inner0:
|
||||||
|
// for { etc }
|
||||||
|
|
||||||
|
// s := nextS
|
||||||
|
MOVQ R13, SI
|
||||||
|
|
||||||
|
// bytesBetweenHashLookups := skip >> 5
|
||||||
|
MOVQ R12, R14
|
||||||
|
SHRQ $5, R14
|
||||||
|
|
||||||
|
// nextS = s + bytesBetweenHashLookups
|
||||||
|
ADDQ R14, R13
|
||||||
|
|
||||||
|
// skip += bytesBetweenHashLookups
|
||||||
|
ADDQ R14, R12
|
||||||
|
|
||||||
|
// if nextS > sLimit { goto emitRemainder }
|
||||||
|
MOVQ R13, AX
|
||||||
|
SUBQ DX, AX
|
||||||
|
CMPQ AX, R9
|
||||||
|
JA emitRemainder
|
||||||
|
|
||||||
|
// candidate = int(table[nextHash])
|
||||||
|
// XXX: MOVWQZX table-32768(SP)(R11*2), R15
|
||||||
|
// XXX: 4e 0f b7 7c 5c 78 movzwq 0x78(%rsp,%r11,2),%r15
|
||||||
|
BYTE $0x4e
|
||||||
|
BYTE $0x0f
|
||||||
|
BYTE $0xb7
|
||||||
|
BYTE $0x7c
|
||||||
|
BYTE $0x5c
|
||||||
|
BYTE $0x78
|
||||||
|
|
||||||
|
// table[nextHash] = uint16(s)
|
||||||
|
MOVQ SI, AX
|
||||||
|
SUBQ DX, AX
|
||||||
|
|
||||||
|
// XXX: MOVW AX, table-32768(SP)(R11*2)
|
||||||
|
// XXX: 66 42 89 44 5c 78 mov %ax,0x78(%rsp,%r11,2)
|
||||||
|
BYTE $0x66
|
||||||
|
BYTE $0x42
|
||||||
|
BYTE $0x89
|
||||||
|
BYTE $0x44
|
||||||
|
BYTE $0x5c
|
||||||
|
BYTE $0x78
|
||||||
|
|
||||||
|
// nextHash = hash(load32(src, nextS), shift)
|
||||||
|
MOVL 0(R13), R11
|
||||||
|
IMULL $0x1e35a7bd, R11
|
||||||
|
SHRL CX, R11
|
||||||
|
|
||||||
|
// if load32(src, s) != load32(src, candidate) { continue } break
|
||||||
|
MOVL 0(SI), AX
|
||||||
|
MOVL (DX)(R15*1), BX
|
||||||
|
CMPL AX, BX
|
||||||
|
JNE inner0
|
||||||
|
|
||||||
|
fourByteMatch:
|
||||||
|
// As per the encode_other.go code:
|
||||||
|
//
|
||||||
|
// A 4-byte match has been found. We'll later see etc.
|
||||||
|
|
||||||
|
// !!! Jump to a fast path for short (<= 16 byte) literals. See the comment
|
||||||
|
// on inputMargin in encode.go.
|
||||||
|
MOVQ SI, AX
|
||||||
|
SUBQ R10, AX
|
||||||
|
CMPQ AX, $16
|
||||||
|
JLE emitLiteralFastPath
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Begin inline of the emitLiteral call.
|
||||||
|
//
|
||||||
|
// d += emitLiteral(dst[d:], src[nextEmit:s])
|
||||||
|
|
||||||
|
MOVL AX, BX
|
||||||
|
SUBL $1, BX
|
||||||
|
|
||||||
|
CMPL BX, $60
|
||||||
|
JLT inlineEmitLiteralOneByte
|
||||||
|
CMPL BX, $256
|
||||||
|
JLT inlineEmitLiteralTwoBytes
|
||||||
|
|
||||||
|
inlineEmitLiteralThreeBytes:
|
||||||
|
MOVB $0xf4, 0(DI)
|
||||||
|
MOVW BX, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
JMP inlineEmitLiteralMemmove
|
||||||
|
|
||||||
|
inlineEmitLiteralTwoBytes:
|
||||||
|
MOVB $0xf0, 0(DI)
|
||||||
|
MOVB BX, 1(DI)
|
||||||
|
ADDQ $2, DI
|
||||||
|
JMP inlineEmitLiteralMemmove
|
||||||
|
|
||||||
|
inlineEmitLiteralOneByte:
|
||||||
|
SHLB $2, BX
|
||||||
|
MOVB BX, 0(DI)
|
||||||
|
ADDQ $1, DI
|
||||||
|
|
||||||
|
inlineEmitLiteralMemmove:
|
||||||
|
// Spill local variables (registers) onto the stack; call; unspill.
|
||||||
|
//
|
||||||
|
// copy(dst[i:], lit)
|
||||||
|
//
|
||||||
|
// This means calling runtime·memmove(&dst[i], &lit[0], len(lit)), so we push
|
||||||
|
// DI, R10 and AX as arguments.
|
||||||
|
MOVQ DI, 0(SP)
|
||||||
|
MOVQ R10, 8(SP)
|
||||||
|
MOVQ AX, 16(SP)
|
||||||
|
ADDQ AX, DI // Finish the "d +=" part of "d += emitLiteral(etc)".
|
||||||
|
MOVQ SI, 72(SP)
|
||||||
|
MOVQ DI, 80(SP)
|
||||||
|
MOVQ R15, 112(SP)
|
||||||
|
CALL runtime·memmove(SB)
|
||||||
|
MOVQ 56(SP), CX
|
||||||
|
MOVQ 64(SP), DX
|
||||||
|
MOVQ 72(SP), SI
|
||||||
|
MOVQ 80(SP), DI
|
||||||
|
MOVQ 88(SP), R9
|
||||||
|
MOVQ 112(SP), R15
|
||||||
|
JMP inner1
|
||||||
|
|
||||||
|
inlineEmitLiteralEnd:
|
||||||
|
// End inline of the emitLiteral call.
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
emitLiteralFastPath:
|
||||||
|
// !!! Emit the 1-byte encoding "uint8(len(lit)-1)<<2".
|
||||||
|
MOVB AX, BX
|
||||||
|
SUBB $1, BX
|
||||||
|
SHLB $2, BX
|
||||||
|
MOVB BX, (DI)
|
||||||
|
ADDQ $1, DI
|
||||||
|
|
||||||
|
// !!! Implement the copy from lit to dst as a 16-byte load and store.
|
||||||
|
// (Encode's documentation says that dst and src must not overlap.)
|
||||||
|
//
|
||||||
|
// This always copies 16 bytes, instead of only len(lit) bytes, but that's
|
||||||
|
// OK. Subsequent iterations will fix up the overrun.
|
||||||
|
//
|
||||||
|
// Note that on amd64, it is legal and cheap to issue unaligned 8-byte or
|
||||||
|
// 16-byte loads and stores. This technique probably wouldn't be as
|
||||||
|
// effective on architectures that are fussier about alignment.
|
||||||
|
MOVOU 0(R10), X0
|
||||||
|
MOVOU X0, 0(DI)
|
||||||
|
ADDQ AX, DI
|
||||||
|
|
||||||
|
inner1:
|
||||||
|
// for { etc }
|
||||||
|
|
||||||
|
// base := s
|
||||||
|
MOVQ SI, R12
|
||||||
|
|
||||||
|
// !!! offset := base - candidate
|
||||||
|
MOVQ R12, R11
|
||||||
|
SUBQ R15, R11
|
||||||
|
SUBQ DX, R11
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Begin inline of the extendMatch call.
|
||||||
|
//
|
||||||
|
// s = extendMatch(src, candidate+4, s+4)
|
||||||
|
|
||||||
|
// !!! R14 = &src[len(src)]
|
||||||
|
MOVQ src_len+32(FP), R14
|
||||||
|
ADDQ DX, R14
|
||||||
|
|
||||||
|
// !!! R13 = &src[len(src) - 8]
|
||||||
|
MOVQ R14, R13
|
||||||
|
SUBQ $8, R13
|
||||||
|
|
||||||
|
// !!! R15 = &src[candidate + 4]
|
||||||
|
ADDQ $4, R15
|
||||||
|
ADDQ DX, R15
|
||||||
|
|
||||||
|
// !!! s += 4
|
||||||
|
ADDQ $4, SI
|
||||||
|
|
||||||
|
inlineExtendMatchCmp8:
|
||||||
|
// As long as we are 8 or more bytes before the end of src, we can load and
|
||||||
|
// compare 8 bytes at a time. If those 8 bytes are equal, repeat.
|
||||||
|
CMPQ SI, R13
|
||||||
|
JA inlineExtendMatchCmp1
|
||||||
|
MOVQ (R15), AX
|
||||||
|
MOVQ (SI), BX
|
||||||
|
CMPQ AX, BX
|
||||||
|
JNE inlineExtendMatchBSF
|
||||||
|
ADDQ $8, R15
|
||||||
|
ADDQ $8, SI
|
||||||
|
JMP inlineExtendMatchCmp8
|
||||||
|
|
||||||
|
inlineExtendMatchBSF:
|
||||||
|
// If those 8 bytes were not equal, XOR the two 8 byte values, and return
|
||||||
|
// the index of the first byte that differs. The BSF instruction finds the
|
||||||
|
// least significant 1 bit, the amd64 architecture is little-endian, and
|
||||||
|
// the shift by 3 converts a bit index to a byte index.
|
||||||
|
XORQ AX, BX
|
||||||
|
BSFQ BX, BX
|
||||||
|
SHRQ $3, BX
|
||||||
|
ADDQ BX, SI
|
||||||
|
JMP inlineExtendMatchEnd
|
||||||
|
|
||||||
|
inlineExtendMatchCmp1:
|
||||||
|
// In src's tail, compare 1 byte at a time.
|
||||||
|
CMPQ SI, R14
|
||||||
|
JAE inlineExtendMatchEnd
|
||||||
|
MOVB (R15), AX
|
||||||
|
MOVB (SI), BX
|
||||||
|
CMPB AX, BX
|
||||||
|
JNE inlineExtendMatchEnd
|
||||||
|
ADDQ $1, R15
|
||||||
|
ADDQ $1, SI
|
||||||
|
JMP inlineExtendMatchCmp1
|
||||||
|
|
||||||
|
inlineExtendMatchEnd:
|
||||||
|
// End inline of the extendMatch call.
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Begin inline of the emitCopy call.
|
||||||
|
//
|
||||||
|
// d += emitCopy(dst[d:], base-candidate, s-base)
|
||||||
|
|
||||||
|
// !!! length := s - base
|
||||||
|
MOVQ SI, AX
|
||||||
|
SUBQ R12, AX
|
||||||
|
|
||||||
|
inlineEmitCopyLoop0:
|
||||||
|
// for length >= 68 { etc }
|
||||||
|
CMPL AX, $68
|
||||||
|
JLT inlineEmitCopyStep1
|
||||||
|
|
||||||
|
// Emit a length 64 copy, encoded as 3 bytes.
|
||||||
|
MOVB $0xfe, 0(DI)
|
||||||
|
MOVW R11, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
SUBL $64, AX
|
||||||
|
JMP inlineEmitCopyLoop0
|
||||||
|
|
||||||
|
inlineEmitCopyStep1:
|
||||||
|
// if length > 64 { etc }
|
||||||
|
CMPL AX, $64
|
||||||
|
JLE inlineEmitCopyStep2
|
||||||
|
|
||||||
|
// Emit a length 60 copy, encoded as 3 bytes.
|
||||||
|
MOVB $0xee, 0(DI)
|
||||||
|
MOVW R11, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
SUBL $60, AX
|
||||||
|
|
||||||
|
inlineEmitCopyStep2:
|
||||||
|
// if length >= 12 || offset >= 2048 { goto inlineEmitCopyStep3 }
|
||||||
|
CMPL AX, $12
|
||||||
|
JGE inlineEmitCopyStep3
|
||||||
|
CMPL R11, $2048
|
||||||
|
JGE inlineEmitCopyStep3
|
||||||
|
|
||||||
|
// Emit the remaining copy, encoded as 2 bytes.
|
||||||
|
MOVB R11, 1(DI)
|
||||||
|
SHRL $8, R11
|
||||||
|
SHLB $5, R11
|
||||||
|
SUBB $4, AX
|
||||||
|
SHLB $2, AX
|
||||||
|
ORB AX, R11
|
||||||
|
ORB $1, R11
|
||||||
|
MOVB R11, 0(DI)
|
||||||
|
ADDQ $2, DI
|
||||||
|
JMP inlineEmitCopyEnd
|
||||||
|
|
||||||
|
inlineEmitCopyStep3:
|
||||||
|
// Emit the remaining copy, encoded as 3 bytes.
|
||||||
|
SUBL $1, AX
|
||||||
|
SHLB $2, AX
|
||||||
|
ORB $2, AX
|
||||||
|
MOVB AX, 0(DI)
|
||||||
|
MOVW R11, 1(DI)
|
||||||
|
ADDQ $3, DI
|
||||||
|
|
||||||
|
inlineEmitCopyEnd:
|
||||||
|
// End inline of the emitCopy call.
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
// nextEmit = s
|
||||||
|
MOVQ SI, R10
|
||||||
|
|
||||||
|
// if s >= sLimit { goto emitRemainder }
|
||||||
|
MOVQ SI, AX
|
||||||
|
SUBQ DX, AX
|
||||||
|
CMPQ AX, R9
|
||||||
|
JAE emitRemainder
|
||||||
|
|
||||||
|
// As per the encode_other.go code:
|
||||||
|
//
|
||||||
|
// We could immediately etc.
|
||||||
|
|
||||||
|
// x := load64(src, s-1)
|
||||||
|
MOVQ -1(SI), R14
|
||||||
|
|
||||||
|
// prevHash := hash(uint32(x>>0), shift)
|
||||||
|
MOVL R14, R11
|
||||||
|
IMULL $0x1e35a7bd, R11
|
||||||
|
SHRL CX, R11
|
||||||
|
|
||||||
|
// table[prevHash] = uint16(s-1)
|
||||||
|
MOVQ SI, AX
|
||||||
|
SUBQ DX, AX
|
||||||
|
SUBQ $1, AX
|
||||||
|
|
||||||
|
// XXX: MOVW AX, table-32768(SP)(R11*2)
|
||||||
|
// XXX: 66 42 89 44 5c 78 mov %ax,0x78(%rsp,%r11,2)
|
||||||
|
BYTE $0x66
|
||||||
|
BYTE $0x42
|
||||||
|
BYTE $0x89
|
||||||
|
BYTE $0x44
|
||||||
|
BYTE $0x5c
|
||||||
|
BYTE $0x78
|
||||||
|
|
||||||
|
// currHash := hash(uint32(x>>8), shift)
|
||||||
|
SHRQ $8, R14
|
||||||
|
MOVL R14, R11
|
||||||
|
IMULL $0x1e35a7bd, R11
|
||||||
|
SHRL CX, R11
|
||||||
|
|
||||||
|
// candidate = int(table[currHash])
|
||||||
|
// XXX: MOVWQZX table-32768(SP)(R11*2), R15
|
||||||
|
// XXX: 4e 0f b7 7c 5c 78 movzwq 0x78(%rsp,%r11,2),%r15
|
||||||
|
BYTE $0x4e
|
||||||
|
BYTE $0x0f
|
||||||
|
BYTE $0xb7
|
||||||
|
BYTE $0x7c
|
||||||
|
BYTE $0x5c
|
||||||
|
BYTE $0x78
|
||||||
|
|
||||||
|
// table[currHash] = uint16(s)
|
||||||
|
ADDQ $1, AX
|
||||||
|
|
||||||
|
// XXX: MOVW AX, table-32768(SP)(R11*2)
|
||||||
|
// XXX: 66 42 89 44 5c 78 mov %ax,0x78(%rsp,%r11,2)
|
||||||
|
BYTE $0x66
|
||||||
|
BYTE $0x42
|
||||||
|
BYTE $0x89
|
||||||
|
BYTE $0x44
|
||||||
|
BYTE $0x5c
|
||||||
|
BYTE $0x78
|
||||||
|
|
||||||
|
// if uint32(x>>8) == load32(src, candidate) { continue }
|
||||||
|
MOVL (DX)(R15*1), BX
|
||||||
|
CMPL R14, BX
|
||||||
|
JEQ inner1
|
||||||
|
|
||||||
|
// nextHash = hash(uint32(x>>16), shift)
|
||||||
|
SHRQ $8, R14
|
||||||
|
MOVL R14, R11
|
||||||
|
IMULL $0x1e35a7bd, R11
|
||||||
|
SHRL CX, R11
|
||||||
|
|
||||||
|
// s++
|
||||||
|
ADDQ $1, SI
|
||||||
|
|
||||||
|
// break out of the inner1 for loop, i.e. continue the outer loop.
|
||||||
|
JMP outer
|
||||||
|
|
||||||
|
emitRemainder:
|
||||||
|
// if nextEmit < len(src) { etc }
|
||||||
|
MOVQ src_len+32(FP), AX
|
||||||
|
ADDQ DX, AX
|
||||||
|
CMPQ R10, AX
|
||||||
|
JEQ encodeBlockEnd
|
||||||
|
|
||||||
|
// d += emitLiteral(dst[d:], src[nextEmit:])
|
||||||
|
//
|
||||||
|
// Push args.
|
||||||
|
MOVQ DI, 0(SP)
|
||||||
|
MOVQ $0, 8(SP) // Unnecessary, as the callee ignores it, but conservative.
|
||||||
|
MOVQ $0, 16(SP) // Unnecessary, as the callee ignores it, but conservative.
|
||||||
|
MOVQ R10, 24(SP)
|
||||||
|
SUBQ R10, AX
|
||||||
|
MOVQ AX, 32(SP)
|
||||||
|
MOVQ AX, 40(SP) // Unnecessary, as the callee ignores it, but conservative.
|
||||||
|
|
||||||
|
// Spill local variables (registers) onto the stack; call; unspill.
|
||||||
|
MOVQ DI, 80(SP)
|
||||||
|
CALL ·emitLiteral(SB)
|
||||||
|
MOVQ 80(SP), DI
|
||||||
|
|
||||||
|
// Finish the "d +=" part of "d += emitLiteral(etc)".
|
||||||
|
ADDQ 48(SP), DI
|
||||||
|
|
||||||
|
encodeBlockEnd:
|
||||||
|
MOVQ dst_base+0(FP), AX
|
||||||
|
SUBQ AX, DI
|
||||||
|
MOVQ DI, d+48(FP)
|
||||||
|
RET
|
|
@ -0,0 +1,238 @@
|
||||||
|
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !amd64 appengine !gc noasm
|
||||||
|
|
||||||
|
package snappy
|
||||||
|
|
||||||
|
func load32(b []byte, i int) uint32 {
|
||||||
|
b = b[i : i+4 : len(b)] // Help the compiler eliminate bounds checks on the next line.
|
||||||
|
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||||
|
}
|
||||||
|
|
||||||
|
func load64(b []byte, i int) uint64 {
|
||||||
|
b = b[i : i+8 : len(b)] // Help the compiler eliminate bounds checks on the next line.
|
||||||
|
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
|
||||||
|
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitLiteral writes a literal chunk and returns the number of bytes written.
|
||||||
|
//
|
||||||
|
// It assumes that:
|
||||||
|
// dst is long enough to hold the encoded bytes
|
||||||
|
// 1 <= len(lit) && len(lit) <= 65536
|
||||||
|
func emitLiteral(dst, lit []byte) int {
|
||||||
|
i, n := 0, uint(len(lit)-1)
|
||||||
|
switch {
|
||||||
|
case n < 60:
|
||||||
|
dst[0] = uint8(n)<<2 | tagLiteral
|
||||||
|
i = 1
|
||||||
|
case n < 1<<8:
|
||||||
|
dst[0] = 60<<2 | tagLiteral
|
||||||
|
dst[1] = uint8(n)
|
||||||
|
i = 2
|
||||||
|
default:
|
||||||
|
dst[0] = 61<<2 | tagLiteral
|
||||||
|
dst[1] = uint8(n)
|
||||||
|
dst[2] = uint8(n >> 8)
|
||||||
|
i = 3
|
||||||
|
}
|
||||||
|
return i + copy(dst[i:], lit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitCopy writes a copy chunk and returns the number of bytes written.
|
||||||
|
//
|
||||||
|
// It assumes that:
|
||||||
|
// dst is long enough to hold the encoded bytes
|
||||||
|
// 1 <= offset && offset <= 65535
|
||||||
|
// 4 <= length && length <= 65535
|
||||||
|
func emitCopy(dst []byte, offset, length int) int {
|
||||||
|
i := 0
|
||||||
|
// The maximum length for a single tagCopy1 or tagCopy2 op is 64 bytes. The
|
||||||
|
// threshold for this loop is a little higher (at 68 = 64 + 4), and the
|
||||||
|
// length emitted down below is is a little lower (at 60 = 64 - 4), because
|
||||||
|
// it's shorter to encode a length 67 copy as a length 60 tagCopy2 followed
|
||||||
|
// by a length 7 tagCopy1 (which encodes as 3+2 bytes) than to encode it as
|
||||||
|
// a length 64 tagCopy2 followed by a length 3 tagCopy2 (which encodes as
|
||||||
|
// 3+3 bytes). The magic 4 in the 64±4 is because the minimum length for a
|
||||||
|
// tagCopy1 op is 4 bytes, which is why a length 3 copy has to be an
|
||||||
|
// encodes-as-3-bytes tagCopy2 instead of an encodes-as-2-bytes tagCopy1.
|
||||||
|
for length >= 68 {
|
||||||
|
// Emit a length 64 copy, encoded as 3 bytes.
|
||||||
|
dst[i+0] = 63<<2 | tagCopy2
|
||||||
|
dst[i+1] = uint8(offset)
|
||||||
|
dst[i+2] = uint8(offset >> 8)
|
||||||
|
i += 3
|
||||||
|
length -= 64
|
||||||
|
}
|
||||||
|
if length > 64 {
|
||||||
|
// Emit a length 60 copy, encoded as 3 bytes.
|
||||||
|
dst[i+0] = 59<<2 | tagCopy2
|
||||||
|
dst[i+1] = uint8(offset)
|
||||||
|
dst[i+2] = uint8(offset >> 8)
|
||||||
|
i += 3
|
||||||
|
length -= 60
|
||||||
|
}
|
||||||
|
if length >= 12 || offset >= 2048 {
|
||||||
|
// Emit the remaining copy, encoded as 3 bytes.
|
||||||
|
dst[i+0] = uint8(length-1)<<2 | tagCopy2
|
||||||
|
dst[i+1] = uint8(offset)
|
||||||
|
dst[i+2] = uint8(offset >> 8)
|
||||||
|
return i + 3
|
||||||
|
}
|
||||||
|
// Emit the remaining copy, encoded as 2 bytes.
|
||||||
|
dst[i+0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1
|
||||||
|
dst[i+1] = uint8(offset)
|
||||||
|
return i + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// extendMatch returns the largest k such that k <= len(src) and that
|
||||||
|
// src[i:i+k-j] and src[j:k] have the same contents.
|
||||||
|
//
|
||||||
|
// It assumes that:
|
||||||
|
// 0 <= i && i < j && j <= len(src)
|
||||||
|
func extendMatch(src []byte, i, j int) int {
|
||||||
|
for ; j < len(src) && src[i] == src[j]; i, j = i+1, j+1 {
|
||||||
|
}
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash(u, shift uint32) uint32 {
|
||||||
|
return (u * 0x1e35a7bd) >> shift
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeBlock encodes a non-empty src to a guaranteed-large-enough dst. It
|
||||||
|
// assumes that the varint-encoded length of the decompressed bytes has already
|
||||||
|
// been written.
|
||||||
|
//
|
||||||
|
// It also assumes that:
|
||||||
|
// len(dst) >= MaxEncodedLen(len(src)) &&
|
||||||
|
// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize
|
||||||
|
func encodeBlock(dst, src []byte) (d int) {
|
||||||
|
// Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive.
|
||||||
|
// The table element type is uint16, as s < sLimit and sLimit < len(src)
|
||||||
|
// and len(src) <= maxBlockSize and maxBlockSize == 65536.
|
||||||
|
const (
|
||||||
|
maxTableSize = 1 << 14
|
||||||
|
// tableMask is redundant, but helps the compiler eliminate bounds
|
||||||
|
// checks.
|
||||||
|
tableMask = maxTableSize - 1
|
||||||
|
)
|
||||||
|
shift := uint32(32 - 8)
|
||||||
|
for tableSize := 1 << 8; tableSize < maxTableSize && tableSize < len(src); tableSize *= 2 {
|
||||||
|
shift--
|
||||||
|
}
|
||||||
|
// In Go, all array elements are zero-initialized, so there is no advantage
|
||||||
|
// to a smaller tableSize per se. However, it matches the C++ algorithm,
|
||||||
|
// and in the asm versions of this code, we can get away with zeroing only
|
||||||
|
// the first tableSize elements.
|
||||||
|
var table [maxTableSize]uint16
|
||||||
|
|
||||||
|
// sLimit is when to stop looking for offset/length copies. The inputMargin
|
||||||
|
// lets us use a fast path for emitLiteral in the main loop, while we are
|
||||||
|
// looking for copies.
|
||||||
|
sLimit := len(src) - inputMargin
|
||||||
|
|
||||||
|
// nextEmit is where in src the next emitLiteral should start from.
|
||||||
|
nextEmit := 0
|
||||||
|
|
||||||
|
// The encoded form must start with a literal, as there are no previous
|
||||||
|
// bytes to copy, so we start looking for hash matches at s == 1.
|
||||||
|
s := 1
|
||||||
|
nextHash := hash(load32(src, s), shift)
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Copied from the C++ snappy implementation:
|
||||||
|
//
|
||||||
|
// Heuristic match skipping: If 32 bytes are scanned with no matches
|
||||||
|
// found, start looking only at every other byte. If 32 more bytes are
|
||||||
|
// scanned (or skipped), look at every third byte, etc.. When a match
|
||||||
|
// is found, immediately go back to looking at every byte. This is a
|
||||||
|
// small loss (~5% performance, ~0.1% density) for compressible data
|
||||||
|
// due to more bookkeeping, but for non-compressible data (such as
|
||||||
|
// JPEG) it's a huge win since the compressor quickly "realizes" the
|
||||||
|
// data is incompressible and doesn't bother looking for matches
|
||||||
|
// everywhere.
|
||||||
|
//
|
||||||
|
// The "skip" variable keeps track of how many bytes there are since
|
||||||
|
// the last match; dividing it by 32 (ie. right-shifting by five) gives
|
||||||
|
// the number of bytes to move ahead for each iteration.
|
||||||
|
skip := 32
|
||||||
|
|
||||||
|
nextS := s
|
||||||
|
candidate := 0
|
||||||
|
for {
|
||||||
|
s = nextS
|
||||||
|
bytesBetweenHashLookups := skip >> 5
|
||||||
|
nextS = s + bytesBetweenHashLookups
|
||||||
|
skip += bytesBetweenHashLookups
|
||||||
|
if nextS > sLimit {
|
||||||
|
goto emitRemainder
|
||||||
|
}
|
||||||
|
candidate = int(table[nextHash&tableMask])
|
||||||
|
table[nextHash&tableMask] = uint16(s)
|
||||||
|
nextHash = hash(load32(src, nextS), shift)
|
||||||
|
if load32(src, s) == load32(src, candidate) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A 4-byte match has been found. We'll later see if more than 4 bytes
|
||||||
|
// match. But, prior to the match, src[nextEmit:s] are unmatched. Emit
|
||||||
|
// them as literal bytes.
|
||||||
|
d += emitLiteral(dst[d:], src[nextEmit:s])
|
||||||
|
|
||||||
|
// Call emitCopy, and then see if another emitCopy could be our next
|
||||||
|
// move. Repeat until we find no match for the input immediately after
|
||||||
|
// what was consumed by the last emitCopy call.
|
||||||
|
//
|
||||||
|
// If we exit this loop normally then we need to call emitLiteral next,
|
||||||
|
// though we don't yet know how big the literal will be. We handle that
|
||||||
|
// by proceeding to the next iteration of the main loop. We also can
|
||||||
|
// exit this loop via goto if we get close to exhausting the input.
|
||||||
|
for {
|
||||||
|
// Invariant: we have a 4-byte match at s, and no need to emit any
|
||||||
|
// literal bytes prior to s.
|
||||||
|
base := s
|
||||||
|
|
||||||
|
// Extend the 4-byte match as long as possible.
|
||||||
|
//
|
||||||
|
// This is an inlined version of:
|
||||||
|
// s = extendMatch(src, candidate+4, s+4)
|
||||||
|
s += 4
|
||||||
|
for i := candidate + 4; s < len(src) && src[i] == src[s]; i, s = i+1, s+1 {
|
||||||
|
}
|
||||||
|
|
||||||
|
d += emitCopy(dst[d:], base-candidate, s-base)
|
||||||
|
nextEmit = s
|
||||||
|
if s >= sLimit {
|
||||||
|
goto emitRemainder
|
||||||
|
}
|
||||||
|
|
||||||
|
// We could immediately start working at s now, but to improve
|
||||||
|
// compression we first update the hash table at s-1 and at s. If
|
||||||
|
// another emitCopy is not our next move, also calculate nextHash
|
||||||
|
// at s+1. At least on GOARCH=amd64, these three hash calculations
|
||||||
|
// are faster as one load64 call (with some shifts) instead of
|
||||||
|
// three load32 calls.
|
||||||
|
x := load64(src, s-1)
|
||||||
|
prevHash := hash(uint32(x>>0), shift)
|
||||||
|
table[prevHash&tableMask] = uint16(s - 1)
|
||||||
|
currHash := hash(uint32(x>>8), shift)
|
||||||
|
candidate = int(table[currHash&tableMask])
|
||||||
|
table[currHash&tableMask] = uint16(s)
|
||||||
|
if uint32(x>>8) != load32(src, candidate) {
|
||||||
|
nextHash = hash(uint32(x>>16), shift)
|
||||||
|
s++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitRemainder:
|
||||||
|
if nextEmit < len(src) {
|
||||||
|
d += emitLiteral(dst[d:], src[nextEmit:])
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
// It aims for very high speeds and reasonable compression.
|
// It aims for very high speeds and reasonable compression.
|
||||||
//
|
//
|
||||||
// The C++ snappy implementation is at https://github.com/google/snappy
|
// The C++ snappy implementation is at https://github.com/google/snappy
|
||||||
package snappy
|
package snappy // import "github.com/golang/snappy"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
|
@ -32,7 +32,10 @@ Lempel-Ziv compression algorithms. In particular:
|
||||||
- For l == 2, the offset ranges in [0, 1<<16) and the length in [1, 65).
|
- For l == 2, the offset ranges in [0, 1<<16) and the length in [1, 65).
|
||||||
The length is 1 + m. The offset is the little-endian unsigned integer
|
The length is 1 + m. The offset is the little-endian unsigned integer
|
||||||
denoted by the next 2 bytes.
|
denoted by the next 2 bytes.
|
||||||
- For l == 3, this tag is a legacy format that is no longer supported.
|
- For l == 3, this tag is a legacy format that is no longer issued by most
|
||||||
|
encoders. Nonetheless, the offset ranges in [0, 1<<32) and the length in
|
||||||
|
[1, 65). The length is 1 + m. The offset is the little-endian unsigned
|
||||||
|
integer denoted by the next 4 bytes.
|
||||||
*/
|
*/
|
||||||
const (
|
const (
|
||||||
tagLiteral = 0x00
|
tagLiteral = 0x00
|
||||||
|
@ -46,9 +49,25 @@ const (
|
||||||
chunkHeaderSize = 4
|
chunkHeaderSize = 4
|
||||||
magicChunk = "\xff\x06\x00\x00" + magicBody
|
magicChunk = "\xff\x06\x00\x00" + magicBody
|
||||||
magicBody = "sNaPpY"
|
magicBody = "sNaPpY"
|
||||||
|
|
||||||
|
// maxBlockSize is the maximum size of the input to encodeBlock. It is not
|
||||||
|
// part of the wire format per se, but some parts of the encoder assume
|
||||||
|
// that an offset fits into a uint16.
|
||||||
|
//
|
||||||
|
// Also, for the framing format (Writer type instead of Encode function),
|
||||||
// https://github.com/google/snappy/blob/master/framing_format.txt says
|
// https://github.com/google/snappy/blob/master/framing_format.txt says
|
||||||
// that "the uncompressed data in a chunk must be no longer than 65536 bytes".
|
// that "the uncompressed data in a chunk must be no longer than 65536
|
||||||
maxUncompressedChunkLen = 65536
|
// bytes".
|
||||||
|
maxBlockSize = 65536
|
||||||
|
|
||||||
|
// maxEncodedLenOfMaxBlockSize equals MaxEncodedLen(maxBlockSize), but is
|
||||||
|
// hard coded to be a const instead of a variable, so that obufLen can also
|
||||||
|
// be a const. Their equivalence is confirmed by
|
||||||
|
// TestMaxEncodedLenOfMaxBlockSize.
|
||||||
|
maxEncodedLenOfMaxBlockSize = 76490
|
||||||
|
|
||||||
|
obufHeaderLen = len(magicChunk) + checksumSize + chunkHeaderSize
|
||||||
|
obufLen = obufHeaderLen + maxEncodedLenOfMaxBlockSize
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
|
@ -37,3 +37,5 @@ type termios struct {
|
||||||
Ispeed int32
|
Ispeed int32
|
||||||
Ospeed int32
|
Ospeed int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cursorColumn = false
|
|
@ -7,7 +7,6 @@ package liner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"container/ring"
|
"container/ring"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -32,6 +31,8 @@ type commonState struct {
|
||||||
multiLineMode bool
|
multiLineMode bool
|
||||||
cursorRows int
|
cursorRows int
|
||||||
maxRows int
|
maxRows int
|
||||||
|
shouldRestart ShouldRestart
|
||||||
|
needRefresh bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TabStyle is used to select how tab completions are displayed.
|
// TabStyle is used to select how tab completions are displayed.
|
||||||
|
@ -58,7 +59,12 @@ var ErrPromptAborted = errors.New("prompt aborted")
|
||||||
// platform is normally supported, but stdout has been redirected
|
// platform is normally supported, but stdout has been redirected
|
||||||
var ErrNotTerminalOutput = errors.New("standard output is not a terminal")
|
var ErrNotTerminalOutput = errors.New("standard output is not a terminal")
|
||||||
|
|
||||||
// Max elements to save on the killring
|
// ErrInvalidPrompt is returned from Prompt or PasswordPrompt if the
|
||||||
|
// prompt contains any unprintable runes (including substrings that could
|
||||||
|
// be colour codes on some platforms).
|
||||||
|
var ErrInvalidPrompt = errors.New("invalid prompt")
|
||||||
|
|
||||||
|
// KillRingMax is the max number of elements to save on the killring.
|
||||||
const KillRingMax = 60
|
const KillRingMax = 60
|
||||||
|
|
||||||
// HistoryLimit is the maximum number of entries saved in the scrollback history.
|
// HistoryLimit is the maximum number of entries saved in the scrollback history.
|
||||||
|
@ -133,6 +139,13 @@ func (s *State) AppendHistory(item string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearHistory clears the scroollback history.
|
||||||
|
func (s *State) ClearHistory() {
|
||||||
|
s.historyMutex.Lock()
|
||||||
|
defer s.historyMutex.Unlock()
|
||||||
|
s.history = nil
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the history lines starting with prefix
|
// Returns the history lines starting with prefix
|
||||||
func (s *State) getHistoryByPrefix(prefix string) (ph []string) {
|
func (s *State) getHistoryByPrefix(prefix string) (ph []string) {
|
||||||
for _, h := range s.history {
|
for _, h := range s.history {
|
||||||
|
@ -215,6 +228,16 @@ func (s *State) SetMultiLineMode(mlmode bool) {
|
||||||
s.multiLineMode = mlmode
|
s.multiLineMode = mlmode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShouldRestart is passed the error generated by readNext and returns true if
|
||||||
|
// the the read should be restarted or false if the error should be returned.
|
||||||
|
type ShouldRestart func(err error) bool
|
||||||
|
|
||||||
|
// SetShouldRestart sets the restart function that Liner will call to determine
|
||||||
|
// whether to retry the call to, or return the error returned by, readNext.
|
||||||
|
func (s *State) SetShouldRestart(f ShouldRestart) {
|
||||||
|
s.shouldRestart = f
|
||||||
|
}
|
||||||
|
|
||||||
func (s *State) promptUnsupported(p string) (string, error) {
|
func (s *State) promptUnsupported(p string) (string, error) {
|
||||||
if !s.inputRedirected || !s.terminalSupported {
|
if !s.inputRedirected || !s.terminalSupported {
|
||||||
fmt.Print(p)
|
fmt.Print(p)
|
||||||
|
@ -223,5 +246,5 @@ func (s *State) promptUnsupported(p string) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return string(bytes.TrimSpace(linebuf)), nil
|
return string(linebuf), nil
|
||||||
}
|
}
|
|
@ -31,11 +31,6 @@ type State struct {
|
||||||
|
|
||||||
// NewLiner initializes a new *State, and sets the terminal into raw mode. To
|
// NewLiner initializes a new *State, and sets the terminal into raw mode. To
|
||||||
// restore the terminal to its previous state, call State.Close().
|
// restore the terminal to its previous state, call State.Close().
|
||||||
//
|
|
||||||
// Note if you are still using Go 1.0: NewLiner handles SIGWINCH, so it will
|
|
||||||
// leak a channel every time you call it. Therefore, it is recommened that you
|
|
||||||
// upgrade to a newer release of Go, or ensure that NewLiner is only called
|
|
||||||
// once.
|
|
||||||
func NewLiner() *State {
|
func NewLiner() *State {
|
||||||
var s State
|
var s State
|
||||||
s.r = bufio.NewReader(os.Stdin)
|
s.r = bufio.NewReader(os.Stdin)
|
||||||
|
@ -67,8 +62,7 @@ func NewLiner() *State {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.outputRedirected {
|
if !s.outputRedirected {
|
||||||
s.getColumns()
|
s.outputRedirected = !s.getColumns()
|
||||||
s.outputRedirected = s.columns <= 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &s
|
return &s
|
||||||
|
@ -88,8 +82,12 @@ func (s *State) startPrompt() {
|
||||||
s.restartPrompt()
|
s.restartPrompt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *State) inputWaiting() bool {
|
||||||
|
return len(s.next) > 0
|
||||||
|
}
|
||||||
|
|
||||||
func (s *State) restartPrompt() {
|
func (s *State) restartPrompt() {
|
||||||
next := make(chan nexter)
|
next := make(chan nexter, 200)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
var n nexter
|
var n nexter
|
||||||
|
@ -127,8 +125,6 @@ func (s *State) nextPending(timeout <-chan time.Time) (rune, error) {
|
||||||
s.pending = s.pending[1:]
|
s.pending = s.pending[1:]
|
||||||
return rv, errTimedOut
|
return rv, errTimedOut
|
||||||
}
|
}
|
||||||
// not reached
|
|
||||||
return 0, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) readNext() (interface{}, error) {
|
func (s *State) readNext() (interface{}, error) {
|
||||||
|
@ -350,7 +346,7 @@ func (s *State) readNext() (interface{}, error) {
|
||||||
|
|
||||||
// Close returns the terminal to its previous mode
|
// Close returns the terminal to its previous mode
|
||||||
func (s *State) Close() error {
|
func (s *State) Close() error {
|
||||||
stopSignal(s.winch)
|
signal.Stop(s.winch)
|
||||||
if !s.inputRedirected {
|
if !s.inputRedirected {
|
||||||
s.origMode.ApplyMode()
|
s.origMode.ApplyMode()
|
||||||
}
|
}
|
|
@ -37,3 +37,7 @@ type termios struct {
|
||||||
Ispeed uintptr
|
Ispeed uintptr
|
||||||
Ospeed uintptr
|
Ospeed uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Terminal.app needs a column for the cursor when the input line is at the
|
||||||
|
// bottom of the window.
|
||||||
|
const cursorColumn = true
|
|
@ -24,3 +24,5 @@ const (
|
||||||
type termios struct {
|
type termios struct {
|
||||||
syscall.Termios
|
syscall.Termios
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cursorColumn = false
|
|
@ -10,13 +10,14 @@ import (
|
||||||
var (
|
var (
|
||||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
procGetStdHandle = kernel32.NewProc("GetStdHandle")
|
procGetStdHandle = kernel32.NewProc("GetStdHandle")
|
||||||
procReadConsoleInput = kernel32.NewProc("ReadConsoleInputW")
|
procReadConsoleInput = kernel32.NewProc("ReadConsoleInputW")
|
||||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
procGetNumberOfConsoleInputEvents = kernel32.NewProc("GetNumberOfConsoleInputEvents")
|
||||||
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||||
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
|
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
||||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
|
||||||
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
|
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||||
|
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
|
||||||
)
|
)
|
||||||
|
|
||||||
// These names are from the Win32 api, so they use underscores (contrary to
|
// These names are from the Win32 api, so they use underscores (contrary to
|
||||||
|
@ -147,6 +148,21 @@ const (
|
||||||
modKeys = shiftPressed | leftAltPressed | rightAltPressed | leftCtrlPressed | rightCtrlPressed
|
modKeys = shiftPressed | leftAltPressed | rightAltPressed | leftCtrlPressed | rightCtrlPressed
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// inputWaiting only returns true if the next call to readNext will return immediately.
|
||||||
|
func (s *State) inputWaiting() bool {
|
||||||
|
var num uint32
|
||||||
|
ok, _, _ := procGetNumberOfConsoleInputEvents.Call(uintptr(s.handle), uintptr(unsafe.Pointer(&num)))
|
||||||
|
if ok == 0 {
|
||||||
|
// call failed, so we cannot guarantee a non-blocking readNext
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// during a "paste" input events are always an odd number, and
|
||||||
|
// the last one results in a blocking readNext, so return false
|
||||||
|
// when num is 1 or 0.
|
||||||
|
return num > 1
|
||||||
|
}
|
||||||
|
|
||||||
func (s *State) readNext() (interface{}, error) {
|
func (s *State) readNext() (interface{}, error) {
|
||||||
if s.repeat > 0 {
|
if s.repeat > 0 {
|
||||||
s.repeat--
|
s.repeat--
|
||||||
|
@ -168,6 +184,9 @@ func (s *State) readNext() (interface{}, error) {
|
||||||
if input.eventType == window_buffer_size_event {
|
if input.eventType == window_buffer_size_event {
|
||||||
xy := (*coord)(unsafe.Pointer(&input.blob[0]))
|
xy := (*coord)(unsafe.Pointer(&input.blob[0]))
|
||||||
s.columns = int(xy.x)
|
s.columns = int(xy.x)
|
||||||
|
if s.columns > 1 {
|
||||||
|
s.columns--
|
||||||
|
}
|
||||||
return winch, nil
|
return winch, nil
|
||||||
}
|
}
|
||||||
if input.eventType != key_event {
|
if input.eventType != key_event {
|
||||||
|
@ -260,7 +279,6 @@ func (s *State) readNext() (interface{}, error) {
|
||||||
}
|
}
|
||||||
return s.key, nil
|
return s.key, nil
|
||||||
}
|
}
|
||||||
return unknown, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close returns the terminal to its previous mode
|
// Close returns the terminal to its previous mode
|
149
cmd/vendor/github.com/peterh/liner/line.go → _vendor/vendor/github.com/peterh/liner/line.go
generated
vendored
149
cmd/vendor/github.com/peterh/liner/line.go → _vendor/vendor/github.com/peterh/liner/line.go
generated
vendored
|
@ -90,11 +90,11 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *State) refresh(prompt []rune, buf []rune, pos int) error {
|
func (s *State) refresh(prompt []rune, buf []rune, pos int) error {
|
||||||
|
s.needRefresh = false
|
||||||
if s.multiLineMode {
|
if s.multiLineMode {
|
||||||
return s.refreshMultiLine(prompt, buf, pos)
|
return s.refreshMultiLine(prompt, buf, pos)
|
||||||
} else {
|
|
||||||
return s.refreshSingleLine(prompt, buf, pos)
|
|
||||||
}
|
}
|
||||||
|
return s.refreshSingleLine(prompt, buf, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) refreshSingleLine(prompt []rune, buf []rune, pos int) error {
|
func (s *State) refreshSingleLine(prompt []rune, buf []rune, pos int) error {
|
||||||
|
@ -387,8 +387,6 @@ func (s *State) tabComplete(p []rune, line []rune, pos int) ([]rune, int, interf
|
||||||
}
|
}
|
||||||
return []rune(head + pick + tail), hl + utf8.RuneCountInString(pick), next, nil
|
return []rune(head + pick + tail), hl + utf8.RuneCountInString(pick), next, nil
|
||||||
}
|
}
|
||||||
// Not reached
|
|
||||||
return line, pos, rune(esc), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// reverse intelligent search, implements a bash-like history search.
|
// reverse intelligent search, implements a bash-like history search.
|
||||||
|
@ -556,14 +554,26 @@ func (s *State) yank(p []rune, text []rune, pos int) ([]rune, int, interface{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return line, pos, esc, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prompt displays p and returns a line of user input, not including a trailing
|
// Prompt displays p and returns a line of user input, not including a trailing
|
||||||
// newline character. An io.EOF error is returned if the user signals end-of-file
|
// newline character. An io.EOF error is returned if the user signals end-of-file
|
||||||
// by pressing Ctrl-D. Prompt allows line editing if the terminal supports it.
|
// by pressing Ctrl-D. Prompt allows line editing if the terminal supports it.
|
||||||
func (s *State) Prompt(prompt string) (string, error) {
|
func (s *State) Prompt(prompt string) (string, error) {
|
||||||
|
return s.PromptWithSuggestion(prompt, "", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PromptWithSuggestion displays prompt and an editable text with cursor at
|
||||||
|
// given position. The cursor will be set to the end of the line if given position
|
||||||
|
// is negative or greater than length of text. Returns a line of user input, not
|
||||||
|
// including a trailing newline character. An io.EOF error is returned if the user
|
||||||
|
// signals end-of-file by pressing Ctrl-D.
|
||||||
|
func (s *State) PromptWithSuggestion(prompt string, text string, pos int) (string, error) {
|
||||||
|
for _, r := range prompt {
|
||||||
|
if unicode.Is(unicode.C, r) {
|
||||||
|
return "", ErrInvalidPrompt
|
||||||
|
}
|
||||||
|
}
|
||||||
if s.inputRedirected || !s.terminalSupported {
|
if s.inputRedirected || !s.terminalSupported {
|
||||||
return s.promptUnsupported(prompt)
|
return s.promptUnsupported(prompt)
|
||||||
}
|
}
|
||||||
|
@ -574,24 +584,37 @@ func (s *State) Prompt(prompt string) (string, error) {
|
||||||
s.historyMutex.RLock()
|
s.historyMutex.RLock()
|
||||||
defer s.historyMutex.RUnlock()
|
defer s.historyMutex.RUnlock()
|
||||||
|
|
||||||
s.startPrompt()
|
|
||||||
defer s.stopPrompt()
|
|
||||||
s.getColumns()
|
|
||||||
|
|
||||||
fmt.Print(prompt)
|
fmt.Print(prompt)
|
||||||
p := []rune(prompt)
|
p := []rune(prompt)
|
||||||
var line []rune
|
var line = []rune(text)
|
||||||
pos := 0
|
|
||||||
historyEnd := ""
|
historyEnd := ""
|
||||||
prefixHistory := s.getHistoryByPrefix(string(line))
|
var historyPrefix []string
|
||||||
historyPos := len(prefixHistory)
|
historyPos := 0
|
||||||
|
historyStale := true
|
||||||
historyAction := false // used to mark history related actions
|
historyAction := false // used to mark history related actions
|
||||||
killAction := 0 // used to mark kill related actions
|
killAction := 0 // used to mark kill related actions
|
||||||
|
|
||||||
|
defer s.stopPrompt()
|
||||||
|
|
||||||
|
if pos < 0 || len(text) < pos {
|
||||||
|
pos = len(text)
|
||||||
|
}
|
||||||
|
if len(line) > 0 {
|
||||||
|
s.refresh(p, line, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
restart:
|
||||||
|
s.startPrompt()
|
||||||
|
s.getColumns()
|
||||||
|
|
||||||
mainLoop:
|
mainLoop:
|
||||||
for {
|
for {
|
||||||
next, err := s.readNext()
|
next, err := s.readNext()
|
||||||
haveNext:
|
haveNext:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if s.shouldRestart != nil && s.shouldRestart(err) {
|
||||||
|
goto restart
|
||||||
|
}
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -600,6 +623,9 @@ mainLoop:
|
||||||
case rune:
|
case rune:
|
||||||
switch v {
|
switch v {
|
||||||
case cr, lf:
|
case cr, lf:
|
||||||
|
if s.needRefresh {
|
||||||
|
s.refresh(p, line, pos)
|
||||||
|
}
|
||||||
if s.multiLineMode {
|
if s.multiLineMode {
|
||||||
s.resetMultiLine(p, line, pos)
|
s.resetMultiLine(p, line, pos)
|
||||||
}
|
}
|
||||||
|
@ -607,21 +633,21 @@ mainLoop:
|
||||||
break mainLoop
|
break mainLoop
|
||||||
case ctrlA: // Start of line
|
case ctrlA: // Start of line
|
||||||
pos = 0
|
pos = 0
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
case ctrlE: // End of line
|
case ctrlE: // End of line
|
||||||
pos = len(line)
|
pos = len(line)
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
case ctrlB: // left
|
case ctrlB: // left
|
||||||
if pos > 0 {
|
if pos > 0 {
|
||||||
pos -= len(getSuffixGlyphs(line[:pos], 1))
|
pos -= len(getSuffixGlyphs(line[:pos], 1))
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
} else {
|
} else {
|
||||||
fmt.Print(beep)
|
fmt.Print(beep)
|
||||||
}
|
}
|
||||||
case ctrlF: // right
|
case ctrlF: // right
|
||||||
if pos < len(line) {
|
if pos < len(line) {
|
||||||
pos += len(getPrefixGlyphs(line[pos:], 1))
|
pos += len(getPrefixGlyphs(line[pos:], 1))
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
} else {
|
} else {
|
||||||
fmt.Print(beep)
|
fmt.Print(beep)
|
||||||
}
|
}
|
||||||
|
@ -640,7 +666,7 @@ mainLoop:
|
||||||
} else {
|
} else {
|
||||||
n := len(getPrefixGlyphs(line[pos:], 1))
|
n := len(getPrefixGlyphs(line[pos:], 1))
|
||||||
line = append(line[:pos], line[pos+n:]...)
|
line = append(line[:pos], line[pos+n:]...)
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
}
|
}
|
||||||
case ctrlK: // delete remainder of line
|
case ctrlK: // delete remainder of line
|
||||||
if pos >= len(line) {
|
if pos >= len(line) {
|
||||||
|
@ -654,32 +680,42 @@ mainLoop:
|
||||||
|
|
||||||
killAction = 2 // Mark that there was a kill action
|
killAction = 2 // Mark that there was a kill action
|
||||||
line = line[:pos]
|
line = line[:pos]
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
}
|
}
|
||||||
case ctrlP: // up
|
case ctrlP: // up
|
||||||
historyAction = true
|
historyAction = true
|
||||||
|
if historyStale {
|
||||||
|
historyPrefix = s.getHistoryByPrefix(string(line))
|
||||||
|
historyPos = len(historyPrefix)
|
||||||
|
historyStale = false
|
||||||
|
}
|
||||||
if historyPos > 0 {
|
if historyPos > 0 {
|
||||||
if historyPos == len(prefixHistory) {
|
if historyPos == len(historyPrefix) {
|
||||||
historyEnd = string(line)
|
historyEnd = string(line)
|
||||||
}
|
}
|
||||||
historyPos--
|
historyPos--
|
||||||
line = []rune(prefixHistory[historyPos])
|
line = []rune(historyPrefix[historyPos])
|
||||||
pos = len(line)
|
pos = len(line)
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
} else {
|
} else {
|
||||||
fmt.Print(beep)
|
fmt.Print(beep)
|
||||||
}
|
}
|
||||||
case ctrlN: // down
|
case ctrlN: // down
|
||||||
historyAction = true
|
historyAction = true
|
||||||
if historyPos < len(prefixHistory) {
|
if historyStale {
|
||||||
|
historyPrefix = s.getHistoryByPrefix(string(line))
|
||||||
|
historyPos = len(historyPrefix)
|
||||||
|
historyStale = false
|
||||||
|
}
|
||||||
|
if historyPos < len(historyPrefix) {
|
||||||
historyPos++
|
historyPos++
|
||||||
if historyPos == len(prefixHistory) {
|
if historyPos == len(historyPrefix) {
|
||||||
line = []rune(historyEnd)
|
line = []rune(historyEnd)
|
||||||
} else {
|
} else {
|
||||||
line = []rune(prefixHistory[historyPos])
|
line = []rune(historyPrefix[historyPos])
|
||||||
}
|
}
|
||||||
pos = len(line)
|
pos = len(line)
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
} else {
|
} else {
|
||||||
fmt.Print(beep)
|
fmt.Print(beep)
|
||||||
}
|
}
|
||||||
|
@ -697,11 +733,11 @@ mainLoop:
|
||||||
copy(line[pos-len(prev):], next)
|
copy(line[pos-len(prev):], next)
|
||||||
copy(line[pos-len(prev)+len(next):], scratch)
|
copy(line[pos-len(prev)+len(next):], scratch)
|
||||||
pos += len(next)
|
pos += len(next)
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
}
|
}
|
||||||
case ctrlL: // clear screen
|
case ctrlL: // clear screen
|
||||||
s.eraseScreen()
|
s.eraseScreen()
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
case ctrlC: // reset
|
case ctrlC: // reset
|
||||||
fmt.Println("^C")
|
fmt.Println("^C")
|
||||||
if s.multiLineMode {
|
if s.multiLineMode {
|
||||||
|
@ -721,7 +757,7 @@ mainLoop:
|
||||||
n := len(getSuffixGlyphs(line[:pos], 1))
|
n := len(getSuffixGlyphs(line[:pos], 1))
|
||||||
line = append(line[:pos-n], line[pos:]...)
|
line = append(line[:pos-n], line[pos:]...)
|
||||||
pos -= n
|
pos -= n
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
}
|
}
|
||||||
case ctrlU: // Erase line before cursor
|
case ctrlU: // Erase line before cursor
|
||||||
if killAction > 0 {
|
if killAction > 0 {
|
||||||
|
@ -733,7 +769,7 @@ mainLoop:
|
||||||
killAction = 2 // Mark that there was some killing
|
killAction = 2 // Mark that there was some killing
|
||||||
line = line[pos:]
|
line = line[pos:]
|
||||||
pos = 0
|
pos = 0
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
case ctrlW: // Erase word
|
case ctrlW: // Erase word
|
||||||
if pos == 0 {
|
if pos == 0 {
|
||||||
fmt.Print(beep)
|
fmt.Print(beep)
|
||||||
|
@ -770,13 +806,13 @@ mainLoop:
|
||||||
}
|
}
|
||||||
killAction = 2 // Mark that there was some killing
|
killAction = 2 // Mark that there was some killing
|
||||||
|
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
case ctrlY: // Paste from Yank buffer
|
case ctrlY: // Paste from Yank buffer
|
||||||
line, pos, next, err = s.yank(p, line, pos)
|
line, pos, next, err = s.yank(p, line, pos)
|
||||||
goto haveNext
|
goto haveNext
|
||||||
case ctrlR: // Reverse Search
|
case ctrlR: // Reverse Search
|
||||||
line, pos, next, err = s.reverseISearch(line, pos)
|
line, pos, next, err = s.reverseISearch(line, pos)
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
goto haveNext
|
goto haveNext
|
||||||
case tab: // Tab completion
|
case tab: // Tab completion
|
||||||
line, pos, next, err = s.tabComplete(p, line, pos)
|
line, pos, next, err = s.tabComplete(p, line, pos)
|
||||||
|
@ -791,14 +827,16 @@ mainLoop:
|
||||||
case 0, 28, 29, 30, 31:
|
case 0, 28, 29, 30, 31:
|
||||||
fmt.Print(beep)
|
fmt.Print(beep)
|
||||||
default:
|
default:
|
||||||
if pos == len(line) && !s.multiLineMode && countGlyphs(p)+countGlyphs(line) < s.columns-1 {
|
if pos == len(line) && !s.multiLineMode &&
|
||||||
|
len(p)+len(line) < s.columns*4 && // Avoid countGlyphs on large lines
|
||||||
|
countGlyphs(p)+countGlyphs(line) < s.columns-1 {
|
||||||
line = append(line, v)
|
line = append(line, v)
|
||||||
fmt.Printf("%c", v)
|
fmt.Printf("%c", v)
|
||||||
pos++
|
pos++
|
||||||
} else {
|
} else {
|
||||||
line = append(line[:pos], append([]rune{v}, line[pos:]...)...)
|
line = append(line[:pos], append([]rune{v}, line[pos:]...)...)
|
||||||
pos++
|
pos++
|
||||||
s.refresh(p, line, pos)
|
s.needRefresh = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case action:
|
case action:
|
||||||
|
@ -866,24 +904,34 @@ mainLoop:
|
||||||
}
|
}
|
||||||
case up:
|
case up:
|
||||||
historyAction = true
|
historyAction = true
|
||||||
|
if historyStale {
|
||||||
|
historyPrefix = s.getHistoryByPrefix(string(line))
|
||||||
|
historyPos = len(historyPrefix)
|
||||||
|
historyStale = false
|
||||||
|
}
|
||||||
if historyPos > 0 {
|
if historyPos > 0 {
|
||||||
if historyPos == len(prefixHistory) {
|
if historyPos == len(historyPrefix) {
|
||||||
historyEnd = string(line)
|
historyEnd = string(line)
|
||||||
}
|
}
|
||||||
historyPos--
|
historyPos--
|
||||||
line = []rune(prefixHistory[historyPos])
|
line = []rune(historyPrefix[historyPos])
|
||||||
pos = len(line)
|
pos = len(line)
|
||||||
} else {
|
} else {
|
||||||
fmt.Print(beep)
|
fmt.Print(beep)
|
||||||
}
|
}
|
||||||
case down:
|
case down:
|
||||||
historyAction = true
|
historyAction = true
|
||||||
if historyPos < len(prefixHistory) {
|
if historyStale {
|
||||||
|
historyPrefix = s.getHistoryByPrefix(string(line))
|
||||||
|
historyPos = len(historyPrefix)
|
||||||
|
historyStale = false
|
||||||
|
}
|
||||||
|
if historyPos < len(historyPrefix) {
|
||||||
historyPos++
|
historyPos++
|
||||||
if historyPos == len(prefixHistory) {
|
if historyPos == len(historyPrefix) {
|
||||||
line = []rune(historyEnd)
|
line = []rune(historyEnd)
|
||||||
} else {
|
} else {
|
||||||
line = []rune(prefixHistory[historyPos])
|
line = []rune(historyPrefix[historyPos])
|
||||||
}
|
}
|
||||||
pos = len(line)
|
pos = len(line)
|
||||||
} else {
|
} else {
|
||||||
|
@ -907,11 +955,13 @@ mainLoop:
|
||||||
s.cursorRows = 1
|
s.cursorRows = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s.needRefresh = true
|
||||||
|
}
|
||||||
|
if s.needRefresh && !s.inputWaiting() {
|
||||||
s.refresh(p, line, pos)
|
s.refresh(p, line, pos)
|
||||||
}
|
}
|
||||||
if !historyAction {
|
if !historyAction {
|
||||||
prefixHistory = s.getHistoryByPrefix(string(line))
|
historyStale = true
|
||||||
historyPos = len(prefixHistory)
|
|
||||||
}
|
}
|
||||||
if killAction > 0 {
|
if killAction > 0 {
|
||||||
killAction--
|
killAction--
|
||||||
|
@ -923,6 +973,11 @@ mainLoop:
|
||||||
// PasswordPrompt displays p, and then waits for user input. The input typed by
|
// PasswordPrompt displays p, and then waits for user input. The input typed by
|
||||||
// the user is not displayed in the terminal.
|
// the user is not displayed in the terminal.
|
||||||
func (s *State) PasswordPrompt(prompt string) (string, error) {
|
func (s *State) PasswordPrompt(prompt string) (string, error) {
|
||||||
|
for _, r := range prompt {
|
||||||
|
if unicode.Is(unicode.C, r) {
|
||||||
|
return "", ErrInvalidPrompt
|
||||||
|
}
|
||||||
|
}
|
||||||
if !s.terminalSupported {
|
if !s.terminalSupported {
|
||||||
return "", errors.New("liner: function not supported in this terminal")
|
return "", errors.New("liner: function not supported in this terminal")
|
||||||
}
|
}
|
||||||
|
@ -933,8 +988,10 @@ func (s *State) PasswordPrompt(prompt string) (string, error) {
|
||||||
return "", ErrNotTerminalOutput
|
return "", ErrNotTerminalOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
s.startPrompt()
|
|
||||||
defer s.stopPrompt()
|
defer s.stopPrompt()
|
||||||
|
|
||||||
|
restart:
|
||||||
|
s.startPrompt()
|
||||||
s.getColumns()
|
s.getColumns()
|
||||||
|
|
||||||
fmt.Print(prompt)
|
fmt.Print(prompt)
|
||||||
|
@ -946,6 +1003,9 @@ mainLoop:
|
||||||
for {
|
for {
|
||||||
next, err := s.readNext()
|
next, err := s.readNext()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if s.shouldRestart != nil && s.shouldRestart(err) {
|
||||||
|
goto restart
|
||||||
|
}
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -953,6 +1013,9 @@ mainLoop:
|
||||||
case rune:
|
case rune:
|
||||||
switch v {
|
switch v {
|
||||||
case cr, lf:
|
case cr, lf:
|
||||||
|
if s.needRefresh {
|
||||||
|
s.refresh(p, line, pos)
|
||||||
|
}
|
||||||
if s.multiLineMode {
|
if s.multiLineMode {
|
||||||
s.resetMultiLine(p, line, pos)
|
s.resetMultiLine(p, line, pos)
|
||||||
}
|
}
|
|
@ -48,14 +48,18 @@ type winSize struct {
|
||||||
xpixel, ypixel uint16
|
xpixel, ypixel uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) getColumns() {
|
func (s *State) getColumns() bool {
|
||||||
var ws winSize
|
var ws winSize
|
||||||
ok, _, _ := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdout),
|
ok, _, _ := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdout),
|
||||||
syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&ws)))
|
syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&ws)))
|
||||||
if ok < 0 {
|
if int(ok) < 0 {
|
||||||
s.columns = 80
|
return false
|
||||||
}
|
}
|
||||||
s.columns = int(ws.col)
|
s.columns = int(ws.col)
|
||||||
|
if cursorColumn && s.columns > 1 {
|
||||||
|
s.columns--
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) checkOutput() {
|
func (s *State) checkOutput() {
|
|
@ -69,4 +69,8 @@ func (s *State) getColumns() {
|
||||||
var sbi consoleScreenBufferInfo
|
var sbi consoleScreenBufferInfo
|
||||||
procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi)))
|
procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi)))
|
||||||
s.columns = int(sbi.dwSize.x)
|
s.columns = int(sbi.dwSize.x)
|
||||||
|
if s.columns > 1 {
|
||||||
|
// Windows 10 needs a spare column for the cursor
|
||||||
|
s.columns--
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -25,6 +25,12 @@ var doubleWidth = []*unicode.RangeTable{
|
||||||
func countGlyphs(s []rune) int {
|
func countGlyphs(s []rune) int {
|
||||||
n := 0
|
n := 0
|
||||||
for _, r := range s {
|
for _, r := range s {
|
||||||
|
// speed up the common case
|
||||||
|
if r < 127 {
|
||||||
|
n++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case unicode.IsOneOf(zeroWidth, r):
|
case unicode.IsOneOf(zeroWidth, r):
|
||||||
case unicode.IsOneOf(doubleWidth, r):
|
case unicode.IsOneOf(doubleWidth, r):
|
||||||
|
@ -39,6 +45,10 @@ func countGlyphs(s []rune) int {
|
||||||
func countMultiLineGlyphs(s []rune, columns int, start int) int {
|
func countMultiLineGlyphs(s []rune, columns int, start int) int {
|
||||||
n := start
|
n := start
|
||||||
for _, r := range s {
|
for _, r := range s {
|
||||||
|
if r < 127 {
|
||||||
|
n++
|
||||||
|
continue
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case unicode.IsOneOf(zeroWidth, r):
|
case unicode.IsOneOf(zeroWidth, r):
|
||||||
case unicode.IsOneOf(doubleWidth, r):
|
case unicode.IsOneOf(doubleWidth, r):
|
||||||
|
@ -58,6 +68,11 @@ func countMultiLineGlyphs(s []rune, columns int, start int) int {
|
||||||
func getPrefixGlyphs(s []rune, num int) []rune {
|
func getPrefixGlyphs(s []rune, num int) []rune {
|
||||||
p := 0
|
p := 0
|
||||||
for n := 0; n < num && p < len(s); p++ {
|
for n := 0; n < num && p < len(s); p++ {
|
||||||
|
// speed up the common case
|
||||||
|
if s[p] < 127 {
|
||||||
|
n++
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !unicode.IsOneOf(zeroWidth, s[p]) {
|
if !unicode.IsOneOf(zeroWidth, s[p]) {
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
|
@ -71,6 +86,11 @@ func getPrefixGlyphs(s []rune, num int) []rune {
|
||||||
func getSuffixGlyphs(s []rune, num int) []rune {
|
func getSuffixGlyphs(s []rune, num int) []rune {
|
||||||
p := len(s)
|
p := len(s)
|
||||||
for n := 0; n < num && p > 0; p-- {
|
for n := 0; n < num && p > 0; p-- {
|
||||||
|
// speed up the common case
|
||||||
|
if s[p-1] < 127 {
|
||||||
|
n++
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !unicode.IsOneOf(zeroWidth, s[p-1]) {
|
if !unicode.IsOneOf(zeroWidth, s[p-1]) {
|
||||||
n++
|
n++
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue