From 473644d9189c82e46061d72668416a437b74e416 Mon Sep 17 00:00:00 2001 From: Jaden Weiss Date: Tue, 7 Apr 2020 17:09:30 -0400 Subject: [PATCH] internal/bytealg: reimplement bytealg in pure Go Previously, we implemented individual bytealg functions via linknaming, and had to update them every once in a while when we hit linker errors. Instead, this change reimplements the bytealg package in pure Go. If something is missing, it will cause a compiler error rather than a linker error. This is easier to test and maintain. --- compiler/compiler.go | 2 +- src/internal/bytealg/bytealg.go | 126 ++++++++++++++++++++++++++++++++ src/runtime/bytes.go | 11 --- src/runtime/string.go | 12 --- src/runtime/string_count.go | 23 ------ src/runtime/strings_go111.go | 4 +- 6 files changed, 130 insertions(+), 48 deletions(-) create mode 100644 src/internal/bytealg/bytealg.go delete mode 100644 src/runtime/bytes.go delete mode 100644 src/runtime/string_count.go diff --git a/compiler/compiler.go b/compiler/compiler.go index f5678887..e21f4a4e 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -180,7 +180,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con path = path[len(tinygoPath+"/src/"):] } switch path { - case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "sync", "testing", "internal/reflectlite", "internal/task": + case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "sync", "testing", "internal/reflectlite", "internal/bytealg", "internal/task": return path default: if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") { diff --git a/src/internal/bytealg/bytealg.go b/src/internal/bytealg/bytealg.go new file mode 100644 index 00000000..f52b507b --- /dev/null +++ b/src/internal/bytealg/bytealg.go @@ -0,0 +1,126 @@ +package bytealg + +const ( + // Index can search any valid length of string. + + MaxLen = int(-1) >> 31 + MaxBruteForce = MaxLen +) + +// Compare two byte slices. +// Returns -1 if the first differing byte is lower in a, or 1 if the first differing byte is greater in b. +// If the byte slices are equal, returns 0. +// If the lengths are different and there are no differing bytes, compares based on length. +func Compare(a, b []byte) int { + // Compare for differing bytes. + for i := 0; i < len(a) && i < len(b); i++ { + switch { + case a[0] < b[0]: + return -1 + case a[0] > b[0]: + return 1 + } + } + + // Compare lengths. + switch { + case len(a) > len(b): + return 1 + case len(a) < len(b): + return -1 + default: + return 0 + } +} + +// Count the number of instances of a byte in a slice. +func Count(b []byte, c byte) int { + // Use a simple implementation, as there is no intrinsic that does this like we want. + n := 0 + for _, v := range b { + if v == c { + n++ + } + } + return n +} + +// Count the number of instances of a byte in a string. +func CountString(s string, c byte) int { + // Use a simple implementation, as there is no intrinsic that does this like we want. + // Currently, the compiler does not generate zero-copy byte-string conversions, so this needs to be seperate from Count. + n := 0 + for i := 0; i < len(s); i++ { + if s[i] == c { + n++ + } + } + return n +} + +// Cutover is not reachable in TinyGo, but must exist as it is referenced. +func Cutover(n int) int { + // Setting MaxLen and MaxBruteForce should force a different path to be taken. + // This should never be called. + panic("cutover is unreachable") +} + +// Equal checks if two byte slices are equal. +// It is equivalent to bytes.Equal. +func Equal(a, b []byte) bool { + if len(a) != len(b) { + return false + } + + for i, v := range a { + if v != b[i] { + return false + } + } + + return true +} + +// Index finds the base index of the first instance of the byte sequence b in a. +// If a does not contain b, this returns -1. +func Index(a, b []byte) int { + for i := 0; i <= len(a)-len(b); i++ { + if Equal(a[i:i+len(b)], b) { + return i + } + } + return -1 +} + +// Index finds the index of the first instance of the specified byte in the slice. +// If the byte is not found, this returns -1. +func IndexByte(b []byte, c byte) int { + for i, v := range b { + if v == c { + return i + } + } + return -1 +} + +// Index finds the index of the first instance of the specified byte in the string. +// If the byte is not found, this returns -1. +func IndexByteString(s string, c byte) int { + for i := 0; i < len(s); i++ { + if s[i] == c { + return i + } + } + return -1 +} + +// Index finds the base index of the first instance of a substring in a string. +// If the substring is not found, this returns -1. +func IndexString(str, sub string) int { + for i := 0; i <= len(str)-len(sub); i++ { + if str[i:i+len(sub)] == sub { + return i + } + } + return -1 +} diff --git a/src/runtime/bytes.go b/src/runtime/bytes.go deleted file mode 100644 index 6b3d6be7..00000000 --- a/src/runtime/bytes.go +++ /dev/null @@ -1,11 +0,0 @@ -package runtime - -//go:linkname indexBytePortable internal/bytealg.IndexByte -func indexBytePortable(s []byte, c byte) int { - for i, b := range s { - if b == c { - return i - } - } - return -1 -} diff --git a/src/runtime/string.go b/src/runtime/string.go index 30736d30..e0d0a226 100644 --- a/src/runtime/string.go +++ b/src/runtime/string.go @@ -204,15 +204,3 @@ func decodeUTF8(s string, index uintptr) (rune, uintptr) { return 0xfffd, 1 } } - -// indexByteString returns the index of the first instance of c in s, or -1 if c -// is not present in s. -//go:linkname indexByteString internal/bytealg.IndexByteString -func indexByteString(s string, c byte) int { - for i := 0; i < len(s); i++ { - if s[i] == c { - return i - } - } - return -1 -} diff --git a/src/runtime/string_count.go b/src/runtime/string_count.go deleted file mode 100644 index 6b4f8d42..00000000 --- a/src/runtime/string_count.go +++ /dev/null @@ -1,23 +0,0 @@ -// +build amd64 arm,go1.13 arm64 ppc64le ppc64 s390x - -package runtime - -// This file implements the string counting functions used by the strings -// package, for example. It must be reimplemented here as a replacement for the -// Go stdlib asm implementations, but only when the asm implementations are used -// (this varies by Go version). -// Track this file for updates: -// https://github.com/golang/go/blob/master/src/internal/bytealg/count_native.go - -// countString copies the implementation from -// https://github.com/golang/go/blob/67f181bfd84dfd5942fe9a29d8a20c9ce5eb2fea/src/internal/bytealg/count_generic.go#L1 -//go:linkname countString internal/bytealg.CountString -func countString(s string, c byte) int { - n := 0 - for i := 0; i < len(s); i++ { - if s[i] == c { - n++ - } - } - return n -} diff --git a/src/runtime/strings_go111.go b/src/runtime/strings_go111.go index b13ae7c0..866cb92a 100644 --- a/src/runtime/strings_go111.go +++ b/src/runtime/strings_go111.go @@ -2,11 +2,13 @@ package runtime +import "internal/bytealg" + // indexByte provides compatibility with Go 1.11. // See the following: // https://github.com/tinygo-org/tinygo/issues/351 // https://github.com/golang/go/commit/ad4a58e31501bce5de2aad90a620eaecdc1eecb8 //go:linkname indexByte strings.IndexByte func indexByte(s string, c byte) int { - return indexByteString(s, c) + return bytealg.IndexByteString(s, c) }