mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-17 16:09:13 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2640c9fb54 | ||
|
|
9719d4b0e3 | ||
|
|
b21c69dc1f | ||
|
|
b0f8ff44a5 | ||
|
|
f37bca6a80 | ||
|
|
b4e8fcb752 | ||
|
|
14b98a5d05 | ||
|
|
36a62264f9 | ||
|
|
33ea564f38 | ||
|
|
5c55d8692f | ||
|
|
be2f3036b4 | ||
|
|
784f82f42f | ||
|
|
cd6ba43e77 | ||
|
|
d7aef63844 | ||
|
|
64e5046f10 | ||
|
|
0bdce8aa68 |
12
const.go
12
const.go
@@ -185,12 +185,12 @@ const (
|
||||
type FunctionFlag uint32
|
||||
|
||||
const (
|
||||
DETERMINISTIC FunctionFlag = 0x000000800
|
||||
DIRECTONLY FunctionFlag = 0x000080000
|
||||
INNOCUOUS FunctionFlag = 0x000200000
|
||||
SELFORDER1 FunctionFlag = 0x002000000
|
||||
// SUBTYPE FunctionFlag = 0x000100000
|
||||
// RESULT_SUBTYPE FunctionFlag = 0x001000000
|
||||
DETERMINISTIC FunctionFlag = 0x000000800
|
||||
DIRECTONLY FunctionFlag = 0x000080000
|
||||
SUBTYPE FunctionFlag = 0x000100000
|
||||
INNOCUOUS FunctionFlag = 0x000200000
|
||||
RESULT_SUBTYPE FunctionFlag = 0x001000000
|
||||
SELFORDER1 FunctionFlag = 0x002000000
|
||||
)
|
||||
|
||||
// StmtStatus name counter values associated with the [Stmt.Status] method.
|
||||
|
||||
@@ -227,6 +227,14 @@ func (ctx Context) ResultError(err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ResultSubtype sets the subtype of the result of the function.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_subtype.html
|
||||
func (ctx Context) ResultSubtype(t uint) {
|
||||
ctx.c.call("sqlite3_result_subtype",
|
||||
stk_t(ctx.handle), stk_t(uint32(t)))
|
||||
}
|
||||
|
||||
// VTabNoChange may return true if a column is being fetched as part
|
||||
// of an update during which the column value will not change.
|
||||
//
|
||||
|
||||
@@ -546,8 +546,8 @@ func (s *stmt) setupBindings(args []driver.NamedValue) (err error) {
|
||||
err = s.Stmt.BindTime(id, a, s.tmWrite)
|
||||
case util.JSON:
|
||||
err = s.Stmt.BindJSON(id, a.Value)
|
||||
case util.PointerUnwrap:
|
||||
err = s.Stmt.BindPointer(id, util.UnwrapPointer(a))
|
||||
case util.Pointer:
|
||||
err = s.Stmt.BindPointer(id, a.Value)
|
||||
case nil:
|
||||
err = s.Stmt.BindNull(id)
|
||||
default:
|
||||
@@ -565,7 +565,7 @@ func (s *stmt) CheckNamedValue(arg *driver.NamedValue) error {
|
||||
switch arg.Value.(type) {
|
||||
case bool, int, int64, float64, string, []byte,
|
||||
time.Time, sqlite3.ZeroBlob,
|
||||
util.JSON, util.PointerUnwrap,
|
||||
util.JSON, util.Pointer,
|
||||
nil:
|
||||
return nil
|
||||
default:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Embeddable Wasm build of SQLite
|
||||
|
||||
This folder includes an embeddable Wasm build of SQLite 3.50.1 for use with
|
||||
This folder includes an embeddable Wasm build of SQLite 3.50.3 for use with
|
||||
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
|
||||
|
||||
The following optional features are compiled in:
|
||||
|
||||
Binary file not shown.
@@ -13,8 +13,8 @@ mkdir -p build/ext/
|
||||
cp "$ROOT"/sqlite3/*.[ch] build/
|
||||
cp "$ROOT"/sqlite3/*.patch build/
|
||||
|
||||
# https://sqlite.org/src/info/93740658c8c6f531
|
||||
curl -# https://sqlite.org/src/tarball/sqlite.tar.gz?r=93740658c8 | tar xz
|
||||
# https://sqlite.org/src/info/ba2174bdca7d1d1a
|
||||
curl -# https://sqlite.org/src/tarball/sqlite.tar.gz?r=ba2174bdca | tar xz
|
||||
|
||||
cd sqlite
|
||||
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
|
||||
|
||||
@@ -4,11 +4,11 @@ go 1.23.0
|
||||
|
||||
toolchain go1.24.0
|
||||
|
||||
require github.com/ncruces/go-sqlite3 v0.26.0
|
||||
require github.com/ncruces/go-sqlite3 v0.26.3
|
||||
|
||||
require (
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/ncruces/sort v0.1.5 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
github.com/ncruces/go-sqlite3 v0.26.0 h1:dY6ASfuhSEbtSge6kJwjyJVC7bXCpgEVOycmdboKJek=
|
||||
github.com/ncruces/go-sqlite3 v0.26.0/go.mod h1:46HIzeCQQ+aNleAxCli+vpA2tfh7ttSnw24kQahBc1o=
|
||||
github.com/ncruces/go-sqlite3 v0.26.3 h1:WFkQj4KNMhbqiBPGDrVpK74w1DzcxQu3wYpmdWAvfYM=
|
||||
github.com/ncruces/go-sqlite3 v0.26.3/go.mod h1:XFTPtFIo1DmGCh+XVP8KGn9b/o2f+z0WZuT09x2N6eo=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/ncruces/sort v0.1.5 h1:fiFWXXAqKI8QckPf/6hu/bGFwcEPrirIOFaJqWujs4k=
|
||||
github.com/ncruces/sort v0.1.5/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
|
||||
@@ -98,6 +98,7 @@ sqlite3_result_error_toobig
|
||||
sqlite3_result_int64
|
||||
sqlite3_result_null
|
||||
sqlite3_result_pointer_go
|
||||
sqlite3_result_subtype
|
||||
sqlite3_result_text_go
|
||||
sqlite3_result_value
|
||||
sqlite3_result_zeroblob64
|
||||
@@ -126,6 +127,7 @@ sqlite3_value_int64
|
||||
sqlite3_value_nochange
|
||||
sqlite3_value_numeric_type
|
||||
sqlite3_value_pointer_go
|
||||
sqlite3_value_subtype
|
||||
sqlite3_value_text
|
||||
sqlite3_value_type
|
||||
sqlite3_vtab_collation
|
||||
|
||||
@@ -19,7 +19,7 @@ func Test_init(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version != "3.50.1" {
|
||||
if version != "3.50.3" {
|
||||
t.Error(version)
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -144,18 +144,16 @@ func Test_readblob(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnText(0)
|
||||
if got != tt.want1 {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != tt.want1 {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnText(0)
|
||||
if got != tt.want2 {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != tt.want2 {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
|
||||
err = stmt.Err()
|
||||
|
||||
@@ -147,20 +147,21 @@ func TestAffinity(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "1" {
|
||||
t.Errorf("got %q want 1", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "1" {
|
||||
t.Errorf("got %q want 1", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "0.1" {
|
||||
t.Errorf("got %q want 0.1", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "0.1" {
|
||||
t.Errorf("got %q want 0.1", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "e" {
|
||||
t.Errorf("got %q want e", got)
|
||||
}
|
||||
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "e" {
|
||||
t.Errorf("got %q want e", got)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,10 +141,10 @@ func TestRegister(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnInt(0); got != 3 {
|
||||
t.Errorf("got %d, want 3", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 3 {
|
||||
t.Errorf("got %d, want 3", got)
|
||||
}
|
||||
|
||||
err = db.Exec(`ALTER TABLE v_x RENAME TO v_y`)
|
||||
|
||||
@@ -92,7 +92,9 @@ func TestRegister(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
x := stmt.ColumnInt(0)
|
||||
y := stmt.ColumnInt(1)
|
||||
hypot := stmt.ColumnInt(2)
|
||||
|
||||
@@ -37,7 +37,9 @@ func TestRegister_boolean(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnBool(0); got != true {
|
||||
t.Errorf("got %v, want true", got)
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ type mode struct {
|
||||
|
||||
func (m mode) Value(ctx sqlite3.Context) {
|
||||
var (
|
||||
max = 0
|
||||
typ = sqlite3.NULL
|
||||
max uint
|
||||
i64 int64
|
||||
f64 float64
|
||||
str string
|
||||
@@ -32,7 +32,6 @@ func (m mode) Value(ctx sqlite3.Context) {
|
||||
i64 = k
|
||||
}
|
||||
}
|
||||
f64 = float64(i64)
|
||||
for k, v := range m.reals {
|
||||
if v > max || v == max && k < f64 {
|
||||
typ = sqlite3.FLOAT
|
||||
@@ -66,33 +65,45 @@ func (m mode) Value(ctx sqlite3.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *mode) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
func (m *mode) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
switch arg[0].Type() {
|
||||
case sqlite3.INTEGER:
|
||||
b.ints.add(arg[0].Int64())
|
||||
if m.reals == nil {
|
||||
m.ints.add(arg[0].Int64())
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case sqlite3.FLOAT:
|
||||
b.reals.add(arg[0].Float())
|
||||
m.reals.add(arg[0].Float())
|
||||
for k, v := range m.ints {
|
||||
m.reals[float64(k)] += v
|
||||
}
|
||||
m.ints = nil
|
||||
case sqlite3.TEXT:
|
||||
b.texts.add(arg[0].Text())
|
||||
m.texts.add(arg[0].Text())
|
||||
case sqlite3.BLOB:
|
||||
b.blobs.add(string(arg[0].RawBlob()))
|
||||
m.blobs.add(string(arg[0].RawBlob()))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *mode) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
func (m *mode) Inverse(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
switch arg[0].Type() {
|
||||
case sqlite3.INTEGER:
|
||||
b.ints.del(arg[0].Int64())
|
||||
if m.reals == nil {
|
||||
m.ints.del(arg[0].Int64())
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case sqlite3.FLOAT:
|
||||
b.reals.del(arg[0].Float())
|
||||
m.reals.del(arg[0].Float())
|
||||
case sqlite3.TEXT:
|
||||
b.texts.del(arg[0].Text())
|
||||
m.texts.del(arg[0].Text())
|
||||
case sqlite3.BLOB:
|
||||
b.blobs.del(string(arg[0].RawBlob()))
|
||||
m.blobs.del(string(arg[0].RawBlob()))
|
||||
}
|
||||
}
|
||||
|
||||
type counter[T comparable] map[T]int
|
||||
type counter[T comparable] map[T]uint
|
||||
|
||||
func (c *counter[T]) add(k T) {
|
||||
if (*c) == nil {
|
||||
@@ -102,11 +113,9 @@ func (c *counter[T]) add(k T) {
|
||||
}
|
||||
|
||||
func (c counter[T]) del(k T) {
|
||||
switch n := c[k]; n {
|
||||
default:
|
||||
c[k] = n - 1
|
||||
case 1:
|
||||
if n := c[k]; n == 1 {
|
||||
delete(c, k)
|
||||
case 0:
|
||||
} else {
|
||||
c[k] = n - 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ func TestRegister_mode(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnInt(0); got != 3 {
|
||||
t.Errorf("got %v, want 3", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 3 {
|
||||
t.Errorf("got %v, want 3", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -32,10 +32,10 @@ func TestRegister_mode(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %v, want 1", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %v, want 1", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -43,10 +43,10 @@ func TestRegister_mode(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 2.5 {
|
||||
t.Errorf("got %v, want 2.5", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnFloat(0); got != 2.5 {
|
||||
t.Errorf("got %v, want 2.5", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -54,21 +54,22 @@ func TestRegister_mode(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "red" {
|
||||
t.Errorf("got %q, want red", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "red" {
|
||||
t.Errorf("got %q, want red", got)
|
||||
}
|
||||
|
||||
stmt.Close()
|
||||
|
||||
stmt, _, err = db.Prepare(`SELECT mode(column1) FROM (VALUES (X'cafebabe'), ('green'), ('blue'), (X'cafebabe'))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnText(0); got != "\xca\xfe\xba\xbe" {
|
||||
t.Errorf("got %q, want cafebabe", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnText(0); got != "\xca\xfe\xba\xbe" {
|
||||
t.Errorf("got %q, want cafebabe", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -82,4 +83,20 @@ func TestRegister_mode(t *testing.T) {
|
||||
for stmt.Step() {
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
stmt, _, err = db.Prepare(`SELECT mode(column1) FROM (VALUES (?), (?), (?), (?), (?))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stmt.BindInt(1, 1)
|
||||
stmt.BindInt(2, 1)
|
||||
stmt.BindInt(3, 2)
|
||||
stmt.BindFloat(4, 2)
|
||||
stmt.BindFloat(5, 2)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 2 {
|
||||
t.Errorf("got %v, want 2", got)
|
||||
}
|
||||
stmt.Close()
|
||||
}
|
||||
|
||||
@@ -38,7 +38,9 @@ func TestRegister_percentile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnFloat(0); got != 10 {
|
||||
t.Errorf("got %v, want 10", got)
|
||||
}
|
||||
@@ -65,30 +67,30 @@ func TestRegister_percentile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 5.5 {
|
||||
t.Errorf("got %v, want 5.5", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnFloat(0); got != 5.5 {
|
||||
t.Errorf("got %v, want 5.5", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 7 {
|
||||
t.Errorf("got %v, want 7", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnFloat(0); got != 7 {
|
||||
t.Errorf("got %v, want 7", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 10 {
|
||||
t.Errorf("got %v, want 10", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnFloat(0); got != 10 {
|
||||
t.Errorf("got %v, want 10", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 14.5 {
|
||||
t.Errorf("got %v, want 14.5", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnFloat(0); got != 14.5 {
|
||||
t.Errorf("got %v, want 14.5", got)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 16 {
|
||||
t.Errorf("got %v, want 16", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnFloat(0); got != 16 {
|
||||
t.Errorf("got %v, want 16", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -103,7 +105,9 @@ func TestRegister_percentile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnFloat(0); got != 4 {
|
||||
t.Errorf("got %v, want 4", got)
|
||||
}
|
||||
@@ -134,7 +138,9 @@ func TestRegister_percentile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.NULL {
|
||||
t.Error("want NULL")
|
||||
}
|
||||
|
||||
@@ -58,8 +58,11 @@ import (
|
||||
|
||||
// Register registers statistics functions.
|
||||
func Register(db *sqlite3.Conn) error {
|
||||
const flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
const order = sqlite3.SELFORDER1 | flags
|
||||
const (
|
||||
flags = sqlite3.DETERMINISTIC | sqlite3.INNOCUOUS
|
||||
json = sqlite3.RESULT_SUBTYPE | flags
|
||||
order = sqlite3.SELFORDER1 | flags
|
||||
)
|
||||
return errors.Join(
|
||||
db.CreateWindowFunction("var_pop", 1, flags, newVariance(var_pop)),
|
||||
db.CreateWindowFunction("var_samp", 1, flags, newVariance(var_samp)),
|
||||
@@ -81,7 +84,7 @@ func Register(db *sqlite3.Conn) error {
|
||||
db.CreateWindowFunction("regr_slope", 2, flags, newCovariance(regr_slope)),
|
||||
db.CreateWindowFunction("regr_intercept", 2, flags, newCovariance(regr_intercept)),
|
||||
db.CreateWindowFunction("regr_count", 2, flags, newCovariance(regr_count)),
|
||||
db.CreateWindowFunction("regr_json", 2, flags, newCovariance(regr_json)),
|
||||
db.CreateWindowFunction("regr_json", 2, json, newCovariance(regr_json)),
|
||||
db.CreateWindowFunction("median", 1, order, newPercentile(median)),
|
||||
db.CreateWindowFunction("percentile", 2, order, newPercentile(percentile_100)),
|
||||
db.CreateWindowFunction("percentile_cont", 2, order, newPercentile(percentile_cont)),
|
||||
@@ -227,6 +230,7 @@ func (fn *covariance) Value(ctx sqlite3.Context) {
|
||||
case regr_json:
|
||||
var buf [128]byte
|
||||
ctx.ResultRawText(fn.regr_json(buf[:0]))
|
||||
ctx.ResultSubtype('J')
|
||||
return
|
||||
}
|
||||
ctx.ResultFloat(r)
|
||||
|
||||
@@ -34,10 +34,10 @@ func TestRegister_variance(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.NULL {
|
||||
t.Errorf("got %v, want NULL", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnType(0); got != sqlite3.NULL {
|
||||
t.Errorf("got %v, want NULL", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -57,7 +57,9 @@ func TestRegister_variance(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnFloat(0); got != 40 {
|
||||
t.Errorf("got %v, want 40", got)
|
||||
}
|
||||
@@ -131,7 +133,9 @@ func TestRegister_covariance(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnInt(0); got != 0 {
|
||||
t.Errorf("got %v, want 0", got)
|
||||
}
|
||||
@@ -156,49 +160,50 @@ func TestRegister_covariance(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt.Step() {
|
||||
if got := stmt.ColumnFloat(0); got != 0.9881049293224639 {
|
||||
t.Errorf("got %v, want 0.9881049293224639", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(1); got != 21.25 {
|
||||
t.Errorf("got %v, want 21.25", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(2); got != 17 {
|
||||
t.Errorf("got %v, want 17", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(3); got != 4.2 {
|
||||
t.Errorf("got %v, want 4.2", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(4); got != 75 {
|
||||
t.Errorf("got %v, want 75", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(5); got != 14.8 {
|
||||
t.Errorf("got %v, want 14.8", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(6); got != 500 {
|
||||
t.Errorf("got %v, want 500", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(7); got != 85 {
|
||||
t.Errorf("got %v, want 85", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(8); got != 0.17 {
|
||||
t.Errorf("got %v, want 0.17", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(9); got != -8.55 {
|
||||
t.Errorf("got %v, want -8.55", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(10); got != 0.9763513513513513 {
|
||||
t.Errorf("got %v, want 0.9763513513513513", got)
|
||||
}
|
||||
if got := stmt.ColumnInt(11); got != 5 {
|
||||
t.Errorf("got %v, want 5", got)
|
||||
}
|
||||
var a map[string]float64
|
||||
if err := stmt.ColumnJSON(12, &a); err != nil {
|
||||
t.Error(err)
|
||||
} else if got := a["count"]; got != 5 {
|
||||
t.Errorf("got %v, want 5", got)
|
||||
}
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
}
|
||||
if got := stmt.ColumnFloat(0); got != 0.9881049293224639 {
|
||||
t.Errorf("got %v, want 0.9881049293224639", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(1); got != 21.25 {
|
||||
t.Errorf("got %v, want 21.25", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(2); got != 17 {
|
||||
t.Errorf("got %v, want 17", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(3); got != 4.2 {
|
||||
t.Errorf("got %v, want 4.2", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(4); got != 75 {
|
||||
t.Errorf("got %v, want 75", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(5); got != 14.8 {
|
||||
t.Errorf("got %v, want 14.8", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(6); got != 500 {
|
||||
t.Errorf("got %v, want 500", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(7); got != 85 {
|
||||
t.Errorf("got %v, want 85", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(8); got != 0.17 {
|
||||
t.Errorf("got %v, want 0.17", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(9); got != -8.55 {
|
||||
t.Errorf("got %v, want -8.55", got)
|
||||
}
|
||||
if got := stmt.ColumnFloat(10); got != 0.9763513513513513 {
|
||||
t.Errorf("got %v, want 0.9763513513513513", got)
|
||||
}
|
||||
if got := stmt.ColumnInt(11); got != 5 {
|
||||
t.Errorf("got %v, want 5", got)
|
||||
}
|
||||
var a map[string]float64
|
||||
if err := stmt.ColumnJSON(12, &a); err != nil {
|
||||
t.Error(err)
|
||||
} else if got := a["count"]; got != 5 {
|
||||
t.Errorf("got %v, want 5", got)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
@@ -248,7 +253,9 @@ func Benchmark_average(b *testing.B) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
b.Fatal(stmt.Err())
|
||||
} else {
|
||||
want := float64(b.N) / 2
|
||||
if got := stmt.ColumnFloat(0); got != want {
|
||||
b.Errorf("got %v, want %v", got, want)
|
||||
@@ -282,7 +289,9 @@ func Benchmark_variance(b *testing.B) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if stmt.Step() && b.N > 100 {
|
||||
if !stmt.Step() {
|
||||
b.Fatal(stmt.Err())
|
||||
} else if b.N > 100 {
|
||||
want := float64(b.N*b.N) / 12
|
||||
if got := stmt.ColumnFloat(0); want > (got-want)*float64(b.N) {
|
||||
b.Errorf("got %v, want %v", got, want)
|
||||
|
||||
@@ -26,11 +26,10 @@ func TestRegister(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
return stmt.ColumnText(0)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
}
|
||||
t.Fatal(stmt.Err())
|
||||
return ""
|
||||
return stmt.ColumnText(0)
|
||||
}
|
||||
|
||||
Register(db)
|
||||
|
||||
8
go.mod
8
go.mod
@@ -8,16 +8,16 @@ require (
|
||||
github.com/ncruces/julianday v1.0.0
|
||||
github.com/ncruces/sort v0.1.5
|
||||
github.com/tetratelabs/wazero v1.9.0
|
||||
golang.org/x/crypto v0.39.0
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/crypto v0.40.0
|
||||
golang.org/x/sys v0.34.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dchest/siphash v1.2.3 // ext/bloom
|
||||
github.com/google/uuid v1.6.0 // ext/uuid
|
||||
github.com/psanford/httpreadat v0.1.0 // example
|
||||
golang.org/x/sync v0.15.0 // test
|
||||
golang.org/x/text v0.26.0 // ext/unicode
|
||||
golang.org/x/sync v0.16.0 // test
|
||||
golang.org/x/text v0.27.0 // ext/unicode
|
||||
lukechampine.com/adiantum v1.1.1 // vfs/adiantum
|
||||
)
|
||||
|
||||
|
||||
16
go.sum
16
go.sum
@@ -10,13 +10,13 @@ github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIw
|
||||
github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
lukechampine.com/adiantum v1.1.1 h1:4fp6gTxWCqpEbLy40ExiYDDED3oUNWx5cTqBCtPdZqA=
|
||||
lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw=
|
||||
|
||||
@@ -5,7 +5,7 @@ go 1.23.0
|
||||
toolchain go1.24.0
|
||||
|
||||
require (
|
||||
github.com/ncruces/go-sqlite3 v0.26.0
|
||||
github.com/ncruces/go-sqlite3 v0.26.3
|
||||
gorm.io/gorm v1.30.0
|
||||
)
|
||||
|
||||
@@ -14,6 +14,6 @@ require (
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
)
|
||||
|
||||
@@ -2,15 +2,15 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/ncruces/go-sqlite3 v0.26.0 h1:dY6ASfuhSEbtSge6kJwjyJVC7bXCpgEVOycmdboKJek=
|
||||
github.com/ncruces/go-sqlite3 v0.26.0/go.mod h1:46HIzeCQQ+aNleAxCli+vpA2tfh7ttSnw24kQahBc1o=
|
||||
github.com/ncruces/go-sqlite3 v0.26.3 h1:WFkQj4KNMhbqiBPGDrVpK74w1DzcxQu3wYpmdWAvfYM=
|
||||
github.com/ncruces/go-sqlite3 v0.26.3/go.mod h1:XFTPtFIo1DmGCh+XVP8KGn9b/o2f+z0WZuT09x2N6eo=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
package util
|
||||
|
||||
type Pointer[T any] struct{ Value T }
|
||||
|
||||
func (p Pointer[T]) unwrap() any { return p.Value }
|
||||
|
||||
type PointerUnwrap interface{ unwrap() any }
|
||||
|
||||
func UnwrapPointer(p PointerUnwrap) any {
|
||||
return p.unwrap()
|
||||
}
|
||||
type Pointer struct{ Value any }
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnwrapPointer(t *testing.T) {
|
||||
p := Pointer[float64]{Value: math.Pi}
|
||||
if got := UnwrapPointer(p); got != math.Pi {
|
||||
t.Errorf("want π, got %v", got)
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,6 @@ import "github.com/ncruces/go-sqlite3/internal/util"
|
||||
// [Value.Pointer], or [Context.ResultPointer].
|
||||
//
|
||||
// https://sqlite.org/bindptr.html
|
||||
func Pointer[T any](value T) any {
|
||||
return util.Pointer[T]{Value: value}
|
||||
func Pointer(value any) any {
|
||||
return util.Pointer{Value: value}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# handle, and interrupt, sqlite3_busy_timeout.
|
||||
--- sqlite3.c.orig
|
||||
+++ sqlite3.c
|
||||
@@ -183364,7 +183364,7 @@
|
||||
@@ -184447,7 +184447,7 @@
|
||||
if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
|
||||
#endif
|
||||
if( ms>0 ){
|
||||
@@ -10,4 +10,4 @@
|
||||
+ sqlite3_busy_handler(db, (int(*)(void*,int))sqliteBusyCallback,
|
||||
(void*)db);
|
||||
db->busyTimeout = ms;
|
||||
}else{
|
||||
#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
|
||||
|
||||
@@ -3,7 +3,7 @@ set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
curl -#OL "https://sqlite.org/2025/sqlite-amalgamation-3500100.zip"
|
||||
curl -#OL "https://sqlite.org/2025/sqlite-amalgamation-3500300.zip"
|
||||
unzip -d . sqlite-amalgamation-*.zip
|
||||
mv sqlite-amalgamation-*/sqlite3.c .
|
||||
mv sqlite-amalgamation-*/sqlite3.h .
|
||||
@@ -19,30 +19,30 @@ rm -rf sqlite-amalgamation-*
|
||||
|
||||
mkdir -p ext/
|
||||
cd ext/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/anycollseq.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/base64.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/decimal.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/ieee754.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/regexp.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/series.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/spellfix.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/ext/misc/uint.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/ext/misc/anycollseq.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/ext/misc/base64.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/ext/misc/decimal.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/ext/misc/ieee754.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/ext/misc/regexp.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/ext/misc/series.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/ext/misc/spellfix.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/ext/misc/uint.c"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/mptest/testdata/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/mptest/config01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/mptest/config02.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/mptest/crash01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/mptest/crash02.subtest"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/mptest/multiwrite01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/mptest/config01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/mptest/config02.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/mptest/crash01.test"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/mptest/crash02.subtest"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/mptest/multiwrite01.test"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/mptest/wasm/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/mptest/mptest.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/mptest/mptest.c"
|
||||
cd ~-
|
||||
|
||||
cd ../vfs/tests/speedtest1/wasm/
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.1/test/speedtest1.c"
|
||||
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.50.3/test/speedtest1.c"
|
||||
cd ~-
|
||||
|
||||
cat *.patch | patch -p0 --no-backup-if-mismatch
|
||||
@@ -1,41 +0,0 @@
|
||||
# Using SIMD for libc
|
||||
|
||||
I found that implementing some libc functions with Wasm SIMD128 can make them significantly faster.
|
||||
|
||||
Rough numbers for [wazero](https://wazero.io/):
|
||||
|
||||
function | speedup
|
||||
------------ | -----
|
||||
`strlen` | 4.1×
|
||||
`memchr` | 4.1×
|
||||
`strchr` | 4.0×
|
||||
`strrchr` | 9.1×
|
||||
`memcmp` | 13.0×
|
||||
`strcmp` | 10.4×
|
||||
`strncmp` | 15.7×
|
||||
`strcasecmp` | 8.8×
|
||||
`strncasecmp`| 8.6×
|
||||
`strspn` | 9.9×
|
||||
`strcspn` | 9.0×
|
||||
`memmem` | 2.2×
|
||||
`strstr` | 5.5×
|
||||
`strcasestr` | 25.2×
|
||||
|
||||
For functions where musl uses SWAR on a 4-byte `size_t`,
|
||||
the improvement is around 4×.
|
||||
This is very close to the expected theoretical improvement,
|
||||
as we're processing 4× the bytes per cycle (16 _vs._ 4).
|
||||
|
||||
For other functions where there's no algorithmic change,
|
||||
the improvement is around 8×.
|
||||
These functions are harder to optimize
|
||||
(which is why musl doesn't bother with SWAR),
|
||||
so getting an 8× improvement from processing 16× bytes seems decent.
|
||||
|
||||
String search is harder to compare, since there are algorithmic changes,
|
||||
and different needles produce very different numbers.
|
||||
We use [Quick Search](https://igm.univ-mlv.fr/~lecroq/string/node19.html) for `memmem`,
|
||||
and a [Rabin–Karp](https://igm.univ-mlv.fr/~lecroq/string/node5.html) for `strstr` and `strcasestr`;
|
||||
musl uses [Two Way](https://igm.univ-mlv.fr/~lecroq/string/node26.html) for `memmem` and `strstr`,
|
||||
and [brute force](https://igm.univ-mlv.fr/~lecroq/string/node3.html) for `strcasestr`.
|
||||
Unlike Two-Way, both replacements can go quadratic for long, periodic needles.
|
||||
@@ -13,7 +13,6 @@ trap 'rm -f libc.c libc.tmp' EXIT
|
||||
cat << EOF > libc.c
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
EOF
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
|
||||
@@ -28,31 +27,18 @@ EOF
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
-Wl,--initial-memory=16777216 \
|
||||
-Wl,--export=memccpy \
|
||||
-Wl,--export=memchr \
|
||||
-Wl,--export=memcmp \
|
||||
-Wl,--export=memcpy \
|
||||
-Wl,--export=memmem \
|
||||
-Wl,--export=memmove \
|
||||
-Wl,--export=memrchr \
|
||||
-Wl,--export=memset \
|
||||
-Wl,--export=stpcpy \
|
||||
-Wl,--export=stpncpy \
|
||||
-Wl,--export=strcasecmp \
|
||||
-Wl,--export=strcasestr \
|
||||
-Wl,--export=strchr \
|
||||
-Wl,--export=strchrnul \
|
||||
-Wl,--export=strcmp \
|
||||
-Wl,--export=strcpy \
|
||||
-Wl,--export=strcspn \
|
||||
-Wl,--export=strlen \
|
||||
-Wl,--export=strncasecmp \
|
||||
-Wl,--export=strncat \
|
||||
-Wl,--export=strncmp \
|
||||
-Wl,--export=strncpy \
|
||||
-Wl,--export=strrchr \
|
||||
-Wl,--export=strspn \
|
||||
-Wl,--export=strstr \
|
||||
-Wl,--export=qsort
|
||||
|
||||
"$BINARYEN/wasm-ctor-eval" -g -c _initialize libc.wasm -o libc.tmp
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
_ "embed"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -24,25 +25,18 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
memory []byte
|
||||
module api.Module
|
||||
memset api.Function
|
||||
memcpy api.Function
|
||||
memchr api.Function
|
||||
memcmp api.Function
|
||||
memmem api.Function
|
||||
strlen api.Function
|
||||
strchr api.Function
|
||||
strcmp api.Function
|
||||
strstr api.Function
|
||||
strspn api.Function
|
||||
strrchr api.Function
|
||||
strncmp api.Function
|
||||
strcspn api.Function
|
||||
strcasecmp api.Function
|
||||
strcasestr api.Function
|
||||
strncasecmp api.Function
|
||||
stack [8]uint64
|
||||
memory []byte
|
||||
module api.Module
|
||||
memset api.Function
|
||||
memcpy api.Function
|
||||
memchr api.Function
|
||||
memcmp api.Function
|
||||
strlen api.Function
|
||||
strchr api.Function
|
||||
strspn api.Function
|
||||
strrchr api.Function
|
||||
strcspn api.Function
|
||||
stack [8]uint64
|
||||
)
|
||||
|
||||
func call(fn api.Function, arg ...uint64) uint64 {
|
||||
@@ -68,18 +62,11 @@ func TestMain(m *testing.M) {
|
||||
memcpy = mod.ExportedFunction("memcpy")
|
||||
memchr = mod.ExportedFunction("memchr")
|
||||
memcmp = mod.ExportedFunction("memcmp")
|
||||
memmem = mod.ExportedFunction("memmem")
|
||||
strlen = mod.ExportedFunction("strlen")
|
||||
strchr = mod.ExportedFunction("strchr")
|
||||
strcmp = mod.ExportedFunction("strcmp")
|
||||
strstr = mod.ExportedFunction("strstr")
|
||||
strspn = mod.ExportedFunction("strspn")
|
||||
strrchr = mod.ExportedFunction("strrchr")
|
||||
strncmp = mod.ExportedFunction("strncmp")
|
||||
strcspn = mod.ExportedFunction("strcspn")
|
||||
strcasecmp = mod.ExportedFunction("strcasecmp")
|
||||
strcasestr = mod.ExportedFunction("strcasestr")
|
||||
strncasecmp = mod.ExportedFunction("strncasecmp")
|
||||
memory, _ = mod.Memory().Read(0, mod.Memory().Size())
|
||||
|
||||
os.Exit(m.Run())
|
||||
@@ -166,58 +153,6 @@ func Benchmark_memcmp(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_strcmp(b *testing.B) {
|
||||
clear(memory)
|
||||
fill(memory[ptr1:ptr1+size-1], 7)
|
||||
fill(memory[ptr2:ptr2+size/2], 7)
|
||||
fill(memory[ptr2+size/2:ptr2+size-1], 5)
|
||||
|
||||
b.SetBytes(size/2 + 1)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(strcmp, ptr1, ptr2, size)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_strncmp(b *testing.B) {
|
||||
clear(memory)
|
||||
fill(memory[ptr1:ptr1+size-1], 7)
|
||||
fill(memory[ptr2:ptr2+size/2], 7)
|
||||
fill(memory[ptr2+size/2:ptr2+size-1], 5)
|
||||
|
||||
b.SetBytes(size/2 + 1)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(strncmp, ptr1, ptr2, size-1)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_strcasecmp(b *testing.B) {
|
||||
clear(memory)
|
||||
fill(memory[ptr1:ptr1+size-1], 7)
|
||||
fill(memory[ptr2:ptr2+size/2], 7)
|
||||
fill(memory[ptr2+size/2:ptr2+size-1], 5)
|
||||
|
||||
b.SetBytes(size/2 + 1)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(strcasecmp, ptr1, ptr2, size)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_strncasecmp(b *testing.B) {
|
||||
clear(memory)
|
||||
fill(memory[ptr1:ptr1+size-1], 7)
|
||||
fill(memory[ptr2:ptr2+size/2], 7)
|
||||
fill(memory[ptr2+size/2:ptr2+size-1], 5)
|
||||
|
||||
b.SetBytes(size/2 + 1)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(strncasecmp, ptr1, ptr2, size-1)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_strspn(b *testing.B) {
|
||||
clear(memory)
|
||||
fill(memory[ptr1:ptr1+size/2], 7)
|
||||
@@ -248,51 +183,6 @@ func Benchmark_strcspn(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
//go:embed string.h
|
||||
var source string
|
||||
|
||||
func Benchmark_memmem(b *testing.B) {
|
||||
needle := "memcpy(dest, src, slen)"
|
||||
|
||||
clear(memory)
|
||||
copy(memory[ptr1:], source)
|
||||
copy(memory[ptr2:], needle)
|
||||
|
||||
b.SetBytes(int64(len(source)))
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(memmem, ptr1, uint64(len(source)), ptr2, uint64(len(needle)))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_strstr(b *testing.B) {
|
||||
needle := "memcpy(dest, src, slen)"
|
||||
|
||||
clear(memory)
|
||||
copy(memory[ptr1:], source)
|
||||
copy(memory[ptr2:], needle)
|
||||
|
||||
b.SetBytes(int64(len(source)))
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(strstr, ptr1, ptr2)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_strcasestr(b *testing.B) {
|
||||
needle := "MEMCPY(dest, src, slen)"
|
||||
|
||||
clear(memory)
|
||||
copy(memory[ptr1:], source)
|
||||
copy(memory[ptr2:], needle)
|
||||
|
||||
b.SetBytes(int64(len(source)))
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
call(strcasestr, ptr1, ptr2)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_strlen(t *testing.T) {
|
||||
for length := range 64 {
|
||||
for alignment := range 24 {
|
||||
@@ -341,6 +231,10 @@ func Test_memchr(t *testing.T) {
|
||||
fill(memory[ptr:ptr+max(pos, length)], 5)
|
||||
memory[ptr+pos] = 7
|
||||
|
||||
if pos >= 0 {
|
||||
memory[ptr+pos+2] = 7
|
||||
}
|
||||
|
||||
got := call(memchr, uint64(ptr), 7, uint64(length))
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("memchr(%d, %d, %d) = %d, want %d",
|
||||
@@ -354,9 +248,14 @@ func Test_memchr(t *testing.T) {
|
||||
fill(memory[ptr:ptr+length], 5)
|
||||
memory[len(memory)-1] = 7
|
||||
|
||||
want := len(memory) - 1
|
||||
if length == 0 {
|
||||
want = 0
|
||||
var want int
|
||||
if length != 0 {
|
||||
want = len(memory) - 1
|
||||
got := call(memchr, uint64(ptr), 7, math.MaxUint32)
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("memchr(%d, %d, %d) = %d, want %d",
|
||||
ptr, 7, uint32(math.MaxUint32), uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
|
||||
got := call(memchr, uint64(ptr), 7, uint64(length))
|
||||
@@ -498,48 +397,6 @@ func Test_memcmp(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_strcmp(t *testing.T) {
|
||||
const s1 = compareTest1
|
||||
const s2 = compareTest2
|
||||
|
||||
ptr2 := len(memory) - len(s2) - 1
|
||||
|
||||
clear(memory)
|
||||
copy(memory[ptr1:], s1)
|
||||
copy(memory[ptr2:], s2)
|
||||
|
||||
for i := range len(s1) + 1 {
|
||||
want := strings.Compare(term(s1[i:]), term(s2[i:]))
|
||||
got := call(strcmp, uint64(ptr1+i), uint64(ptr2+i))
|
||||
if sign(int32(got)) != want {
|
||||
t.Errorf("strcmp(%d, %d) = %d, want %d",
|
||||
ptr1+i, ptr2+i, int32(got), want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_strncmp(t *testing.T) {
|
||||
const s1 = compareTest1
|
||||
const s2 = compareTest2
|
||||
|
||||
ptr2 := len(memory) - len(s2) - 1
|
||||
|
||||
clear(memory)
|
||||
copy(memory[ptr1:], s1)
|
||||
copy(memory[ptr2:], s2)
|
||||
|
||||
for i := range len(s1) + 1 {
|
||||
for j := range len(s1) - i + 1 {
|
||||
want := strings.Compare(term(s1[i:i+j]), term(s2[i:i+j]))
|
||||
got := call(strncmp, uint64(ptr1+i), uint64(ptr2+i), uint64(j))
|
||||
if sign(int32(got)) != want {
|
||||
t.Errorf("strncmp(%d, %d, %d) = %d, want %d",
|
||||
ptr1+i, ptr2+i, j, int32(got), want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_strspn(t *testing.T) {
|
||||
for length := range 64 {
|
||||
for pos := range length + 2 {
|
||||
@@ -551,7 +408,7 @@ func Test_strspn(t *testing.T) {
|
||||
fill(memory[ptr:ptr+max(pos, length)], 5)
|
||||
memory[ptr+pos] = 7
|
||||
memory[ptr+length] = 0
|
||||
memory[128] = 3
|
||||
memory[128] = 7 | 128
|
||||
memory[129] = 5
|
||||
|
||||
got := call(strspn, uint64(ptr), 129)
|
||||
@@ -577,7 +434,7 @@ func Test_strspn(t *testing.T) {
|
||||
clear(memory)
|
||||
fill(memory[ptr:ptr+length], 5)
|
||||
memory[len(memory)-1] = 7
|
||||
memory[128] = 3
|
||||
memory[128] = 7 | 128
|
||||
memory[129] = 5
|
||||
|
||||
got := call(strspn, uint64(ptr), 129)
|
||||
@@ -605,7 +462,7 @@ func Test_strcspn(t *testing.T) {
|
||||
fill(memory[ptr:ptr+max(pos, length)], 5)
|
||||
memory[ptr+pos] = 7
|
||||
memory[ptr+length] = 0
|
||||
memory[128] = 3
|
||||
memory[128] = 5 | 128
|
||||
memory[129] = 7
|
||||
|
||||
got := call(strcspn, uint64(ptr), 129)
|
||||
@@ -631,7 +488,7 @@ func Test_strcspn(t *testing.T) {
|
||||
clear(memory)
|
||||
fill(memory[ptr:ptr+length], 5)
|
||||
memory[len(memory)-1] = 7
|
||||
memory[128] = 3
|
||||
memory[128] = 5 | 128
|
||||
memory[129] = 7
|
||||
|
||||
got := call(strcspn, uint64(ptr), 129)
|
||||
@@ -782,102 +639,6 @@ var searchTests = []searchTest{
|
||||
{"000000000000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000001", 5},
|
||||
}
|
||||
|
||||
func Test_memmem(t *testing.T) {
|
||||
tt := append(searchTests,
|
||||
searchTest{"abcABCabc", "A", 3},
|
||||
searchTest{"fofofofofofo\x00foffofoobar", "foffof", 13},
|
||||
searchTest{"0000000000000000\x000123456789012345678901234567890", "0123456789012345", 17},
|
||||
)
|
||||
|
||||
for i := range tt {
|
||||
ptr1 := uint64(len(memory) - len(tt[i].haystk))
|
||||
|
||||
clear(memory)
|
||||
copy(memory[ptr1:], tt[i].haystk)
|
||||
copy(memory[ptr2:], tt[i].needle)
|
||||
|
||||
var want uint64
|
||||
if tt[i].out >= 0 {
|
||||
want = ptr1 + uint64(tt[i].out)
|
||||
}
|
||||
|
||||
got := call(memmem,
|
||||
uint64(ptr1), uint64(len(tt[i].haystk)),
|
||||
uint64(ptr2), uint64(len(tt[i].needle)))
|
||||
if got != want {
|
||||
t.Errorf("memmem(%q, %q) = %d, want %d",
|
||||
tt[i].haystk, tt[i].needle,
|
||||
uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_strstr(t *testing.T) {
|
||||
tt := append(searchTests,
|
||||
searchTest{"abcABCabc", "A", 3},
|
||||
searchTest{"fofofofofofo\x00foffofoobar", "foffof", -1},
|
||||
searchTest{"0000000000000000\x000123456789012345678901234567890", "0123456789012345", -1},
|
||||
)
|
||||
|
||||
for i := range tt {
|
||||
ptr1 := uint64(len(memory) - len(tt[i].haystk) - 1)
|
||||
|
||||
clear(memory)
|
||||
copy(memory[ptr1:], tt[i].haystk)
|
||||
copy(memory[ptr2:], tt[i].needle)
|
||||
|
||||
var want uint64
|
||||
if tt[i].out >= 0 {
|
||||
want = ptr1 + uint64(tt[i].out)
|
||||
}
|
||||
|
||||
got := call(strstr, uint64(ptr1), uint64(ptr2))
|
||||
if got != want {
|
||||
t.Errorf("strstr(%q, %q) = %d, want %d",
|
||||
tt[i].haystk, tt[i].needle,
|
||||
uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_strcasestr(t *testing.T) {
|
||||
tt := append(searchTests[1:],
|
||||
searchTest{"A", "a", 0},
|
||||
searchTest{"a", "A", 0},
|
||||
searchTest{"Z", "z", 0},
|
||||
searchTest{"z", "Z", 0},
|
||||
searchTest{"@", "`", -1},
|
||||
searchTest{"`", "@", -1},
|
||||
searchTest{"[", "{", -1},
|
||||
searchTest{"{", "[", -1},
|
||||
searchTest{"abcABCabc", "A", 0},
|
||||
searchTest{"fofofofofofofoffofoobarfoo", "FoFFoF", 12},
|
||||
searchTest{"fofofofofofofOffOfoobarfoo", "FoFFoF", 12},
|
||||
searchTest{"fofofofofofo\x00foffofoobar", "foffof", -1},
|
||||
searchTest{"0000000000000000\x000123456789012345678901234567890", "0123456789012345", -1},
|
||||
)
|
||||
|
||||
for i := range tt {
|
||||
ptr1 := uint64(len(memory) - len(tt[i].haystk) - 1)
|
||||
|
||||
clear(memory)
|
||||
copy(memory[ptr1:], tt[i].haystk)
|
||||
copy(memory[ptr2:], tt[i].needle)
|
||||
|
||||
var want uint64
|
||||
if tt[i].out >= 0 {
|
||||
want = ptr1 + uint64(tt[i].out)
|
||||
}
|
||||
|
||||
got := call(strcasestr, uint64(ptr1), uint64(ptr2))
|
||||
if got != want {
|
||||
t.Errorf("strcasestr(%q, %q) = %d, want %d",
|
||||
tt[i].haystk, tt[i].needle,
|
||||
uint32(got), uint32(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Fuzz_memchr(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, s string, c, i byte) {
|
||||
if len(s) > 128 || int(i) > len(s) {
|
||||
@@ -971,120 +732,6 @@ func Fuzz_memcmp(f *testing.F) {
|
||||
})
|
||||
}
|
||||
|
||||
func Fuzz_strcmp(f *testing.F) {
|
||||
const s1 = compareTest1
|
||||
const s2 = compareTest2
|
||||
|
||||
for i := range len(compareTest1) + 1 {
|
||||
f.Add(term(s1[i:]), term(s2[i:]))
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, s1, s2 string) {
|
||||
if len(s1) > 128 || len(s2) > 128 {
|
||||
t.SkipNow()
|
||||
}
|
||||
copy(memory[ptr1:], s1)
|
||||
copy(memory[ptr2:], s2)
|
||||
memory[ptr1+len(s1)] = 0
|
||||
memory[ptr2+len(s2)] = 0
|
||||
|
||||
got := call(strcmp, uint64(ptr1), uint64(ptr2))
|
||||
want := strings.Compare(term(s1), term(s2))
|
||||
|
||||
if sign(int32(got)) != want {
|
||||
t.Errorf("strcmp(%q, %q) = %d, want %d",
|
||||
s1, s2, uint32(got), uint32(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Fuzz_strncmp(f *testing.F) {
|
||||
const s1 = compareTest1
|
||||
const s2 = compareTest2
|
||||
|
||||
for i := range len(compareTest1) + 1 {
|
||||
f.Add(term(s1[i:]), term(s2[i:]), byte(len(s1)))
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, s1, s2 string, n byte) {
|
||||
if len(s1) > 128 || len(s2) > 128 {
|
||||
t.SkipNow()
|
||||
}
|
||||
copy(memory[ptr1:], s1)
|
||||
copy(memory[ptr2:], s2)
|
||||
memory[ptr1+len(s1)] = 0
|
||||
memory[ptr2+len(s2)] = 0
|
||||
|
||||
got := call(strncmp, uint64(ptr1), uint64(ptr2), uint64(n))
|
||||
want := bytes.Compare(
|
||||
term(memory[ptr1:][:n]),
|
||||
term(memory[ptr2:][:n]))
|
||||
|
||||
if sign(int32(got)) != want {
|
||||
t.Errorf("strncmp(%q, %q, %d) = %d, want %d",
|
||||
s1, s2, n, uint32(got), uint32(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Fuzz_strcasecmp(f *testing.F) {
|
||||
const s1 = compareTest1
|
||||
const s2 = compareTest2
|
||||
|
||||
for i := range len(compareTest1) + 1 {
|
||||
f.Add(term(s1[i:]), term(s2[i:]))
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, s1, s2 string) {
|
||||
if len(s1) > 128 || len(s2) > 128 {
|
||||
t.SkipNow()
|
||||
}
|
||||
copy(memory[ptr1:], s1)
|
||||
copy(memory[ptr2:], s2)
|
||||
memory[ptr1+len(s1)] = 0
|
||||
memory[ptr2+len(s2)] = 0
|
||||
|
||||
got := call(strcasecmp, uint64(ptr1), uint64(ptr2))
|
||||
want := bytes.Compare(
|
||||
lower(term(memory[ptr1:])),
|
||||
lower(term(memory[ptr2:])))
|
||||
|
||||
if sign(int32(got)) != want {
|
||||
t.Errorf("strcasecmp(%q, %q) = %d, want %d",
|
||||
s1, s2, uint32(got), uint32(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Fuzz_strncasecmp(f *testing.F) {
|
||||
const s1 = compareTest1
|
||||
const s2 = compareTest2
|
||||
|
||||
for i := range len(compareTest1) + 1 {
|
||||
f.Add(term(s1[i:]), term(s2[i:]), byte(len(s1)))
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, s1, s2 string, n byte) {
|
||||
if len(s1) > 128 || len(s2) > 128 {
|
||||
t.SkipNow()
|
||||
}
|
||||
copy(memory[ptr1:], s1)
|
||||
copy(memory[ptr2:], s2)
|
||||
memory[ptr1+len(s1)] = 0
|
||||
memory[ptr2+len(s2)] = 0
|
||||
|
||||
got := call(strncasecmp, uint64(ptr1), uint64(ptr2), uint64(n))
|
||||
want := bytes.Compare(
|
||||
lower(term(memory[ptr1:][:n])),
|
||||
lower(term(memory[ptr2:][:n])))
|
||||
|
||||
if sign(int32(got)) != want {
|
||||
t.Errorf("strncasecmp(%q, %q, %d) = %d, want %d",
|
||||
s1, s2, n, uint32(got), uint32(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Fuzz_strspn(f *testing.F) {
|
||||
for _, t := range searchTests {
|
||||
f.Add(t.haystk, t.needle)
|
||||
@@ -1114,8 +761,10 @@ func Fuzz_strspn(f *testing.F) {
|
||||
}
|
||||
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strspn(%q, %q) = %d, want %d",
|
||||
s, chars, uint32(got), uint32(want))
|
||||
t.Errorf("strspn(%v, %v) = %d, want %d",
|
||||
[]byte(memory[ptr1:ptr1+len(s)]),
|
||||
[]byte(memory[ptr2:ptr2+len(chars)]),
|
||||
uint32(got), uint32(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1155,129 +804,6 @@ func Fuzz_strcspn(f *testing.F) {
|
||||
})
|
||||
}
|
||||
|
||||
func Fuzz_memmem(f *testing.F) {
|
||||
tt := append(searchTests,
|
||||
searchTest{"abcABCabc", "A", 3},
|
||||
searchTest{"fofofofofofo\x00foffofoobar", "foffof", 13},
|
||||
searchTest{"0000000000000000\x000123456789012345678901234567890", "0123456789012345", 17},
|
||||
)
|
||||
|
||||
for _, t := range tt {
|
||||
f.Add(t.haystk, t.needle)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, haystk, needle string) {
|
||||
if len(haystk) > 128 || len(needle) > 128 {
|
||||
t.SkipNow()
|
||||
}
|
||||
copy(memory[ptr1:], haystk)
|
||||
copy(memory[ptr2:], needle)
|
||||
|
||||
got := call(memmem,
|
||||
uint64(ptr1), uint64(len(haystk)),
|
||||
uint64(ptr2), uint64(len(needle)))
|
||||
|
||||
want := strings.Index(haystk, needle)
|
||||
if want >= 0 {
|
||||
want = ptr1 + want
|
||||
} else {
|
||||
want = 0
|
||||
}
|
||||
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("memmem(%q, %q) = %d, want %d",
|
||||
haystk, needle, uint32(got), uint32(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Fuzz_strstr(f *testing.F) {
|
||||
tt := append(searchTests,
|
||||
searchTest{"abcABCabc", "A", 3},
|
||||
searchTest{"fofofofofofo\x00foffofoobar", "foffof", -1},
|
||||
searchTest{"0000000000000000\x000123456789012345678901234567890", "0123456789012345", -1},
|
||||
)
|
||||
|
||||
for _, t := range tt {
|
||||
f.Add(t.haystk, t.needle)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, haystk, needle string) {
|
||||
if len(haystk) > 128 || len(needle) > 128 {
|
||||
t.SkipNow()
|
||||
}
|
||||
copy(memory[ptr1:], haystk)
|
||||
copy(memory[ptr2:], needle)
|
||||
memory[ptr1+len(haystk)] = 0
|
||||
memory[ptr2+len(needle)] = 0
|
||||
|
||||
got := call(strstr, uint64(ptr1), uint64(ptr2))
|
||||
|
||||
want := strings.Index(term(haystk), term(needle))
|
||||
if want >= 0 {
|
||||
want = ptr1 + want
|
||||
} else {
|
||||
want = 0
|
||||
}
|
||||
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strstr(%q, %q) = %d, want %d",
|
||||
haystk, needle, uint32(got), uint32(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Fuzz_strcasestr(f *testing.F) {
|
||||
tt := append(searchTests,
|
||||
searchTest{"A", "a", 0},
|
||||
searchTest{"a", "A", 0},
|
||||
searchTest{"Z", "z", 0},
|
||||
searchTest{"z", "Z", 0},
|
||||
searchTest{"@", "`", -1},
|
||||
searchTest{"`", "@", -1},
|
||||
searchTest{"[", "{", -1},
|
||||
searchTest{"{", "[", -1},
|
||||
searchTest{"abcABCabc", "A", 0},
|
||||
searchTest{"fofofofofofofoffofoobarfoo", "FoFFoF", 12},
|
||||
searchTest{"fofofofofofofOffOfoobarfoo", "FoFFoF", 12},
|
||||
searchTest{"fofofofofofo\x00foffofoobar", "foffof", -1},
|
||||
searchTest{"0000000000000000\x000123456789012345678901234567890", "0123456789012345", -1},
|
||||
)
|
||||
|
||||
for _, t := range tt {
|
||||
f.Add(t.haystk, t.needle)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, haystk, needle string) {
|
||||
if len(haystk) > 128 || len(needle) > 128 {
|
||||
t.SkipNow()
|
||||
}
|
||||
if len(needle) == 0 {
|
||||
t.Skip("musl bug")
|
||||
}
|
||||
copy(memory[ptr1:], haystk)
|
||||
copy(memory[ptr2:], needle)
|
||||
memory[ptr1+len(haystk)] = 0
|
||||
memory[ptr2+len(needle)] = 0
|
||||
|
||||
got := call(strcasestr, uint64(ptr1), uint64(ptr2))
|
||||
|
||||
want := bytes.Index(
|
||||
lower(term(memory[ptr1:])),
|
||||
lower(term(memory[ptr2:])))
|
||||
if want >= 0 {
|
||||
want = ptr1 + want
|
||||
} else {
|
||||
want = 0
|
||||
}
|
||||
|
||||
if uint32(got) != uint32(want) {
|
||||
t.Errorf("strcasestr(%q, %q) = %d, want %d",
|
||||
haystk, needle, uint32(got), uint32(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func sign(x int32) int {
|
||||
switch {
|
||||
case x > 0:
|
||||
@@ -1295,15 +821,6 @@ func fill(s []byte, v byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func lower(s []byte) []byte {
|
||||
for i, c := range s {
|
||||
if 'A' <= c && c <= 'Z' {
|
||||
s[i] = c - 'A' + 'a'
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func term[T interface{ []byte | string }](s T) T {
|
||||
for i, c := range []byte(s) {
|
||||
if c == 0 {
|
||||
|
||||
@@ -3,11 +3,8 @@
|
||||
#ifndef _WASM_SIMD128_STRING_H
|
||||
#define _WASM_SIMD128_STRING_H
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <strings.h>
|
||||
#include <wasm_simd128.h>
|
||||
#include <__macro_PAGESIZE.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -17,19 +14,18 @@ extern "C" {
|
||||
|
||||
// Use the builtins if compiled with bulk memory operations.
|
||||
// Clang will intrinsify using SIMD for small, constant N.
|
||||
// For everything else, this helps inlining.
|
||||
|
||||
__attribute__((weak))
|
||||
__attribute__((weak, always_inline))
|
||||
void *memset(void *dest, int c, size_t n) {
|
||||
return __builtin_memset(dest, c, n);
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
__attribute__((weak, always_inline))
|
||||
void *memcpy(void *__restrict dest, const void *__restrict src, size_t n) {
|
||||
return __builtin_memcpy(dest, src, n);
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
__attribute__((weak, always_inline))
|
||||
void *memmove(void *dest, const void *src, size_t n) {
|
||||
return __builtin_memmove(dest, src, n);
|
||||
}
|
||||
@@ -39,11 +35,11 @@ void *memmove(void *dest, const void *src, size_t n) {
|
||||
#ifdef __wasm_simd128__
|
||||
|
||||
__attribute__((weak))
|
||||
int memcmp(const void *v1, const void *v2, size_t n) {
|
||||
int memcmp(const void *vl, const void *vr, size_t n) {
|
||||
// Scalar algorithm.
|
||||
if (n < sizeof(v128_t)) {
|
||||
const unsigned char *u1 = (unsigned char *)v1;
|
||||
const unsigned char *u2 = (unsigned char *)v2;
|
||||
const unsigned char *u1 = (unsigned char *)vl;
|
||||
const unsigned char *u2 = (unsigned char *)vr;
|
||||
while (n--) {
|
||||
if (*u1 != *u2) return *u1 - *u2;
|
||||
u1++;
|
||||
@@ -56,16 +52,16 @@ int memcmp(const void *v1, const void *v2, size_t n) {
|
||||
// Find the first different character in the objects.
|
||||
// Unaligned loads handle the case where the objects
|
||||
// have mismatching alignments.
|
||||
const v128_t *w1 = (v128_t *)v1;
|
||||
const v128_t *w2 = (v128_t *)v2;
|
||||
const v128_t *v1 = (v128_t *)vl;
|
||||
const v128_t *v2 = (v128_t *)vr;
|
||||
while (n) {
|
||||
const v128_t cmp = wasm_i8x16_eq(wasm_v128_load(w1), wasm_v128_load(w2));
|
||||
const v128_t cmp = wasm_i8x16_eq(wasm_v128_load(v1), wasm_v128_load(v2));
|
||||
// Bitmask is slow on AArch64, all_true is much faster.
|
||||
if (!wasm_i8x16_all_true(cmp)) {
|
||||
// Find the offset of the first zero bit (little-endian).
|
||||
size_t ctz = __builtin_ctz(~wasm_i8x16_bitmask(cmp));
|
||||
const unsigned char *u1 = (unsigned char *)w1 + ctz;
|
||||
const unsigned char *u2 = (unsigned char *)w2 + ctz;
|
||||
const unsigned char *u1 = (unsigned char *)v1 + ctz;
|
||||
const unsigned char *u2 = (unsigned char *)v2 + ctz;
|
||||
// This may help the compiler if the function is inlined.
|
||||
__builtin_assume(*u1 - *u2 != 0);
|
||||
return *u1 - *u2;
|
||||
@@ -73,15 +69,15 @@ int memcmp(const void *v1, const void *v2, size_t n) {
|
||||
// This makes n a multiple of sizeof(v128_t)
|
||||
// for every iteration except the first.
|
||||
size_t align = (n - 1) % sizeof(v128_t) + 1;
|
||||
w1 = (v128_t *)((char *)w1 + align);
|
||||
w2 = (v128_t *)((char *)w2 + align);
|
||||
v1 = (v128_t *)((char *)v1 + align);
|
||||
v2 = (v128_t *)((char *)v2 + align);
|
||||
n -= align;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
void *memchr(const void *v, int c, size_t n) {
|
||||
void *memchr(const void *s, int c, size_t n) {
|
||||
// When n is zero, a function that locates a character finds no occurrence.
|
||||
// Otherwise, decrement n to ensure sub_overflow overflows
|
||||
// when n would go equal-to-or-below zero.
|
||||
@@ -91,29 +87,30 @@ void *memchr(const void *v, int c, size_t n) {
|
||||
|
||||
// memchr must behave as if it reads characters sequentially
|
||||
// and stops as soon as a match is found.
|
||||
// Aligning ensures loads beyond the first match are safe.
|
||||
uintptr_t align = (uintptr_t)v % sizeof(v128_t);
|
||||
const v128_t *w = (v128_t *)((char *)v - align);
|
||||
const v128_t wc = wasm_i8x16_splat(c);
|
||||
// Aligning ensures out of bounds loads are safe.
|
||||
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
v128_t vc = wasm_i8x16_splat(c);
|
||||
|
||||
for (;;) {
|
||||
const v128_t cmp = wasm_i8x16_eq(*w, wc);
|
||||
v128_t v = *(v128_t *)addr;
|
||||
v128_t cmp = wasm_i8x16_eq(v, vc);
|
||||
// Bitmask is slow on AArch64, any_true is much faster.
|
||||
if (wasm_v128_any_true(cmp)) {
|
||||
// Clear the bits corresponding to alignment (little-endian)
|
||||
// Clear the bits corresponding to align (little-endian)
|
||||
// so we can count trailing zeros.
|
||||
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
|
||||
// At least one bit will be set, unless we cleared them.
|
||||
// Knowing this helps the compiler.
|
||||
// At least one bit will be set, unless align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// If the mask is zero because of alignment,
|
||||
// If the mask became zero because of align,
|
||||
// it's as if we didn't find anything.
|
||||
if (mask) {
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
// That's a match, unless it is beyond the end of the object.
|
||||
// Recall that we decremented n, so less-than-or-equal-to is correct.
|
||||
size_t ctz = __builtin_ctz(mask);
|
||||
return ctz <= n + align ? (char *)w + ctz : NULL;
|
||||
return ctz - align <= n ? (char *)s + (addr - (uintptr_t)s + ctz) : NULL;
|
||||
}
|
||||
}
|
||||
// Decrement n; if it overflows we're done.
|
||||
@@ -121,28 +118,30 @@ void *memchr(const void *v, int c, size_t n) {
|
||||
return NULL;
|
||||
}
|
||||
align = 0;
|
||||
w++;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
void *memrchr(const void *v, int c, size_t n) {
|
||||
void *memrchr(const void *s, int c, size_t n) {
|
||||
// memrchr is allowed to read up to n bytes from the object.
|
||||
// Search backward for the last matching character.
|
||||
const v128_t *w = (v128_t *)((char *)v + n);
|
||||
const v128_t wc = wasm_i8x16_splat(c);
|
||||
const v128_t *v = (v128_t *)((char *)s + n);
|
||||
const v128_t vc = wasm_i8x16_splat(c);
|
||||
for (; n >= sizeof(v128_t); n -= sizeof(v128_t)) {
|
||||
const v128_t cmp = wasm_i8x16_eq(wasm_v128_load(--w), wc);
|
||||
const v128_t cmp = wasm_i8x16_eq(wasm_v128_load(--v), vc);
|
||||
// Bitmask is slow on AArch64, any_true is much faster.
|
||||
if (wasm_v128_any_true(cmp)) {
|
||||
// Find the offset of the last one bit (little-endian).
|
||||
size_t clz = __builtin_clz(wasm_i8x16_bitmask(cmp)) - 15;
|
||||
return (char *)(w + 1) - clz;
|
||||
// The leading 16 bits of the bitmask are always zero,
|
||||
// and to be ignored.
|
||||
size_t clz = __builtin_clz(wasm_i8x16_bitmask(cmp)) - 16;
|
||||
return (char *)(v + 1) - (clz + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Scalar algorithm.
|
||||
const char *a = (char *)w;
|
||||
const char *a = (char *)v;
|
||||
while (n--) {
|
||||
if (*(--a) == (char)c) return (char *)a;
|
||||
}
|
||||
@@ -152,141 +151,60 @@ void *memrchr(const void *v, int c, size_t n) {
|
||||
__attribute__((weak))
|
||||
size_t strlen(const char *s) {
|
||||
// strlen must stop as soon as it finds the terminator.
|
||||
// Aligning ensures loads beyond the terminator are safe.
|
||||
// Aligning ensures out of bounds loads are safe.
|
||||
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
|
||||
const v128_t *w = (v128_t *)(s - align);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
|
||||
for (;;) {
|
||||
v128_t v = *(v128_t *)addr;
|
||||
// Bitmask is slow on AArch64, all_true is much faster.
|
||||
if (!wasm_i8x16_all_true(*w)) {
|
||||
const v128_t cmp = wasm_i8x16_eq(*w, (v128_t){});
|
||||
// Clear the bits corresponding to alignment (little-endian)
|
||||
if (!wasm_i8x16_all_true(v)) {
|
||||
const v128_t cmp = wasm_i8x16_eq(v, (v128_t){});
|
||||
// Clear the bits corresponding to align (little-endian)
|
||||
// so we can count trailing zeros.
|
||||
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
|
||||
// At least one bit will be set, unless we cleared them.
|
||||
// Knowing this helps the compiler.
|
||||
// At least one bit will be set, unless align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// If the mask became zero because of align,
|
||||
// it's as if we didn't find anything.
|
||||
if (mask) {
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
return (char *)w - s + __builtin_ctz(mask);
|
||||
return addr - (uintptr_t)s + __builtin_ctz(mask);
|
||||
}
|
||||
}
|
||||
align = 0;
|
||||
w++;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
static int __strcmp_s(const char *s1, const char *s2) {
|
||||
// Scalar algorithm.
|
||||
const unsigned char *u1 = (unsigned char *)s1;
|
||||
const unsigned char *u2 = (unsigned char *)s2;
|
||||
for (;;) {
|
||||
if (*u1 != *u2) return *u1 - *u2;
|
||||
if (*u1 == 0) break;
|
||||
u1++;
|
||||
u2++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __strcmp(const char *s1, const char *s2) {
|
||||
// How many bytes can be read before pointers go out of bounds.
|
||||
size_t N = __builtin_wasm_memory_size(0) * PAGESIZE - //
|
||||
(size_t)(s1 > s2 ? s1 : s2);
|
||||
|
||||
// Unaligned loads handle the case where the strings
|
||||
// have mismatching alignments.
|
||||
const v128_t *w1 = (v128_t *)s1;
|
||||
const v128_t *w2 = (v128_t *)s2;
|
||||
for (; N >= sizeof(v128_t); N -= sizeof(v128_t)) {
|
||||
// Find any single bit difference.
|
||||
if (wasm_v128_any_true(wasm_v128_load(w1) ^ wasm_v128_load(w2))) {
|
||||
// The terminator may come before the difference.
|
||||
break;
|
||||
}
|
||||
// We know all characters are equal.
|
||||
// If any is a terminator the strings are equal.
|
||||
if (!wasm_i8x16_all_true(wasm_v128_load(w1))) {
|
||||
return 0;
|
||||
}
|
||||
w1++;
|
||||
w2++;
|
||||
}
|
||||
|
||||
return __strcmp_s((char *)w1, (char *)w2);
|
||||
}
|
||||
|
||||
__attribute__((weak, always_inline))
|
||||
int strcmp(const char *s1, const char *s2) {
|
||||
// Skip the vector search when comparing against small literal strings.
|
||||
if (__builtin_constant_p(strlen(s2)) && strlen(s2) < sizeof(v128_t)) {
|
||||
return __strcmp_s(s1, s2);
|
||||
}
|
||||
return __strcmp(s1, s2);
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
int strncmp(const char *s1, const char *s2, size_t n) {
|
||||
// How many bytes can be read before pointers go out of bounds.
|
||||
size_t N = __builtin_wasm_memory_size(0) * PAGESIZE - //
|
||||
(size_t)(s1 > s2 ? s1 : s2);
|
||||
if (n > N) n = N;
|
||||
|
||||
// Unaligned loads handle the case where the strings
|
||||
// have mismatching alignments.
|
||||
const v128_t *w1 = (v128_t *)s1;
|
||||
const v128_t *w2 = (v128_t *)s2;
|
||||
for (; n >= sizeof(v128_t); n -= sizeof(v128_t)) {
|
||||
// Find any single bit difference.
|
||||
if (wasm_v128_any_true(wasm_v128_load(w1) ^ wasm_v128_load(w2))) {
|
||||
// The terminator may come before the difference.
|
||||
break;
|
||||
}
|
||||
// We know all characters are equal.
|
||||
// If any is a terminator the strings are equal.
|
||||
if (!wasm_i8x16_all_true(wasm_v128_load(w1))) {
|
||||
return 0;
|
||||
}
|
||||
w1++;
|
||||
w2++;
|
||||
}
|
||||
|
||||
// Scalar algorithm.
|
||||
const unsigned char *u1 = (unsigned char *)w1;
|
||||
const unsigned char *u2 = (unsigned char *)w2;
|
||||
while (n--) {
|
||||
if (*u1 != *u2) return *u1 - *u2;
|
||||
if (*u1 == 0) break;
|
||||
u1++;
|
||||
u2++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *__strchrnul(const char *s, int c) {
|
||||
// strchrnul must stop as soon as a match is found.
|
||||
// Aligning ensures loads beyond the first match are safe.
|
||||
// Aligning ensures out of bounds loads are safe.
|
||||
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
|
||||
const v128_t *w = (v128_t *)(s - align);
|
||||
const v128_t wc = wasm_i8x16_splat(c);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
v128_t vc = wasm_i8x16_splat(c);
|
||||
|
||||
for (;;) {
|
||||
const v128_t cmp = wasm_i8x16_eq(*w, (v128_t){}) | wasm_i8x16_eq(*w, wc);
|
||||
v128_t v = *(v128_t *)addr;
|
||||
const v128_t cmp = wasm_i8x16_eq(v, (v128_t){}) | wasm_i8x16_eq(v, vc);
|
||||
// Bitmask is slow on AArch64, any_true is much faster.
|
||||
if (wasm_v128_any_true(cmp)) {
|
||||
// Clear the bits corresponding to alignment (little-endian)
|
||||
// Clear the bits corresponding to align (little-endian)
|
||||
// so we can count trailing zeros.
|
||||
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
|
||||
// At least one bit will be set, unless we cleared them.
|
||||
// Knowing this helps the compiler.
|
||||
// At least one bit will be set, unless align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// If the mask became zero because of align,
|
||||
// it's as if we didn't find anything.
|
||||
if (mask) {
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
return (char *)w + __builtin_ctz(mask);
|
||||
return (char *)s + (addr - (uintptr_t)s + __builtin_ctz(mask));
|
||||
}
|
||||
}
|
||||
align = 0;
|
||||
w++;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,34 +239,24 @@ char *strrchr(const char *s, int c) {
|
||||
return (char *)memrchr(s, c, strlen(s) + 1);
|
||||
}
|
||||
|
||||
// SIMDized check which bytes are in a set
|
||||
// SIMDized check which bytes are in a set (Geoff Langdale)
|
||||
// http://0x80.pl/notesen/2018-10-18-simd-byte-lookup.html
|
||||
|
||||
typedef struct {
|
||||
__u8x16 l;
|
||||
__u8x16 h;
|
||||
__u8x16 lo;
|
||||
__u8x16 hi;
|
||||
} __wasm_v128_bitmap256_t;
|
||||
|
||||
__attribute__((always_inline))
|
||||
static void __wasm_v128_setbit(__wasm_v128_bitmap256_t *bitmap, int i) {
|
||||
uint8_t hi_nibble = (uint8_t)i >> 4;
|
||||
uint8_t lo_nibble = (uint8_t)i & 0xf;
|
||||
bitmap->l[lo_nibble] |= 1 << (hi_nibble - 0);
|
||||
bitmap->h[lo_nibble] |= 1 << (hi_nibble - 8);
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static int __wasm_v128_chkbit(__wasm_v128_bitmap256_t bitmap, int i) {
|
||||
uint8_t hi_nibble = (uint8_t)i >> 4;
|
||||
uint8_t lo_nibble = (uint8_t)i & 0xf;
|
||||
uint8_t bitmask = 1 << (hi_nibble & 0x7);
|
||||
uint8_t bitset = (hi_nibble < 8 ? bitmap.l : bitmap.h)[lo_nibble];
|
||||
return bitmask & bitset;
|
||||
bitmap->lo[lo_nibble] |= (uint8_t)((uint32_t)1 << (hi_nibble - 0));
|
||||
bitmap->hi[lo_nibble] |= (uint8_t)((uint32_t)1 << (hi_nibble - 8));
|
||||
}
|
||||
|
||||
#ifndef __wasm_relaxed_simd__
|
||||
|
||||
#define wasm_i8x16_relaxed_laneselect wasm_v128_bitselect
|
||||
#define wasm_i8x16_relaxed_swizzle wasm_i8x16_swizzle
|
||||
|
||||
#endif // __wasm_relaxed_simd__
|
||||
@@ -356,47 +264,53 @@ static int __wasm_v128_chkbit(__wasm_v128_bitmap256_t bitmap, int i) {
|
||||
__attribute__((always_inline))
|
||||
static v128_t __wasm_v128_chkbits(__wasm_v128_bitmap256_t bitmap, v128_t v) {
|
||||
v128_t hi_nibbles = wasm_u8x16_shr(v, 4);
|
||||
v128_t lo_nibbles = v & wasm_u8x16_const_splat(0xf);
|
||||
|
||||
v128_t bitmask_lookup = wasm_u8x16_const(1, 2, 4, 8, 16, 32, 64, 128, //
|
||||
1, 2, 4, 8, 16, 32, 64, 128);
|
||||
|
||||
v128_t bitmask = wasm_i8x16_relaxed_swizzle(bitmask_lookup, hi_nibbles);
|
||||
v128_t bitsets = wasm_i8x16_relaxed_laneselect(
|
||||
wasm_i8x16_relaxed_swizzle(bitmap.l, lo_nibbles),
|
||||
wasm_i8x16_relaxed_swizzle(bitmap.h, lo_nibbles),
|
||||
wasm_i8x16_lt(hi_nibbles, wasm_u8x16_const_splat(8)));
|
||||
|
||||
v128_t indices_0_7 = v & wasm_u8x16_const_splat(0x8f);
|
||||
v128_t indices_8_15 = indices_0_7 ^ wasm_u8x16_const_splat(0x80);
|
||||
|
||||
v128_t row_0_7 = wasm_i8x16_swizzle(bitmap.lo, indices_0_7);
|
||||
v128_t row_8_15 = wasm_i8x16_swizzle(bitmap.hi, indices_8_15);
|
||||
|
||||
v128_t bitsets = row_0_7 | row_8_15;
|
||||
return wasm_i8x16_eq(bitsets & bitmask, bitmask);
|
||||
}
|
||||
|
||||
#undef wasm_i8x16_relaxed_laneselect
|
||||
#undef wasm_i8x16_relaxed_swizzle
|
||||
|
||||
__attribute__((weak))
|
||||
size_t strspn(const char *s, const char *c) {
|
||||
// How many bytes can be read before the pointer goes out of bounds.
|
||||
size_t N = __builtin_wasm_memory_size(0) * PAGESIZE - (size_t)s;
|
||||
const v128_t *w = (v128_t *)s;
|
||||
const char *const a = s;
|
||||
// strspn must stop as soon as it finds the terminator.
|
||||
// Aligning ensures out of bounds loads are safe.
|
||||
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
|
||||
if (!c[0]) return 0;
|
||||
if (!c[1]) {
|
||||
const v128_t wc = wasm_i8x16_splat(*c);
|
||||
for (; N >= sizeof(v128_t); N -= sizeof(v128_t)) {
|
||||
const v128_t cmp = wasm_i8x16_eq(wasm_v128_load(w), wc);
|
||||
v128_t vc = wasm_i8x16_splat(*c);
|
||||
for (;;) {
|
||||
v128_t v = *(v128_t *)addr;
|
||||
v128_t cmp = wasm_i8x16_eq(v, vc);
|
||||
// Bitmask is slow on AArch64, all_true is much faster.
|
||||
if (!wasm_i8x16_all_true(cmp)) {
|
||||
// Find the offset of the first zero bit (little-endian).
|
||||
size_t ctz = __builtin_ctz(~wasm_i8x16_bitmask(cmp));
|
||||
return (char *)w + ctz - s;
|
||||
// Clear the bits corresponding to align (little-endian)
|
||||
// so we can count trailing zeros.
|
||||
int mask = (uint16_t)~wasm_i8x16_bitmask(cmp) >> align << align;
|
||||
// At least one bit will be set, unless align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// If the mask became zero because of align,
|
||||
// it's as if we didn't find anything.
|
||||
if (mask) {
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
return addr - (uintptr_t)s + __builtin_ctz(mask);
|
||||
}
|
||||
}
|
||||
w++;
|
||||
align = 0;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
|
||||
// Scalar algorithm.
|
||||
for (s = (char *)w; *s == *c; s++);
|
||||
return s - a;
|
||||
}
|
||||
|
||||
__wasm_v128_bitmap256_t bitmap = {};
|
||||
@@ -406,30 +320,37 @@ size_t strspn(const char *s, const char *c) {
|
||||
__wasm_v128_setbit(&bitmap, *c);
|
||||
}
|
||||
|
||||
for (; N >= sizeof(v128_t); N -= sizeof(v128_t)) {
|
||||
const v128_t cmp = __wasm_v128_chkbits(bitmap, wasm_v128_load(w));
|
||||
for (;;) {
|
||||
v128_t v = *(v128_t *)addr;
|
||||
v128_t cmp = __wasm_v128_chkbits(bitmap, v);
|
||||
// Bitmask is slow on AArch64, all_true is much faster.
|
||||
if (!wasm_i8x16_all_true(cmp)) {
|
||||
// Find the offset of the first zero bit (little-endian).
|
||||
size_t ctz = __builtin_ctz(~wasm_i8x16_bitmask(cmp));
|
||||
return (char *)w + ctz - s;
|
||||
// Clear the bits corresponding to align (little-endian)
|
||||
// so we can count trailing zeros.
|
||||
int mask = (uint16_t)~wasm_i8x16_bitmask(cmp) >> align << align;
|
||||
// At least one bit will be set, unless align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// If the mask became zero because of align,
|
||||
// it's as if we didn't find anything.
|
||||
if (mask) {
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
return addr - (uintptr_t)s + __builtin_ctz(mask);
|
||||
}
|
||||
}
|
||||
w++;
|
||||
align = 0;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
|
||||
// Scalar algorithm.
|
||||
for (s = (char *)w; __wasm_v128_chkbit(bitmap, *s); s++);
|
||||
return s - a;
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
size_t strcspn(const char *s, const char *c) {
|
||||
if (!c[0] || !c[1]) return __strchrnul(s, *c) - s;
|
||||
|
||||
// How many bytes can be read before the pointer goes out of bounds.
|
||||
size_t N = __builtin_wasm_memory_size(0) * PAGESIZE - (size_t)s;
|
||||
const v128_t *w = (v128_t *)s;
|
||||
const char *const a = s;
|
||||
// strcspn must stop as soon as it finds the terminator.
|
||||
// Aligning ensures out of bounds loads are safe.
|
||||
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
|
||||
uintptr_t addr = (uintptr_t)s - align;
|
||||
|
||||
__wasm_v128_bitmap256_t bitmap = {};
|
||||
|
||||
@@ -438,229 +359,27 @@ size_t strcspn(const char *s, const char *c) {
|
||||
__wasm_v128_setbit(&bitmap, *c);
|
||||
} while (*c++);
|
||||
|
||||
for (; N >= sizeof(v128_t); N -= sizeof(v128_t)) {
|
||||
const v128_t cmp = __wasm_v128_chkbits(bitmap, wasm_v128_load(w));
|
||||
for (;;) {
|
||||
v128_t v = *(v128_t *)addr;
|
||||
v128_t cmp = __wasm_v128_chkbits(bitmap, v);
|
||||
// Bitmask is slow on AArch64, any_true is much faster.
|
||||
if (wasm_v128_any_true(cmp)) {
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
size_t ctz = __builtin_ctz(wasm_i8x16_bitmask(cmp));
|
||||
return (char *)w + ctz - s;
|
||||
}
|
||||
w++;
|
||||
}
|
||||
|
||||
// Scalar algorithm.
|
||||
for (s = (char *)w; !__wasm_v128_chkbit(bitmap, *s); s++);
|
||||
return s - a;
|
||||
}
|
||||
|
||||
// SIMD-friendly algorithms for substring searching
|
||||
// http://0x80.pl/notesen/2016-11-28-simd-strfind.html
|
||||
|
||||
// For haystacks of known length and large enough needles,
|
||||
// Boyer-Moore's bad-character rule may be useful,
|
||||
// as proposed by Horspool, Sunday and Raita.
|
||||
//
|
||||
// We augment the SIMD algorithm with Quick Search's
|
||||
// bad-character shift.
|
||||
//
|
||||
// https://igm.univ-mlv.fr/~lecroq/string/node14.html
|
||||
// https://igm.univ-mlv.fr/~lecroq/string/node18.html
|
||||
// https://igm.univ-mlv.fr/~lecroq/string/node19.html
|
||||
// https://igm.univ-mlv.fr/~lecroq/string/node22.html
|
||||
|
||||
static const char *__memmem(const char *haystk, size_t sh, //
|
||||
const char *needle, size_t sn, //
|
||||
uint8_t bmbc[256]) {
|
||||
// We've handled empty and single character needles.
|
||||
// The needle is not longer than the haystack.
|
||||
__builtin_assume(2 <= sn && sn <= sh);
|
||||
|
||||
// Find the farthest character not equal to the first one.
|
||||
size_t i = sn - 1;
|
||||
while (i > 0 && needle[0] == needle[i]) i--;
|
||||
if (i == 0) i = sn - 1;
|
||||
|
||||
// Subtracting ensures sub_overflow overflows
|
||||
// when we reach the end of the haystack.
|
||||
if (sh != SIZE_MAX) sh -= sn;
|
||||
|
||||
const v128_t fst = wasm_i8x16_splat(needle[0]);
|
||||
const v128_t lst = wasm_i8x16_splat(needle[i]);
|
||||
|
||||
// The last haystack offset for which loading blk_lst is safe.
|
||||
const char *H = (char *)(__builtin_wasm_memory_size(0) * PAGESIZE - //
|
||||
(sizeof(v128_t) + i));
|
||||
|
||||
while (haystk <= H) {
|
||||
const v128_t blk_fst = wasm_v128_load((v128_t *)(haystk));
|
||||
const v128_t blk_lst = wasm_v128_load((v128_t *)(haystk + i));
|
||||
const v128_t eq_fst = wasm_i8x16_eq(fst, blk_fst);
|
||||
const v128_t eq_lst = wasm_i8x16_eq(lst, blk_lst);
|
||||
|
||||
const v128_t cmp = eq_fst & eq_lst;
|
||||
if (wasm_v128_any_true(cmp)) {
|
||||
// The terminator may come before the match.
|
||||
if (sh == SIZE_MAX && !wasm_i8x16_all_true(blk_fst)) break;
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
// Each iteration clears that bit, tries again.
|
||||
for (uint32_t mask = wasm_i8x16_bitmask(cmp); mask; mask &= mask - 1) {
|
||||
size_t ctz = __builtin_ctz(mask);
|
||||
// The match may be after the end of the haystack.
|
||||
if (ctz > sh) return NULL;
|
||||
// We know the first character matches.
|
||||
if (!bcmp(haystk + ctz + 1, needle + 1, sn - 1)) {
|
||||
return haystk + ctz;
|
||||
}
|
||||
// Clear the bits corresponding to align (little-endian)
|
||||
// so we can count trailing zeros.
|
||||
int mask = wasm_i8x16_bitmask(cmp) >> align << align;
|
||||
// At least one bit will be set, unless align cleared them.
|
||||
// Knowing this helps the compiler if it unrolls the loop.
|
||||
__builtin_assume(mask || align);
|
||||
// If the mask became zero because of align,
|
||||
// it's as if we didn't find anything.
|
||||
if (mask) {
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
return addr - (uintptr_t)s + __builtin_ctz(mask);
|
||||
}
|
||||
}
|
||||
|
||||
size_t skip = sizeof(v128_t);
|
||||
if (sh == SIZE_MAX) {
|
||||
// Have we reached the end of the haystack?
|
||||
if (!wasm_i8x16_all_true(blk_fst)) return NULL;
|
||||
} else {
|
||||
// Apply the bad-character rule to the character to the right
|
||||
// of the righmost character of the search window.
|
||||
if (bmbc) skip += bmbc[(unsigned char)haystk[sn - 1 + sizeof(v128_t)]];
|
||||
// Have we reached the end of the haystack?
|
||||
if (__builtin_sub_overflow(sh, skip, &sh)) return NULL;
|
||||
}
|
||||
haystk += skip;
|
||||
align = 0;
|
||||
addr += sizeof(v128_t);
|
||||
}
|
||||
|
||||
// Scalar algorithm.
|
||||
for (size_t j = 0; j <= sh; j++) {
|
||||
for (size_t i = 0;; i++) {
|
||||
if (sn == i) return haystk;
|
||||
if (sh == SIZE_MAX && !haystk[i]) return NULL;
|
||||
if (needle[i] != haystk[i]) break;
|
||||
}
|
||||
haystk++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
void *memmem(const void *vh, size_t sh, const void *vn, size_t sn) {
|
||||
// Return immediately on empty needle.
|
||||
if (sn == 0) return (void *)vh;
|
||||
|
||||
// Return immediately when needle is longer than haystack.
|
||||
if (sn > sh) return NULL;
|
||||
|
||||
// Skip to the first matching character using memchr,
|
||||
// thereby handling single character needles.
|
||||
const char *needle = (char *)vn;
|
||||
const char *haystk = (char *)memchr(vh, *needle, sh);
|
||||
if (!haystk || sn == 1) return (void *)haystk;
|
||||
|
||||
// The haystack got shorter, is the needle now longer than it?
|
||||
sh -= haystk - (char *)vh;
|
||||
if (sn > sh) return NULL;
|
||||
|
||||
// Is Boyer-Moore's bad-character rule useful?
|
||||
if (sn < sizeof(v128_t) || sh - sn < sizeof(v128_t)) {
|
||||
return (void *)__memmem(haystk, sh, needle, sn, NULL);
|
||||
}
|
||||
|
||||
// Compute Boyer-Moore's bad-character shift function.
|
||||
// Only the last 255 characters of the needle matter for shifts up to 255,
|
||||
// which is good enough for most needles.
|
||||
size_t c = sn;
|
||||
size_t i = 0;
|
||||
if (c >= 255) {
|
||||
i = sn - 255;
|
||||
c = 255;
|
||||
}
|
||||
|
||||
#ifndef _REENTRANT
|
||||
static
|
||||
#endif
|
||||
uint8_t bmbc[256];
|
||||
memset(bmbc, c, sizeof(bmbc));
|
||||
for (; i < sn; i++) {
|
||||
// One less than the usual offset because
|
||||
// we advance at least one vector at a time.
|
||||
bmbc[(unsigned char)needle[i]] = sn - i - 1;
|
||||
}
|
||||
|
||||
return (void *)__memmem(haystk, sh, needle, sn, bmbc);
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
char *strstr(const char *haystk, const char *needle) {
|
||||
// Return immediately on empty needle.
|
||||
if (!needle[0]) return (char *)haystk;
|
||||
|
||||
// Skip to the first matching character using strchr,
|
||||
// thereby handling single character needles.
|
||||
haystk = strchr(haystk, *needle);
|
||||
if (!haystk || !needle[1]) return (char *)haystk;
|
||||
|
||||
return (char *)__memmem(haystk, SIZE_MAX, needle, strlen(needle), NULL);
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
char *strcasestr(const char *haystk, const char *needle) {
|
||||
// Return immediately on empty needle.
|
||||
if (!needle[0]) return (char *)haystk;
|
||||
|
||||
// We've handled empty needles.
|
||||
size_t sn = strlen(needle);
|
||||
__builtin_assume(sn >= 1);
|
||||
|
||||
// Find the farthest character not equal to the first one.
|
||||
size_t i = sn - 1;
|
||||
while (i > 0 && needle[0] == needle[i]) i--;
|
||||
if (i == 0) i = sn - 1;
|
||||
|
||||
const v128_t fstl = wasm_i8x16_splat(tolower(needle[0]));
|
||||
const v128_t fstu = wasm_i8x16_splat(toupper(needle[0]));
|
||||
const v128_t lstl = wasm_i8x16_splat(tolower(needle[i]));
|
||||
const v128_t lstu = wasm_i8x16_splat(toupper(needle[i]));
|
||||
|
||||
// The last haystk offset for which loading blk_lst is safe.
|
||||
const char *H = (char *)(__builtin_wasm_memory_size(0) * PAGESIZE - //
|
||||
(sizeof(v128_t) + i));
|
||||
|
||||
while (haystk <= H) {
|
||||
const v128_t blk_fst = wasm_v128_load((v128_t *)(haystk));
|
||||
const v128_t blk_lst = wasm_v128_load((v128_t *)(haystk + i));
|
||||
const v128_t eq_fst =
|
||||
wasm_i8x16_eq(fstl, blk_fst) | wasm_i8x16_eq(fstu, blk_fst);
|
||||
const v128_t eq_lst =
|
||||
wasm_i8x16_eq(lstl, blk_lst) | wasm_i8x16_eq(lstu, blk_lst);
|
||||
|
||||
const v128_t cmp = eq_fst & eq_lst;
|
||||
if (wasm_v128_any_true(cmp)) {
|
||||
// The terminator may come before the match.
|
||||
if (!wasm_i8x16_all_true(blk_fst)) break;
|
||||
// Find the offset of the first one bit (little-endian).
|
||||
// Each iteration clears that bit, tries again.
|
||||
for (uint32_t mask = wasm_i8x16_bitmask(cmp); mask; mask &= mask - 1) {
|
||||
size_t ctz = __builtin_ctz(mask);
|
||||
if (!strncasecmp(haystk + ctz + 1, needle + 1, sn - 1)) {
|
||||
return (char *)haystk + ctz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Have we reached the end of the haystack?
|
||||
if (!wasm_i8x16_all_true(blk_fst)) return NULL;
|
||||
haystk += sizeof(v128_t);
|
||||
}
|
||||
|
||||
// Scalar algorithm.
|
||||
for (;;) {
|
||||
for (size_t i = 0;; i++) {
|
||||
if (sn == i) return (char *)haystk;
|
||||
if (!haystk[i]) return NULL;
|
||||
if (tolower(needle[i]) != tolower(haystk[i])) break;
|
||||
}
|
||||
haystk++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Given the above SIMD implementations,
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
#include_next <strings.h> // the system strings.h
|
||||
|
||||
#ifndef _WASM_SIMD128_STRINGS_H
|
||||
#define _WASM_SIMD128_STRINGS_H
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <wasm_simd128.h>
|
||||
#include <__macro_PAGESIZE.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef __wasm_simd128__
|
||||
|
||||
#ifdef __OPTIMIZE_SIZE__
|
||||
|
||||
// bcmp is the same as memcmp but only compares for equality.
|
||||
int bcmp(const void *v1, const void *v2, size_t n);
|
||||
|
||||
#else // __OPTIMIZE_SIZE__
|
||||
|
||||
__attribute__((weak))
|
||||
int bcmp(const void *v1, const void *v2, size_t n) {
|
||||
// Scalar algorithm.
|
||||
if (n < sizeof(v128_t)) {
|
||||
const unsigned char *u1 = (unsigned char *)v1;
|
||||
const unsigned char *u2 = (unsigned char *)v2;
|
||||
while (n--) {
|
||||
if (*u1 != *u2) return 1;
|
||||
u1++;
|
||||
u2++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// bcmp is allowed to read up to n bytes from each object.
|
||||
// Unaligned loads handle the case where the objects
|
||||
// have mismatching alignments.
|
||||
const v128_t *w1 = (v128_t *)v1;
|
||||
const v128_t *w2 = (v128_t *)v2;
|
||||
while (n) {
|
||||
// Find any single bit difference.
|
||||
if (wasm_v128_any_true(wasm_v128_load(w1) ^ wasm_v128_load(w2))) {
|
||||
return 1;
|
||||
}
|
||||
// This makes n a multiple of sizeof(v128_t)
|
||||
// for every iteration except the first.
|
||||
size_t align = (n - 1) % sizeof(v128_t) + 1;
|
||||
w1 = (v128_t *)((char *)w1 + align);
|
||||
w2 = (v128_t *)((char *)w2 + align);
|
||||
n -= align;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // __OPTIMIZE_SIZE__
|
||||
|
||||
static v128_t __tolower8x16(v128_t v) {
|
||||
__i8x16 i = v;
|
||||
i = i + wasm_i8x16_splat(INT8_MAX - ('Z'));
|
||||
i = i > wasm_i8x16_splat(INT8_MAX - ('Z' - 'A' + 1));
|
||||
i = i & wasm_i8x16_splat('a' - 'A');
|
||||
return v | i;
|
||||
}
|
||||
|
||||
static int __strcasecmp_s(const char *s1, const char *s2) {
|
||||
// Scalar algorithm.
|
||||
const unsigned char *u1 = (unsigned char *)s1;
|
||||
const unsigned char *u2 = (unsigned char *)s2;
|
||||
for (;;) {
|
||||
int c1 = tolower(*u1);
|
||||
int c2 = tolower(*u2);
|
||||
if (c1 != c2) return c1 - c2;
|
||||
if (c1 == 0) break;
|
||||
u1++;
|
||||
u2++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __strcasecmp(const char *s1, const char *s2) {
|
||||
// How many bytes can be read before pointers go out of bounds.
|
||||
size_t N = __builtin_wasm_memory_size(0) * PAGESIZE - //
|
||||
(size_t)(s1 > s2 ? s1 : s2);
|
||||
|
||||
// Unaligned loads handle the case where the strings
|
||||
// have mismatching alignments.
|
||||
const v128_t *w1 = (v128_t *)s1;
|
||||
const v128_t *w2 = (v128_t *)s2;
|
||||
for (; N >= sizeof(v128_t); N -= sizeof(v128_t)) {
|
||||
v128_t v1 = __tolower8x16(wasm_v128_load(w1));
|
||||
v128_t v2 = __tolower8x16(wasm_v128_load(w2));
|
||||
|
||||
// Find any single bit difference.
|
||||
if (wasm_v128_any_true(v1 ^ v2)) {
|
||||
// The terminator may come before the difference.
|
||||
break;
|
||||
}
|
||||
// We know all characters are equal.
|
||||
// If any is a terminator the strings are equal.
|
||||
if (!wasm_i8x16_all_true(v1)) {
|
||||
return 0;
|
||||
}
|
||||
w1++;
|
||||
w2++;
|
||||
}
|
||||
|
||||
return __strcasecmp_s((char *)w1, (char *)w2);
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
int strcasecmp(const char *s1, const char *s2) {
|
||||
// Skip the vector search when comparing against small literal strings.
|
||||
if (__builtin_constant_p(strlen(s2)) && strlen(s2) < sizeof(v128_t)) {
|
||||
return __strcasecmp_s(s1, s2);
|
||||
}
|
||||
return __strcasecmp(s1, s2);
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
int strncasecmp(const char *s1, const char *s2, size_t n) {
|
||||
// How many bytes can be read before pointers go out of bounds.
|
||||
size_t N = __builtin_wasm_memory_size(0) * PAGESIZE - //
|
||||
(size_t)(s1 > s2 ? s1 : s2);
|
||||
if (n > N) n = N;
|
||||
|
||||
// Unaligned loads handle the case where the strings
|
||||
// have mismatching alignments.
|
||||
const v128_t *w1 = (v128_t *)s1;
|
||||
const v128_t *w2 = (v128_t *)s2;
|
||||
for (; n >= sizeof(v128_t); n -= sizeof(v128_t)) {
|
||||
v128_t v1 = __tolower8x16(wasm_v128_load(w1));
|
||||
v128_t v2 = __tolower8x16(wasm_v128_load(w2));
|
||||
|
||||
// Find any single bit difference.
|
||||
if (wasm_v128_any_true(v1 ^ v2)) {
|
||||
// The terminator may come before the difference.
|
||||
break;
|
||||
}
|
||||
// We know all characters are equal.
|
||||
// If any is a terminator the strings are equal.
|
||||
if (!wasm_i8x16_all_true(v1)) {
|
||||
return 0;
|
||||
}
|
||||
w1++;
|
||||
w2++;
|
||||
}
|
||||
|
||||
// Scalar algorithm.
|
||||
const unsigned char *u1 = (unsigned char *)w1;
|
||||
const unsigned char *u2 = (unsigned char *)w2;
|
||||
while (n--) {
|
||||
int c1 = tolower(*u1);
|
||||
int c2 = tolower(*u2);
|
||||
if (c1 != c2) return c1 - c2;
|
||||
if (c1 == 0) break;
|
||||
u1++;
|
||||
u2++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // __wasm_simd128__
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // _WASM_SIMD128_STRINGS_H
|
||||
@@ -1,41 +0,0 @@
|
||||
# Use strcasecmp and strncasecmp.
|
||||
--- sqlite3.c.orig
|
||||
+++ sqlite3.c
|
||||
@@ -35685,35 +35685,15 @@
|
||||
return sqlite3StrICmp(zLeft, zRight);
|
||||
}
|
||||
SQLITE_PRIVATE int sqlite3StrICmp(const char *zLeft, const char *zRight){
|
||||
- unsigned char *a, *b;
|
||||
- int c, x;
|
||||
- a = (unsigned char *)zLeft;
|
||||
- b = (unsigned char *)zRight;
|
||||
- for(;;){
|
||||
- c = *a;
|
||||
- x = *b;
|
||||
- if( c==x ){
|
||||
- if( c==0 ) break;
|
||||
- }else{
|
||||
- c = (int)UpperToLower[c] - (int)UpperToLower[x];
|
||||
- if( c ) break;
|
||||
- }
|
||||
- a++;
|
||||
- b++;
|
||||
- }
|
||||
- return c;
|
||||
+ return strcasecmp(zLeft, zRight);
|
||||
}
|
||||
SQLITE_API int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){
|
||||
- register unsigned char *a, *b;
|
||||
if( zLeft==0 ){
|
||||
return zRight ? -1 : 0;
|
||||
}else if( zRight==0 ){
|
||||
return 1;
|
||||
}
|
||||
- a = (unsigned char *)zLeft;
|
||||
- b = (unsigned char *)zRight;
|
||||
- while( N-- > 0 && *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; }
|
||||
- return N<0 ? 0 : UpperToLower[*a] - UpperToLower[*b];
|
||||
+ return strncasecmp(zLeft, zRight, N);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1,7 +1,7 @@
|
||||
# Remove VFS registration. Go handles it.
|
||||
--- sqlite3.c.orig
|
||||
+++ sqlite3.c
|
||||
@@ -26726,7 +26726,7 @@
|
||||
@@ -26882,7 +26882,7 @@
|
||||
sqlite3_free(p);
|
||||
return sqlite3_os_init();
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
/*
|
||||
** The list of all registered VFS implementations.
|
||||
*/
|
||||
@@ -26823,7 +26823,7 @@
|
||||
@@ -26979,7 +26979,7 @@
|
||||
sqlite3_mutex_leave(mutex);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
1
stmt.go
1
stmt.go
@@ -631,7 +631,6 @@ func (s *Stmt) ColumnValue(col int) Value {
|
||||
stk_t(s.handle), stk_t(col)))
|
||||
return Value{
|
||||
c: s.c,
|
||||
unprot: true,
|
||||
handle: ptr,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,9 @@ func Test_endianness(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnInt64(0); got != value {
|
||||
t.Errorf("got %d, want %d", got, value)
|
||||
}
|
||||
@@ -67,9 +69,5 @@ func Test_endianness(t *testing.T) {
|
||||
t.Errorf("got %s, want %d", got, value)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -64,7 +64,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
}
|
||||
@@ -73,7 +75,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
}
|
||||
@@ -82,7 +86,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
}
|
||||
@@ -91,7 +97,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.FLOAT {
|
||||
t.Errorf("got %v, want FLOAT", got)
|
||||
}
|
||||
@@ -100,7 +108,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
|
||||
t.Errorf("got %v, want TEXT", got)
|
||||
}
|
||||
@@ -109,7 +119,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.BLOB {
|
||||
t.Errorf("got %v, want BLOB", got)
|
||||
}
|
||||
@@ -118,7 +130,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.BLOB {
|
||||
t.Errorf("got %v, want BLOB", got)
|
||||
}
|
||||
@@ -127,7 +141,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
|
||||
t.Errorf("got %v, want TEXT", got)
|
||||
}
|
||||
@@ -136,7 +152,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
|
||||
t.Errorf("got %v, want TEXT", got)
|
||||
}
|
||||
@@ -148,7 +166,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
}
|
||||
@@ -157,7 +177,9 @@ func TestCreateFunction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.NULL {
|
||||
t.Errorf("got %v, want NULL", got)
|
||||
}
|
||||
|
||||
@@ -168,7 +168,9 @@ func TestStmt(t *testing.T) {
|
||||
t.Errorf(`got %q, want "main"`, got)
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
}
|
||||
@@ -195,7 +197,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
}
|
||||
@@ -222,7 +226,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.INTEGER {
|
||||
t.Errorf("got %v, want INTEGER", got)
|
||||
}
|
||||
@@ -249,7 +255,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.FLOAT {
|
||||
t.Errorf("got %v, want FLOAT", got)
|
||||
}
|
||||
@@ -276,7 +284,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.NULL {
|
||||
t.Errorf("got %v, want NULL", got)
|
||||
}
|
||||
@@ -303,7 +313,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
|
||||
t.Errorf("got %v, want TEXT", got)
|
||||
}
|
||||
@@ -328,7 +340,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
|
||||
t.Errorf("got %v, want TEXT", got)
|
||||
}
|
||||
@@ -353,7 +367,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
|
||||
t.Errorf("got %v, want TEXT", got)
|
||||
}
|
||||
@@ -378,7 +394,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.BLOB {
|
||||
t.Errorf("got %v, want BLOB", got)
|
||||
}
|
||||
@@ -403,7 +421,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.BLOB {
|
||||
t.Errorf("got %v, want BLOB", got)
|
||||
}
|
||||
@@ -428,7 +448,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.BLOB {
|
||||
t.Errorf("got %v, want BLOB", got)
|
||||
}
|
||||
@@ -453,7 +475,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.TEXT {
|
||||
t.Errorf("got %v, want TEXT", got)
|
||||
}
|
||||
@@ -480,7 +504,9 @@ func TestStmt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
if got := stmt.ColumnType(0); got != sqlite3.NULL {
|
||||
t.Errorf("got %v, want NULL", got)
|
||||
}
|
||||
@@ -643,7 +669,9 @@ func TestStmt_ColumnValue(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
val := stmt.ColumnValue(0)
|
||||
if _, err := val.InFirst(); err == nil {
|
||||
t.Error("want error")
|
||||
@@ -675,7 +703,9 @@ func TestStmt_Columns(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
var dest [5]any
|
||||
if err := stmt.Columns(dest[:]...); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -244,7 +244,9 @@ func TestDB_isoWeek(t *testing.T) {
|
||||
tstart := time.Date(1500, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||
for tm := tstart; tm.Before(tend); tm = tm.AddDate(0, 0, 1) {
|
||||
stmt.BindTime(1, tm, sqlite3.TimeFormatDefault)
|
||||
if stmt.Step() {
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else {
|
||||
y, w := tm.ISOWeek()
|
||||
d := tm.Weekday()
|
||||
if d == 0 {
|
||||
|
||||
@@ -37,11 +37,10 @@ func TestConn_Transaction_exec(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
if stmt.Step() {
|
||||
return stmt.ColumnInt(0)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
}
|
||||
t.Fatal(stmt.Err())
|
||||
return 0
|
||||
return stmt.ColumnInt(0)
|
||||
}
|
||||
|
||||
insert := func(succeed bool) (err error) {
|
||||
@@ -130,14 +129,12 @@ func TestConn_Transaction_panic(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnInt(0)
|
||||
if got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
return
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
}
|
||||
if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
t.Fatal(stmt.Err())
|
||||
}()
|
||||
|
||||
err = panics()
|
||||
@@ -213,15 +210,10 @@ func TestConn_Transaction_interrupt(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnInt(0)
|
||||
if got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
err = stmt.Err()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,15 +325,10 @@ func TestConn_Transaction_rollback(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnInt(0)
|
||||
if got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
err = stmt.Err()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,11 +369,10 @@ func TestConn_Savepoint_exec(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
if stmt.Step() {
|
||||
return stmt.ColumnInt(0)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
}
|
||||
t.Fatal(stmt.Err())
|
||||
return 0
|
||||
return stmt.ColumnInt(0)
|
||||
}
|
||||
|
||||
insert := func(succeed bool) (err error) {
|
||||
@@ -469,14 +455,12 @@ func TestConn_Savepoint_panic(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnInt(0)
|
||||
if got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
return
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
}
|
||||
if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
t.Fatal(stmt.Err())
|
||||
}()
|
||||
|
||||
err = panics()
|
||||
@@ -553,15 +537,10 @@ func TestConn_Savepoint_interrupt(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnInt(0)
|
||||
if got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
err = stmt.Err()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,14 +578,9 @@ func TestConn_Savepoint_rollback(t *testing.T) {
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
if stmt.Step() {
|
||||
got := stmt.ColumnInt(0)
|
||||
if got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
err = stmt.Err()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
if !stmt.Step() {
|
||||
t.Fatal(stmt.Err())
|
||||
} else if got := stmt.ColumnInt(0); got != 1 {
|
||||
t.Errorf("got %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
42
value.go
42
value.go
@@ -15,15 +15,6 @@ import (
|
||||
type Value struct {
|
||||
c *Conn
|
||||
handle ptr_t
|
||||
unprot bool
|
||||
copied bool
|
||||
}
|
||||
|
||||
func (v Value) protected() stk_t {
|
||||
if v.unprot {
|
||||
panic(util.ValueErr)
|
||||
}
|
||||
return stk_t(v.handle)
|
||||
}
|
||||
|
||||
// Dup makes a copy of the SQL value and returns a pointer to that copy.
|
||||
@@ -33,7 +24,6 @@ func (v Value) Dup() *Value {
|
||||
ptr := ptr_t(v.c.call("sqlite3_value_dup", stk_t(v.handle)))
|
||||
return &Value{
|
||||
c: v.c,
|
||||
copied: true,
|
||||
handle: ptr,
|
||||
}
|
||||
}
|
||||
@@ -42,9 +32,6 @@ func (v Value) Dup() *Value {
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_dup.html
|
||||
func (dup *Value) Close() error {
|
||||
if !dup.copied {
|
||||
panic(util.ValueErr)
|
||||
}
|
||||
dup.c.call("sqlite3_value_free", stk_t(dup.handle))
|
||||
dup.handle = 0
|
||||
return nil
|
||||
@@ -54,14 +41,21 @@ func (dup *Value) Close() error {
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Type() Datatype {
|
||||
return Datatype(v.c.call("sqlite3_value_type", v.protected()))
|
||||
return Datatype(v.c.call("sqlite3_value_type", stk_t(v.handle)))
|
||||
}
|
||||
|
||||
// Type returns the numeric datatype of the value.
|
||||
// Subtype returns the subtype of the value.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_subtype.html
|
||||
func (v Value) Subtype() uint {
|
||||
return uint(uint32(v.c.call("sqlite3_value_subtype", stk_t(v.handle))))
|
||||
}
|
||||
|
||||
// NumericType returns the numeric datatype of the value.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) NumericType() Datatype {
|
||||
return Datatype(v.c.call("sqlite3_value_numeric_type", v.protected()))
|
||||
return Datatype(v.c.call("sqlite3_value_numeric_type", stk_t(v.handle)))
|
||||
}
|
||||
|
||||
// Bool returns the value as a bool.
|
||||
@@ -85,14 +79,14 @@ func (v Value) Int() int {
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Int64() int64 {
|
||||
return int64(v.c.call("sqlite3_value_int64", v.protected()))
|
||||
return int64(v.c.call("sqlite3_value_int64", stk_t(v.handle)))
|
||||
}
|
||||
|
||||
// Float returns the value as a float64.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Float() float64 {
|
||||
f := uint64(v.c.call("sqlite3_value_double", v.protected()))
|
||||
f := uint64(v.c.call("sqlite3_value_double", stk_t(v.handle)))
|
||||
return math.Float64frombits(f)
|
||||
}
|
||||
|
||||
@@ -138,7 +132,7 @@ func (v Value) Blob(buf []byte) []byte {
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) RawText() []byte {
|
||||
ptr := ptr_t(v.c.call("sqlite3_value_text", v.protected()))
|
||||
ptr := ptr_t(v.c.call("sqlite3_value_text", stk_t(v.handle)))
|
||||
return v.rawBytes(ptr, 1)
|
||||
}
|
||||
|
||||
@@ -148,7 +142,7 @@ func (v Value) RawText() []byte {
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) RawBlob() []byte {
|
||||
ptr := ptr_t(v.c.call("sqlite3_value_blob", v.protected()))
|
||||
ptr := ptr_t(v.c.call("sqlite3_value_blob", stk_t(v.handle)))
|
||||
return v.rawBytes(ptr, 0)
|
||||
}
|
||||
|
||||
@@ -157,14 +151,14 @@ func (v Value) rawBytes(ptr ptr_t, nul int32) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
n := int32(v.c.call("sqlite3_value_bytes", v.protected()))
|
||||
n := int32(v.c.call("sqlite3_value_bytes", stk_t(v.handle)))
|
||||
return util.View(v.c.mod, ptr, int64(n+nul))[:n]
|
||||
}
|
||||
|
||||
// Pointer gets the pointer associated with this value,
|
||||
// or nil if it has no associated pointer.
|
||||
func (v Value) Pointer() any {
|
||||
ptr := ptr_t(v.c.call("sqlite3_value_pointer_go", v.protected()))
|
||||
ptr := ptr_t(v.c.call("sqlite3_value_pointer_go", stk_t(v.handle)))
|
||||
return util.GetHandle(v.c.ctx, ptr)
|
||||
}
|
||||
|
||||
@@ -194,7 +188,7 @@ func (v Value) JSON(ptr any) error {
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) NoChange() bool {
|
||||
b := int32(v.c.call("sqlite3_value_nochange", v.protected()))
|
||||
b := int32(v.c.call("sqlite3_value_nochange", stk_t(v.handle)))
|
||||
return b != 0
|
||||
}
|
||||
|
||||
@@ -202,7 +196,7 @@ func (v Value) NoChange() bool {
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) FromBind() bool {
|
||||
b := int32(v.c.call("sqlite3_value_frombind", v.protected()))
|
||||
b := int32(v.c.call("sqlite3_value_frombind", stk_t(v.handle)))
|
||||
return b != 0
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package memdb
|
||||
|
||||
import (
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -85,9 +84,10 @@ type memDB struct {
|
||||
// +checklocks:memoryMtx
|
||||
refs int32
|
||||
|
||||
shared int32 // +checklocks:lockMtx
|
||||
pending bool // +checklocks:lockMtx
|
||||
reserved bool // +checklocks:lockMtx
|
||||
shared int32 // +checklocks:lockMtx
|
||||
pending bool // +checklocks:lockMtx
|
||||
reserved bool // +checklocks:lockMtx
|
||||
waiter *sync.Cond // +checklocks:lockMtx
|
||||
|
||||
lockMtx sync.Mutex
|
||||
dataMtx sync.RWMutex
|
||||
@@ -195,8 +195,6 @@ func (m *memFile) Size() (int64, error) {
|
||||
return m.size, nil
|
||||
}
|
||||
|
||||
const spinWait = 25 * time.Microsecond
|
||||
|
||||
func (m *memFile) Lock(lock vfs.LockLevel) error {
|
||||
if m.lock >= lock {
|
||||
return nil
|
||||
@@ -228,13 +226,18 @@ func (m *memFile) Lock(lock vfs.LockLevel) error {
|
||||
m.pending = true
|
||||
}
|
||||
|
||||
for before := time.Now(); m.shared > 1; {
|
||||
if time.Since(before) > spinWait {
|
||||
return sqlite3.BUSY
|
||||
if m.shared > 1 {
|
||||
before := time.Now()
|
||||
if m.waiter == nil {
|
||||
m.waiter = sync.NewCond(&m.lockMtx)
|
||||
}
|
||||
defer time.AfterFunc(time.Millisecond, m.waiter.Broadcast).Stop()
|
||||
for m.shared > 1 {
|
||||
if time.Since(before) > time.Millisecond {
|
||||
return sqlite3.BUSY
|
||||
}
|
||||
m.waiter.Wait()
|
||||
}
|
||||
m.lockMtx.Unlock()
|
||||
runtime.Gosched()
|
||||
m.lockMtx.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +260,9 @@ func (m *memFile) Unlock(lock vfs.LockLevel) error {
|
||||
m.pending = false
|
||||
}
|
||||
if lock < vfs.LOCK_SHARED {
|
||||
m.shared--
|
||||
if m.shared--; m.pending && m.shared <= 1 && m.waiter != nil {
|
||||
m.waiter.Broadcast()
|
||||
}
|
||||
}
|
||||
m.lock = lock
|
||||
return nil
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user