Compare commits

...

35 Commits

Author SHA1 Message Date
Nuno Cruces
c780ef16e2 SQLite 3.49.2. 2025-05-07 14:08:18 +01:00
Nuno Cruces
b609930142 Refactor #274. 2025-05-07 12:46:13 +01:00
Nuno Cruces
fd165ce724 Issue #274. 2025-05-07 01:37:52 +01:00
Nuno Cruces
d3973b23e3 More memcmp. 2025-05-06 15:48:58 +01:00
Nuno Cruces
320b68e74f More tests. 2025-05-05 14:47:43 +01:00
Nuno Cruces
2c3850e5d1 Reuse fast funcs. 2025-05-03 01:18:10 +01:00
Nuno Cruces
db7aacff9f Add strrchr. 2025-05-02 14:35:14 +01:00
Nuno Cruces
d748d98e39 Fix. 2025-05-01 12:49:38 +01:00
Nuno Cruces
13b8642384 Compile as C++. 2025-04-29 14:03:59 +01:00
Nuno Cruces
29c5c816cb More libc. 2025-04-27 23:35:13 +01:00
Nuno Cruces
b32db76da6 Fix flake. 2025-04-25 00:40:52 +01:00
Nuno Cruces
383f620a1e Action permissions. 2025-04-25 00:28:03 +01:00
Nuno Cruces
a3c3515e96 Update binaries. 2025-04-25 00:20:26 +01:00
Nuno Cruces
e580f080b9 Test libc. 2025-04-24 23:59:53 +01:00
Nuno Cruces
9ea7099c24 Size optimized versions. 2025-04-24 23:17:30 +01:00
Nuno Cruces
29aa365806 Fix. 2025-04-24 00:41:48 +01:00
Nuno Cruces
bb87a920f7 Fix memchr. 2025-04-22 02:27:50 +01:00
Nuno Cruces
48379336dc Improve strspn. 2025-04-21 18:59:20 +01:00
Nuno Cruces
251a92fa1a Weak symbols. 2025-04-18 14:50:57 +01:00
Nuno Cruces
f5206ea8da Shellsort. 2025-04-18 09:56:18 +01:00
Nuno Cruces
68ef4593d6 Make libc easier to use. 2025-04-17 13:55:44 +01:00
Nuno Cruces
79bf171210 Fix #263. 2025-04-16 16:39:36 +01:00
Nuno Cruces
ad16d329ea Optimize strlen and strchr on ARM (#262) 2025-04-15 00:44:31 +01:00
Nuno Cruces
9706fa9607 Benchmark more CPUs 2025-04-13 13:28:36 +01:00
Nuno Cruces
45494f5fb6 Redundant defers. 2025-04-09 10:21:54 +01:00
Nuno Cruces
1b0bf3495e Fix flake. 2025-04-09 10:21:54 +01:00
Nuno Cruces
73ac7e06f6 Use SIMD libc. 2025-04-09 10:21:44 +01:00
Nuno Cruces
a3ce8f9de5 More. 2025-04-07 01:32:15 +01:00
Nuno Cruces
2043d5fca4 Benchmark. 2025-04-06 23:24:34 +01:00
Nuno Cruces
3bd11a0a86 More SIMD. 2025-04-05 11:16:47 +01:00
Nuno Cruces
39f3fa64eb More SIMD. 2025-04-05 02:03:31 +01:00
Nuno Cruces
4c19387535 SIMD. 2025-04-04 17:47:20 +01:00
Nuno Cruces
e6c9f18934 Benchmark libc. 2025-04-04 10:56:12 +01:00
Nuno Cruces
970eb6a2f9 Fix. 2025-04-02 15:33:21 +01:00
Nuno Cruces
fac27b8bab libc. 2025-04-02 11:55:20 +01:00
53 changed files with 4042 additions and 173 deletions

23
.github/workflows/libc.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Benchmark libc
on:
workflow_dispatch:
permissions:
contents: read
jobs:
test:
strategy:
matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm, macos-13, macos-15]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: stable }
- name: Benchmark
shell: bash
run: sqlite3/libc/benchmark.sh

View File

@@ -1,28 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "$OSTYPE" == "linux"* ]]; then
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-x86_64-linux.tar.gz"
elif [[ "$OSTYPE" == "darwin"* ]]; then
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-arm64-macos.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-arm64-macos.tar.gz"
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-windows.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-x86_64-windows.tar.gz"
fi
# Download tools
mkdir -p tools/
[ -d "tools/wasi-sdk" ] || curl -#L "$WASI_SDK" | tar xzC tools &
[ -d "tools/binaryen" ] || curl -#L "$BINARYEN" | tar xzC tools &
wait
[ -d "tools/wasi-sdk" ] || mv "tools/wasi-sdk"* "tools/wasi-sdk"
[ -d "tools/binaryen" ] || mv "tools/binaryen"* "tools/binaryen"
# Download and build SQLite
sqlite3/download.sh
sqlite3/tools.sh
embed/build.sh
embed/bcw2/build.sh

View File

@@ -17,12 +17,17 @@ on:
- '**.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
test:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
permissions:
contents: write
steps:
- uses: actions/checkout@v4

View File

@@ -241,8 +241,9 @@ func (n *connector) Connect(ctx context.Context) (ret driver.Conn, err error) {
}
}()
old := c.Conn.SetInterrupt(ctx)
defer c.Conn.SetInterrupt(old)
if old := c.Conn.SetInterrupt(ctx); old != ctx {
defer c.Conn.SetInterrupt(old)
}
if !n.pragmas {
err = c.Conn.BusyTimeout(time.Minute)
@@ -362,8 +363,9 @@ func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, e
c.txReset = `; PRAGMA query_only=` + string(c.readOnly)
}
old := c.Conn.SetInterrupt(ctx)
defer c.Conn.SetInterrupt(old)
if old := c.Conn.SetInterrupt(ctx); old != ctx {
defer c.Conn.SetInterrupt(old)
}
err := c.Conn.Exec(txBegin)
if err != nil {
@@ -382,8 +384,10 @@ func (c *conn) Commit() error {
func (c *conn) Rollback() error {
// ROLLBACK even if interrupted.
old := c.Conn.SetInterrupt(context.Background())
defer c.Conn.SetInterrupt(old)
ctx := context.Background()
if old := c.Conn.SetInterrupt(ctx); old != ctx {
defer c.Conn.SetInterrupt(old)
}
return c.Conn.Exec(`ROLLBACK` + c.txReset)
}
@@ -393,8 +397,9 @@ func (c *conn) Prepare(query string) (driver.Stmt, error) {
}
func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
old := c.Conn.SetInterrupt(ctx)
defer c.Conn.SetInterrupt(old)
if old := c.Conn.SetInterrupt(ctx); old != ctx {
defer c.Conn.SetInterrupt(old)
}
s, tail, err := c.Conn.Prepare(query)
if err != nil {
@@ -419,8 +424,9 @@ func (c *conn) ExecContext(ctx context.Context, query string, args []driver.Name
return resultRowsAffected(0), nil
}
old := c.Conn.SetInterrupt(ctx)
defer c.Conn.SetInterrupt(old)
if old := c.Conn.SetInterrupt(ctx); old != ctx {
defer c.Conn.SetInterrupt(old)
}
err := c.Conn.Exec(query)
if err != nil {
@@ -483,8 +489,10 @@ func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (drive
return nil, err
}
old := s.Stmt.Conn().SetInterrupt(ctx)
defer s.Stmt.Conn().SetInterrupt(old)
c := s.Stmt.Conn()
if old := c.SetInterrupt(ctx); old != ctx {
defer c.SetInterrupt(old)
}
err = errors.Join(
s.Stmt.Exec(),
@@ -493,7 +501,7 @@ func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (drive
return nil, err
}
return newResult(s.Stmt.Conn()), nil
return newResult(c), nil
}
func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
@@ -678,13 +686,14 @@ func (r *rows) scanType(index int) scantype {
func (r *rows) loadColumnMetadata() {
if r.nulls == nil {
c := r.Stmt.Conn()
count := r.Stmt.ColumnCount()
nulls := make([]bool, count)
types := make([]string, count)
scans := make([]scantype, count)
for i := range nulls {
if col := r.Stmt.ColumnOriginName(i); col != "" {
types[i], _, nulls[i], _, _, _ = r.Stmt.Conn().TableColumnMetadata(
types[i], _, nulls[i], _, _, _ = c.TableColumnMetadata(
r.Stmt.ColumnDatabaseName(i),
r.Stmt.ColumnTableName(i),
col)
@@ -762,8 +771,10 @@ func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
}
func (r *rows) Next(dest []driver.Value) error {
old := r.Stmt.Conn().SetInterrupt(r.ctx)
defer r.Stmt.Conn().SetInterrupt(old)
c := r.Stmt.Conn()
if old := c.SetInterrupt(r.ctx); old != r.ctx {
defer c.SetInterrupt(old)
}
if !r.Stmt.Step() {
if err := r.Stmt.Err(); err != nil {

View File

@@ -248,8 +248,10 @@ func Test_nested_context(t *testing.T) {
want(inner, 0)
cancel()
if inner.Next() || !errors.Is(inner.Err(), sqlite3.INTERRUPT) {
t.Fatal(inner.Err())
var terr interface{ Temporary() bool }
if inner.Next() || !errors.Is(inner.Err(), context.Canceled) &&
(!errors.As(inner.Err(), &terr) || !terr.Temporary()) {
t.Fatalf("got %v, want cancellation", inner.Err())
}
want(outer, 1)

View File

@@ -1,6 +1,6 @@
# Embeddable Wasm build of SQLite
This folder includes an embeddable Wasm build of SQLite 3.49.1 for use with
This folder includes an embeddable Wasm build of SQLite 3.49.2 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.

View File

@@ -13,11 +13,10 @@ mkdir -p build/ext/
cp "$ROOT"/sqlite3/*.[ch] build/
cp "$ROOT"/sqlite3/*.patch build/
# https://sqlite.org/src/info/c09656c62155a6e8
curl -# https://sqlite.org/src/tarball/sqlite.tar.gz?r=c09656c6 | tar xz
# https://sqlite.org/src/info/9d6517e7cc8bf175
curl -# https://sqlite.org/src/tarball/sqlite.tar.gz?r=9d6517e7 | tar xz
cd sqlite
cat ../repro.patch | patch -p0 --no-backup-if-mismatch
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
MSYS_NO_PATHCONV=1 nmake /f makefile.msc sqlite3.c "OPTS=-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES"
else
@@ -44,8 +43,8 @@ cd ~-
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
-o bcw2.wasm "build/main.c" \
-I"build" \
-o bcw2.wasm build/main.c \
-I"$ROOT/sqlite3/libc" -I"build" \
-mexec-model=reactor \
-msimd128 -mmutable-globals -mmultivalue \
-mbulk-memory -mreference-types \
@@ -61,7 +60,7 @@ cd ~-
"$BINARYEN/wasm-ctor-eval" -g -c _initialize bcw2.wasm -o bcw2.tmp
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
bcw2.tmp -o bcw2.wasm \
bcw2.tmp -o bcw2.wasm --low-memory-unused \
--enable-simd --enable-mutable-globals --enable-multivalue \
--enable-bulk-memory --enable-reference-types \
--enable-nontrapping-float-to-int --enable-sign-ext

View File

@@ -4,11 +4,11 @@ go 1.23.0
toolchain go1.24.0
require github.com/ncruces/go-sqlite3 v0.24.0
require github.com/ncruces/go-sqlite3 v0.25.0
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.30.0 // indirect
golang.org/x/sys v0.32.0 // indirect
)

View File

@@ -1,12 +1,12 @@
github.com/ncruces/go-sqlite3 v0.24.0 h1:Z4jfmzu2NCd4SmyFwLT2OmF3EnTZbqwATvdiuNHNhLA=
github.com/ncruces/go-sqlite3 v0.24.0/go.mod h1:/Vs8ACZHjJ1SA6E9RZUn3EyB1OP3nDQ4z/ar+0fplTQ=
github.com/ncruces/go-sqlite3 v0.25.0 h1:trugKUs98Zwy9KwRr/EUxZHL92LYt7UqcKqAfpGpK+I=
github.com/ncruces/go-sqlite3 v0.25.0/go.mod h1:n6Z7036yFilJx04yV0mi5JWaF66rUmXn1It9Ux8dx68=
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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=

View File

@@ -1,23 +0,0 @@
# https://sqlite.org/src/vpatch?from=67809715977a5bad&to=3f57584710d61174
--- tool/mkpragmatab.tcl
+++ tool/mkpragmatab.tcl
@@ -526,14 +526,17 @@
puts $fd [format {#define PragFlg_%-10s 0x%02x /* %s */} \
$f $fv $flagMeaning($f)]
set fv [expr {$fv*2}]
}
-# Sort the column lists so that longer column lists occur first
+# Sort the column lists so that longer column lists occur first.
+# In the event of a tie, sort column lists lexicographically.
#
proc colscmp {a b} {
- return [expr {[llength $b] - [llength $a]}]
+ set rc [expr {[llength $b] - [llength $a]}]
+ if {$rc} {return $rc}
+ return [string compare $a $b]
}
set cols_list [lsort -command colscmp $cols_list]
# Generate the array of column names used by pragmas that act like
# queries.

View File

@@ -12,7 +12,7 @@ trap 'rm -f sqlite3.tmp' EXIT
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
-o sqlite3.wasm "$ROOT/sqlite3/main.c" \
-I"$ROOT/sqlite3" \
-I"$ROOT/sqlite3/libc" -I"$ROOT/sqlite3" \
-mexec-model=reactor \
-msimd128 -mmutable-globals -mmultivalue \
-mbulk-memory -mreference-types \
@@ -27,7 +27,7 @@ trap 'rm -f sqlite3.tmp' EXIT
"$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
sqlite3.tmp -o sqlite3.wasm \
sqlite3.tmp -o sqlite3.wasm --low-memory-unused \
--enable-simd --enable-mutable-globals --enable-multivalue \
--enable-bulk-memory --enable-reference-types \
--enable-nontrapping-float-to-int --enable-sign-ext

View File

@@ -66,6 +66,7 @@ sqlite3_errmsg
sqlite3_error_offset
sqlite3_errstr
sqlite3_exec
sqlite3_exec_go
sqlite3_expanded_sql
sqlite3_file_control
sqlite3_filename_database

View File

@@ -19,7 +19,7 @@ func Test_init(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if version != "3.49.1" {
if version != "3.49.2" {
t.Error(version)
}
}

Binary file not shown.

View File

@@ -75,7 +75,7 @@ func (e *Error) As(err any) bool {
// Temporary returns true for [BUSY] errors.
func (e *Error) Temporary() bool {
return e.Code() == BUSY
return e.Code() == BUSY || e.Code() == INTERRUPT
}
// Timeout returns true for [BUSY_TIMEOUT] errors.

View File

@@ -64,7 +64,7 @@ func Example() {
func TestMain(m *testing.M) {
sqlite3.AutoExtension(blobio.Register)
sqlite3.AutoExtension(array.Register)
m.Run()
os.Exit(m.Run())
}
func Test_readblob(t *testing.T) {

View File

@@ -14,7 +14,7 @@ import (
func TestMain(m *testing.M) {
sqlite3.AutoExtension(bloom.Register)
m.Run()
os.Exit(m.Run())
}
func TestRegister(t *testing.T) {

View File

@@ -4,6 +4,7 @@ import (
_ "embed"
"fmt"
"log"
"os"
"testing"
"github.com/ncruces/go-sqlite3"
@@ -14,7 +15,7 @@ import (
func TestMain(m *testing.M) {
sqlite3.AutoExtension(closure.Register)
m.Run()
os.Exit(m.Run())
}
func Example() {

View File

@@ -3,6 +3,7 @@ package csv_test
import (
"fmt"
"log"
"os"
"testing"
"github.com/ncruces/go-sqlite3"
@@ -56,7 +57,7 @@ func Example() {
func TestMain(m *testing.M) {
sqlite3.AutoExtension(csv.Register)
m.Run()
os.Exit(m.Run())
}
func TestRegister(t *testing.T) {

View File

@@ -3,6 +3,7 @@ package pivot_test
import (
"fmt"
"log"
"os"
"strings"
"testing"
@@ -85,7 +86,7 @@ func Example() {
func TestMain(m *testing.M) {
sqlite3.AutoExtension(pivot.Register)
m.Run()
os.Exit(m.Run())
}
func TestRegister(t *testing.T) {

View File

@@ -3,6 +3,7 @@ package statement_test
import (
"fmt"
"log"
"os"
"testing"
"github.com/ncruces/go-sqlite3"
@@ -50,7 +51,7 @@ func Example() {
func TestMain(m *testing.M) {
sqlite3.AutoExtension(statement.Register)
m.Run()
os.Exit(m.Run())
}
func TestRegister(t *testing.T) {

View File

@@ -2,6 +2,7 @@ package stats_test
import (
"math"
"os"
"testing"
"github.com/ncruces/go-sqlite3"
@@ -12,7 +13,7 @@ import (
func TestMain(m *testing.M) {
sqlite3.AutoExtension(stats.Register)
m.Run()
os.Exit(m.Run())
}
func TestRegister_variance(t *testing.T) {

8
go.mod
View File

@@ -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.36.0
golang.org/x/sys v0.31.0
golang.org/x/crypto v0.38.0
golang.org/x/sys v0.33.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.12.0 // test
golang.org/x/text v0.23.0 // ext/unicode
golang.org/x/sync v0.14.0 // test
golang.org/x/text v0.25.0 // ext/unicode
lukechampine.com/adiantum v1.1.1 // vfs/adiantum
)

16
go.sum
View File

@@ -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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
lukechampine.com/adiantum v1.1.1 h1:4fp6gTxWCqpEbLy40ExiYDDED3oUNWx5cTqBCtPdZqA=
lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw=

View File

@@ -5,7 +5,7 @@ go 1.23.0
toolchain go1.24.0
require (
github.com/ncruces/go-sqlite3 v0.24.0
github.com/ncruces/go-sqlite3 v0.25.0
gorm.io/gorm v1.25.12
)
@@ -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.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
)

View File

@@ -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.24.0 h1:Z4jfmzu2NCd4SmyFwLT2OmF3EnTZbqwATvdiuNHNhLA=
github.com/ncruces/go-sqlite3 v0.24.0/go.mod h1:/Vs8ACZHjJ1SA6E9RZUn3EyB1OP3nDQ4z/ar+0fplTQ=
github.com/ncruces/go-sqlite3 v0.25.0 h1:trugKUs98Zwy9KwRr/EUxZHL92LYt7UqcKqAfpGpK+I=
github.com/ncruces/go-sqlite3 v0.25.0/go.mod h1:n6Z7036yFilJx04yV0mi5JWaF66rUmXn1It9Ux8dx68=
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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=

View File

@@ -3,7 +3,7 @@ set -euo pipefail
cd -P -- "$(dirname -- "$0")"
curl -#OL "https://sqlite.org/2025/sqlite-amalgamation-3490100.zip"
curl -#OL "https://sqlite.org/2025/sqlite-amalgamation-3490200.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.49.1/ext/misc/anycollseq.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/base64.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/decimal.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/ieee754.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/regexp.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/series.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/spellfix.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/ext/misc/uint.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/ext/misc/anycollseq.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/ext/misc/base64.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/ext/misc/decimal.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/ext/misc/ieee754.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/ext/misc/regexp.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/ext/misc/series.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/ext/misc/spellfix.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/ext/misc/uint.c"
cd ~-
cd ../vfs/tests/mptest/testdata/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/config01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/config02.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/crash01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/crash02.subtest"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/multiwrite01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/mptest/config01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/mptest/config02.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/mptest/crash01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/mptest/crash02.subtest"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/mptest/multiwrite01.test"
cd ~-
cd ../vfs/tests/mptest/wasm/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/mptest/mptest.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/mptest/mptest.c"
cd ~-
cd ../vfs/tests/speedtest1/wasm/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.1/test/speedtest1.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.49.2/test/speedtest1.c"
cd ~-
cat *.patch | patch -p0 --no-backup-if-mismatch

12
sqlite3/libc/benchmark.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail
cd -P -- "$(dirname -- "$0")"
touch empty.S
./build.sh empty.S
go test -bench=.
rm -f empty.S
./build.sh
go test -bench=.

59
sqlite3/libc/build.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail
cd -P -- "$(dirname -- "$0")"
ROOT=../../
BINARYEN="$ROOT/tools/binaryen/bin"
WASI_SDK="$ROOT/tools/wasi-sdk/bin"
SRCS="${1:-libc.c}"
"../tools.sh"
trap 'rm -f libc.c libc.tmp' EXIT
cat << EOF > libc.c
#include <string.h>
#include <stdlib.h>
EOF
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
-o libc.wasm -I. "$SRCS" \
-mexec-model=reactor \
-msimd128 -mmutable-globals -mmultivalue \
-mbulk-memory -mreference-types \
-mnontrapping-fptoint -msign-ext \
-fno-stack-protector -fno-stack-clash-protection \
-Wl,-z,stack-size=1024 \
-Wl,--stack-first \
-Wl,--import-undefined \
-Wl,--initial-memory=16777216 \
-Wl,--export=memccpy \
-Wl,--export=memchr \
-Wl,--export=memcmp \
-Wl,--export=memcpy \
-Wl,--export=memmove \
-Wl,--export=memrchr \
-Wl,--export=memset \
-Wl,--export=stpcpy \
-Wl,--export=stpncpy \
-Wl,--export=strchr \
-Wl,--export=strchrnul \
-Wl,--export=strcmp \
-Wl,--export=strcpy \
-Wl,--export=strcspn \
-Wl,--export=strlen \
-Wl,--export=strncat \
-Wl,--export=strncmp \
-Wl,--export=strncpy \
-Wl,--export=strrchr \
-Wl,--export=strspn \
-Wl,--export=qsort
"$BINARYEN/wasm-ctor-eval" -g -c _initialize libc.wasm -o libc.tmp
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
libc.tmp -o libc.wasm \
--enable-simd --enable-mutable-globals --enable-multivalue \
--enable-bulk-memory --enable-reference-types \
--enable-nontrapping-float-to-int --enable-sign-ext
"$BINARYEN/wasm-dis" -o libc.wat libc.wasm

BIN
sqlite3/libc/libc.wasm Executable file

Binary file not shown.

2447
sqlite3/libc/libc.wat Normal file

File diff suppressed because it is too large Load Diff

620
sqlite3/libc/libc_test.go Normal file
View File

@@ -0,0 +1,620 @@
package libc
import (
"context"
_ "embed"
"os"
"strings"
"testing"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
//go:embed libc.wasm
var binary []byte
const (
page = 64 * 1024
size = 1024 * 1024 * 4
ptr1 = 1024 * 1024
ptr2 = ptr1 + size
)
var (
memory []byte
module api.Module
memset api.Function
memcpy api.Function
memchr api.Function
memcmp api.Function
strlen api.Function
strchr api.Function
strcmp api.Function
strspn api.Function
strrchr api.Function
strncmp api.Function
strcspn api.Function
stack [8]uint64
)
func call(fn api.Function, arg ...uint64) uint64 {
copy(stack[:], arg)
err := fn.CallWithStack(context.Background(), stack[:])
if err != nil {
panic(err)
}
return stack[0]
}
func TestMain(m *testing.M) {
ctx := context.Background()
runtime := wazero.NewRuntime(ctx)
mod, err := runtime.Instantiate(ctx, binary)
if err != nil {
panic(err)
}
module = mod
memset = mod.ExportedFunction("memset")
memcpy = mod.ExportedFunction("memcpy")
memchr = mod.ExportedFunction("memchr")
memcmp = mod.ExportedFunction("memcmp")
strlen = mod.ExportedFunction("strlen")
strchr = mod.ExportedFunction("strchr")
strcmp = mod.ExportedFunction("strcmp")
strspn = mod.ExportedFunction("strspn")
strrchr = mod.ExportedFunction("strrchr")
strncmp = mod.ExportedFunction("strncmp")
strcspn = mod.ExportedFunction("strcspn")
memory, _ = mod.Memory().Read(0, mod.Memory().Size())
os.Exit(m.Run())
}
func Benchmark_memset(b *testing.B) {
clear(memory)
b.SetBytes(size)
b.ResetTimer()
for range b.N {
call(memset, ptr1, 3, size)
}
}
func Benchmark_memcpy(b *testing.B) {
clear(memory)
fill(memory[ptr2:ptr2+size], 5)
b.SetBytes(size)
b.ResetTimer()
for range b.N {
call(memcpy, ptr1, ptr2, size)
}
}
func Benchmark_memchr(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size/2], 7)
fill(memory[ptr1+size/2:ptr1+size], 5)
b.SetBytes(size/2 + 1)
b.ResetTimer()
for range b.N {
call(memchr, ptr1, 5, size)
}
}
func Benchmark_memcmp(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size], 7)
fill(memory[ptr2:ptr2+size/2], 7)
fill(memory[ptr2+size/2:ptr2+size], 5)
b.SetBytes(size/2 + 1)
b.ResetTimer()
for range b.N {
call(memcmp, ptr1, ptr2, size)
}
}
func Benchmark_strlen(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size-1], 5)
b.SetBytes(size)
b.ResetTimer()
for range b.N {
call(strlen, ptr1)
}
}
func Benchmark_strchr(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size/2], 7)
fill(memory[ptr1+size/2:ptr1+size-1], 5)
b.SetBytes(size/2 + 1)
b.ResetTimer()
for range b.N {
call(strchr, ptr1, 5)
}
}
func Benchmark_strrchr(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size/2], 5)
fill(memory[ptr1+size/2:ptr1+size-1], 7)
b.SetBytes(size/2 + 1)
b.ResetTimer()
for range b.N {
call(strrchr, ptr1, 5)
}
}
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_strspn(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size/2], 7)
fill(memory[ptr1+size/2:ptr1+size-1], 5)
memory[ptr2+0] = 3
memory[ptr2+1] = 5
memory[ptr2+2] = 7
memory[ptr2+3] = 9
b.SetBytes(size)
b.ResetTimer()
for range b.N {
call(strspn, ptr1, ptr2)
}
}
func Benchmark_strcspn(b *testing.B) {
clear(memory)
fill(memory[ptr1:ptr1+size/2], 7)
fill(memory[ptr1+size/2:ptr1+size-1], 5)
memory[ptr2+0] = 3
memory[ptr2+1] = 9
b.SetBytes(size)
b.ResetTimer()
for range b.N {
call(strcspn, ptr1, ptr2)
}
}
func Test_memcmp(t *testing.T) {
const s1 string = "" +
"\x94\x63\x8f\x01\x74\x63\x8f\x01\x54\x63\x8f\x01\x34\x63\x8f\x01" +
"\xb4\xf2\x93\x01\x94\xf2\x93\x01\x54\xf1\x93\x01\x34\xf1\x93\x01" +
"\x14\xf1\x93\x01\x14\xf2\x93\x01\x34\xf2\x93\x01\x54\xf2\x93\x01" +
"\x74\xf2\x93\x01\x74\xf1\x93\x01\xd4\xf2\x93\x01\x94\xf1\x93\x01" +
"\xb4\xf1\x93\x01\xd4\xf1\x93\x01\xf4\xf1\x93\x01\xf4\xf2\x93\x01" +
"\x14\xf4\x93\x01\xf4\xf3\x93\x01\xd4\xf3\x93\x01\xb4\xf3\x93\x01" +
"\x94\xf3\x93\x01\x74\x80\x93\x01\x54\xf3\x93\x01\x34\xf3\x93\x01" +
"\x7f\xf3\x93\x01\x00\x01"
const s2 string = "" +
"\x94\x63\x8f\x01\x74\x63\x8f\x01\x54\x63\x8f\x01\x34\x63\x8f\x01" +
"\xb4\xf2\x93\x01\x94\xf2\x93\x01\x54\xf1\x93\x01\x34\xf1\x93\x01" +
"\x14\xf1\x93\x01\x14\xf2\x93\x01\x34\xf2\x93\x01\x54\xf2\x93\x01" +
"\x74\xf2\x93\x01\x74\xf1\x93\x01\xd4\xf2\x93\x01\x94\xf1\x93\x01" +
"\xb4\xf1\x93\x01\xd4\xf1\x93\x01\xf4\xf1\x93\x01\xf4\xf2\x93\x01" +
"\xbc\x40\x96\x01\xf4\xf3\x93\x01\xd4\xf3\x93\x01\xb4\xf3\x93\x01" +
"\x94\xf3\x93\x01\x74\x7f\x93\x01\x54\xf3\x93\x01\x34\xf3\x93\x01" +
"\x80\xf3\x93\x01\x00\x02"
p1 := ptr1
p2 := len(memory) - len(s2)
clear(memory)
copy(memory[p1:], s1)
copy(memory[p2:], s2)
for i := range len(s1) + 1 {
for j := range len(s1) - i {
want := strings.Compare(s1[i:i+j], s2[i:i+j])
got := call(memcmp, uint64(p1+i), uint64(p2+i), uint64(j))
if sign(int32(got)) != want {
t.Errorf("strcmp(%d, %d, %d) = %d, want %d",
ptr1+i, ptr2+i, j, int32(got), want)
}
}
}
}
func Test_strcmp(t *testing.T) {
const s1 string = "" +
"\x94\x63\x8f\x01\x74\x63\x8f\x01\x54\x63\x8f\x01\x34\x63\x8f\x01" +
"\xb4\xf2\x93\x01\x94\xf2\x93\x01\x54\xf1\x93\x01\x34\xf1\x93\x01" +
"\x14\xf1\x93\x01\x14\xf2\x93\x01\x34\xf2\x93\x01\x54\xf2\x93\x01" +
"\x74\xf2\x93\x01\x74\xf1\x93\x01\xd4\xf2\x93\x01\x94\xf1\x93\x01" +
"\xb4\xf1\x93\x01\xd4\xf1\x93\x01\xf4\xf1\x93\x01\xf4\xf2\x93\x01" +
"\x14\xf4\x93\x01\xf4\xf3\x93\x01\xd4\xf3\x93\x01\xb4\xf3\x93\x01" +
"\x94\xf3\x93\x01\x74\x80\x93\x01\x54\xf3\x93\x01\x34\xf3\x93\x01" +
"\x7f\xf3\x93\x01\x00\x01"
const s2 string = "" +
"\x94\x63\x8f\x01\x74\x63\x8f\x01\x54\x63\x8f\x01\x34\x63\x8f\x01" +
"\xb4\xf2\x93\x01\x94\xf2\x93\x01\x54\xf1\x93\x01\x34\xf1\x93\x01" +
"\x14\xf1\x93\x01\x14\xf2\x93\x01\x34\xf2\x93\x01\x54\xf2\x93\x01" +
"\x74\xf2\x93\x01\x74\xf1\x93\x01\xd4\xf2\x93\x01\x94\xf1\x93\x01" +
"\xb4\xf1\x93\x01\xd4\xf1\x93\x01\xf4\xf1\x93\x01\xf4\xf2\x93\x01" +
"\xbc\x40\x96\x01\xf4\xf3\x93\x01\xd4\xf3\x93\x01\xb4\xf3\x93\x01" +
"\x94\xf3\x93\x01\x74\x7f\x93\x01\x54\xf3\x93\x01\x34\xf3\x93\x01" +
"\x80\xf3\x93\x01\x00\x02"
p1 := ptr1
p2 := len(memory) - len(s2) - 1
clear(memory)
copy(memory[p1:], s1)
copy(memory[p2:], s2)
for i := range len(s1) + 1 {
want := strings.Compare(term(s1[i:]), term(s2[i:]))
got := call(strcmp, uint64(p1+i), uint64(p2+i))
if sign(int32(got)) != want {
t.Errorf("strcmp(%d, %d) = %d, want %d",
p1+i, ptr2+i, int32(got), want)
}
}
}
func Test_strncmp(t *testing.T) {
const s1 string = "" +
"\x94\x63\x8f\x01\x74\x63\x8f\x01\x54\x63\x8f\x01\x34\x63\x8f\x01" +
"\xb4\xf2\x93\x01\x94\xf2\x93\x01\x54\xf1\x93\x01\x34\xf1\x93\x01" +
"\x14\xf1\x93\x01\x14\xf2\x93\x01\x34\xf2\x93\x01\x54\xf2\x93\x01" +
"\x74\xf2\x93\x01\x74\xf1\x93\x01\xd4\xf2\x93\x01\x94\xf1\x93\x01" +
"\xb4\xf1\x93\x01\xd4\xf1\x93\x01\xf4\xf1\x93\x01\xf4\xf2\x93\x01" +
"\x14\xf4\x93\x01\xf4\xf3\x93\x01\xd4\xf3\x93\x01\xb4\xf3\x93\x01" +
"\x94\xf3\x93\x01\x74\x80\x93\x01\x54\xf3\x93\x01\x34\xf3\x93\x01" +
"\x7f\xf3\x93\x01\x00\x01"
const s2 string = "" +
"\x94\x63\x8f\x01\x74\x63\x8f\x01\x54\x63\x8f\x01\x34\x63\x8f\x01" +
"\xb4\xf2\x93\x01\x94\xf2\x93\x01\x54\xf1\x93\x01\x34\xf1\x93\x01" +
"\x14\xf1\x93\x01\x14\xf2\x93\x01\x34\xf2\x93\x01\x54\xf2\x93\x01" +
"\x74\xf2\x93\x01\x74\xf1\x93\x01\xd4\xf2\x93\x01\x94\xf1\x93\x01" +
"\xb4\xf1\x93\x01\xd4\xf1\x93\x01\xf4\xf1\x93\x01\xf4\xf2\x93\x01" +
"\xbc\x40\x96\x01\xf4\xf3\x93\x01\xd4\xf3\x93\x01\xb4\xf3\x93\x01" +
"\x94\xf3\x93\x01\x74\x7f\x93\x01\x54\xf3\x93\x01\x34\xf3\x93\x01" +
"\x80\xf3\x93\x01\x00\x02"
p1 := ptr1
p2 := len(memory) - len(s2) - 1
clear(memory)
copy(memory[p1:], s1)
copy(memory[p2:], 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(p1+i), uint64(p2+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_strlen(t *testing.T) {
for length := range 64 {
for alignment := range 24 {
ptr := (page - 8) + alignment
clear(memory[:2*page])
fill(memory[ptr:ptr+length], 5)
got := call(strlen, uint64(ptr))
if uint32(got) != uint32(length) {
t.Errorf("strlen(%d) = %d, want %d",
ptr, uint32(got), uint32(length))
}
memory[ptr-1] = 5
got = call(strlen, uint64(ptr))
if uint32(got) != uint32(length) {
t.Errorf("strlen(%d) = %d, want %d",
ptr, uint32(got), uint32(length))
}
}
clear(memory)
ptr := len(memory) - length - 1
fill(memory[ptr:ptr+length], 5)
got := call(strlen, uint64(ptr))
if uint32(got) != uint32(length) {
t.Errorf("strlen(%d) = %d, want %d",
ptr, uint32(got), uint32(length))
}
}
}
func Test_memchr(t *testing.T) {
for length := range 64 {
for pos := range length + 2 {
for alignment := range 24 {
ptr := (page - 8) + alignment
want := 0
if pos < length {
want = ptr + pos
}
clear(memory[:2*page])
fill(memory[ptr:ptr+max(pos, length)], 5)
memory[ptr+pos] = 7
got := call(memchr, uint64(ptr), 7, uint64(length))
if uint32(got) != uint32(want) {
t.Errorf("memchr(%d, %d, %d) = %d, want %d",
ptr, 7, uint64(length), uint32(got), uint32(want))
}
}
}
clear(memory)
ptr := len(memory) - length
fill(memory[ptr:ptr+length], 5)
memory[len(memory)-1] = 7
want := len(memory) - 1
if length == 0 {
want = 0
}
got := call(memchr, uint64(ptr), 7, uint64(length))
if uint32(got) != uint32(want) {
t.Errorf("memchr(%d, %d, %d) = %d, want %d",
ptr, 7, uint64(length), uint32(got), uint32(want))
}
}
}
func Test_strchr(t *testing.T) {
for length := range 64 {
for pos := range length + 2 {
for alignment := range 24 {
ptr := (page - 8) + alignment
want := 0
if pos < length {
want = ptr + pos
}
clear(memory[:2*page])
fill(memory[ptr:ptr+max(pos, length)], 5)
memory[ptr+pos] = 7
memory[ptr+pos+1] = 7
memory[ptr+length] = 0
got := call(strchr, uint64(ptr), 7)
if uint32(got) != uint32(want) {
t.Errorf("strchr(%d, %d) = %d, want %d",
ptr, 7, uint32(got), uint32(want))
}
}
}
clear(memory)
ptr := len(memory) - length
fill(memory[ptr:ptr+length], 5)
memory[len(memory)-1] = 7
want := len(memory) - 1
if length == 0 {
continue
}
got := call(strchr, uint64(ptr), 7)
if uint32(got) != uint32(want) {
t.Errorf("strchr(%d, %d) = %d, want %d",
ptr, 7, uint32(got), uint32(want))
}
}
}
func Test_strrchr(t *testing.T) {
for length := range 64 {
for pos := range length + 2 {
for alignment := range 24 {
ptr := (page - 8) + alignment
want := 0
if pos < length {
want = ptr + pos
} else if length > 0 {
want = ptr
}
clear(memory[:2*page])
fill(memory[ptr:ptr+max(pos, length)], 5)
memory[ptr] = 7
memory[ptr+pos] = 7
memory[ptr+length] = 0
got := call(strrchr, uint64(ptr), 7)
if uint32(got) != uint32(want) {
t.Errorf("strrchr(%d, %d) = %d, want %d",
ptr, 7, uint32(got), uint32(want))
}
}
}
ptr := len(memory) - length
want := len(memory) - 2
if length <= 1 {
continue
}
clear(memory)
fill(memory[ptr:ptr+length], 5)
memory[ptr] = 7
memory[len(memory)-2] = 7
memory[len(memory)-1] = 0
got := call(strrchr, uint64(ptr), 7)
if uint32(got) != uint32(want) {
t.Errorf("strrchr(%d, %d) = %d, want %d",
ptr, 7, uint32(got), uint32(want))
}
}
}
func Test_strspn(t *testing.T) {
for length := range 64 {
for pos := range length + 2 {
for alignment := range 24 {
ptr := (page - 8) + alignment
want := min(pos, length)
clear(memory[:2*page])
fill(memory[ptr:ptr+max(pos, length)], 5)
memory[ptr+pos] = 7
memory[ptr+length] = 0
memory[128] = 3
memory[129] = 5
got := call(strspn, uint64(ptr), 129)
if uint32(got) != uint32(want) {
t.Errorf("strspn(%d, %d) = %d, want %d",
ptr, 129, uint32(got), uint32(want))
}
got = call(strspn, uint64(ptr), 128)
if uint32(got) != uint32(want) {
t.Errorf("strspn(%d, %d) = %d, want %d",
ptr, 128, uint32(got), uint32(want))
}
}
}
ptr := len(memory) - length
want := length - 1
if length == 0 {
continue
}
clear(memory)
fill(memory[ptr:ptr+length], 5)
memory[len(memory)-1] = 7
memory[128] = 3
memory[129] = 5
got := call(strspn, uint64(ptr), 129)
if uint32(got) != uint32(want) {
t.Errorf("strspn(%d, %d) = %d, want %d",
ptr, 129, uint32(got), uint32(want))
}
got = call(strspn, uint64(ptr), 128)
if uint32(got) != uint32(want) {
t.Errorf("strspn(%d, %d) = %d, want %d",
ptr, 128, uint32(got), uint32(want))
}
}
}
func Test_strcspn(t *testing.T) {
for length := range 64 {
for pos := range length + 2 {
for alignment := range 24 {
ptr := (page - 8) + alignment
want := min(pos, length)
clear(memory[:2*page])
fill(memory[ptr:ptr+max(pos, length)], 5)
memory[ptr+pos] = 7
memory[ptr+length] = 0
memory[128] = 3
memory[129] = 7
got := call(strcspn, uint64(ptr), 129)
if uint32(got) != uint32(want) {
t.Errorf("strcspn(%d, %d) = %d, want %d",
ptr, 129, uint32(got), uint32(want))
}
got = call(strcspn, uint64(ptr), 128)
if uint32(got) != uint32(want) {
t.Errorf("strcspn(%d, %d) = %d, want %d",
ptr, 128, uint32(got), uint32(want))
}
}
}
ptr := len(memory) - length
want := length - 1
if length == 0 {
continue
}
clear(memory)
fill(memory[ptr:ptr+length], 5)
memory[len(memory)-1] = 7
memory[128] = 3
memory[129] = 7
got := call(strcspn, uint64(ptr), 129)
if uint32(got) != uint32(want) {
t.Errorf("strcspn(%d, %d) = %d, want %d",
ptr, 129, uint32(got), uint32(want))
}
got = call(strcspn, uint64(ptr), 128)
if uint32(got) != uint32(want) {
t.Errorf("strcspn(%d, %d) = %d, want %d",
ptr, 128, uint32(got), uint32(want))
}
}
}
func fill(s []byte, v byte) {
for i := range s {
s[i] = v
}
}
func sign(x int32) int {
switch {
case x > 0:
return +1
case x < 0:
return -1
default:
return 0
}
}
func term(s string) string {
if i := strings.IndexByte(s, 0); i >= 0 {
return s[:i]
}
return s
}

36
sqlite3/libc/math.h Normal file
View File

@@ -0,0 +1,36 @@
#ifndef _WASM_SIMD128_MATH_H
#define _WASM_SIMD128_MATH_H
#include <wasm_simd128.h>
#include_next <math.h> // the system math.h
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __wasm_relaxed_simd__
// This header assumes "relaxed fused multiply-add"
// is both faster and more precise.
#define FP_FAST_FMA 1
__attribute__((weak))
double fma(double x, double y, double z) {
// If we get a software implementation from the host,
// this is enough to short circuit it on the 2nd lane.
const v128_t wx = wasm_f64x2_replace_lane(b, 0, x);
const v128_t wy = wasm_f64x2_splat(y);
const v128_t wz = wasm_f64x2_splat(z);
const v128_t wr = wasm_f64x2_relaxed_madd(wx, wy, wz);
return wasm_f64x2_extract_lane(wr, 0);
}
#endif // __wasm_relaxed_simd__
#ifdef __cplusplus
} // extern "C"
#endif
#endif // _WASM_SIMD128_MATH_H

60
sqlite3/libc/stdlib.h Normal file
View File

@@ -0,0 +1,60 @@
#ifndef _WASM_SIMD128_STDLIB_H
#define _WASM_SIMD128_STDLIB_H
#include <stddef.h>
#include <stdint.h>
#include_next <stdlib.h> // the system stdlib.h
#ifdef __cplusplus
extern "C" {
#endif
// Shellsort with Gonnet & Baeza-Yates gap sequence.
// Simple, no recursion, doesn't use the C stack.
// Clang auto-vectorizes the inner loop.
__attribute__((weak))
void qsort(void *base, size_t nel, size_t width,
int (*comp)(const void *, const void *)) {
// If nel is zero, we're required to do nothing.
// If it's one, the array is already sorted.
size_t wnel = width * nel;
size_t gap = nel;
while (gap > 1) {
// Use 64-bit unsigned arithmetic to avoid intermediate overflow.
// Absent overflow, gap will be strictly less than its previous value.
// Once it is one or zero, set it to one: do a final pass, and stop.
gap = (5ull * gap - 1) / 11;
if (gap == 0) gap = 1;
// It'd be undefined behavior for wnel to overflow a size_t;
// or if width is zero: the base pointer would be invalid.
// Since gap is stricly less than nel, we can assume
// wgap is strictly less than wnel.
size_t wgap = width * gap;
__builtin_assume(wgap < wnel);
for (size_t i = wgap; i < wnel; i += width) {
// Even without overflow flags, the overflow builtin helps the compiler.
for (size_t j = i; !__builtin_sub_overflow(j, wgap, &j);) {
char *a = j + (char *)base;
char *b = a + wgap;
if (comp(a, b) <= 0) break;
// This well known loop is automatically vectorized.
size_t s = width;
do {
char tmp = *a;
*a++ = *b;
*b++ = tmp;
} while (--s);
}
}
}
}
#ifdef __cplusplus
} // extern "C"
#endif
#endif // _WASM_SIMD128_STDLIB_H

538
sqlite3/libc/string.h Normal file
View File

@@ -0,0 +1,538 @@
#ifndef _WASM_SIMD128_STRING_H
#define _WASM_SIMD128_STRING_H
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <wasm_simd128.h>
#include <__macro_PAGESIZE.h>
#include_next <string.h> // the system string.h
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __wasm_bulk_memory__
// 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))
void *memset(void *dest, int c, size_t n) {
return __builtin_memset(dest, c, n);
}
__attribute__((weak))
void *memcpy(void *__restrict dest, const void *__restrict src, size_t n) {
return __builtin_memcpy(dest, src, n);
}
__attribute__((weak))
void *memmove(void *dest, const void *src, size_t n) {
return __builtin_memmove(dest, src, n);
}
#endif // __wasm_bulk_memory__
#ifdef __wasm_simd128__
// SIMD implementations of string.h functions.
__attribute__((weak))
int memcmp(const void *v1, const void *v2, size_t n) {
// Baseline 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 *u1 - *u2;
u1++;
u2++;
}
return 0;
}
// memcmp is allowed to read up to n bytes from each object.
// 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;
while (n) {
const v128_t cmp = wasm_i8x16_eq(wasm_v128_load(w1), wasm_v128_load(w2));
// 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;
// This may help the compiler if the function is inlined.
__builtin_assume(*u1 - *u2 != 0);
return *u1 - *u2;
}
// 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;
}
__attribute__((weak))
void *memchr(const void *v, 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.
if (n-- == 0) {
return NULL;
}
// 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 don't fail.
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);
for (;;) {
const v128_t cmp = wasm_i8x16_eq(*w, wc);
// Bitmask is slow on AArch64, any_true is much faster.
if (wasm_v128_any_true(cmp)) {
// Clear the bits corresponding to alignment (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.
__builtin_assume(mask || align);
// If the mask is zero because of alignment,
// it's as if we didn't find anything.
if (mask) {
// We found 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;
}
}
// Decrement n; if it overflows we're done.
if (__builtin_sub_overflow(n, sizeof(v128_t) - align, &n)) {
return NULL;
}
align = 0;
w++;
}
}
__attribute__((weak))
void *memrchr(const void *v, 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);
for (; n >= sizeof(v128_t); n -= sizeof(v128_t)) {
const v128_t cmp = wasm_i8x16_eq(wasm_v128_load(--w), wc);
// Bitmask is slow on AArch64, any_true is much faster.
if (wasm_v128_any_true(cmp)) {
size_t clz = __builtin_clz(wasm_i8x16_bitmask(cmp)) - 15;
return (char *)(w + 1) - clz;
}
}
// Baseline algorithm.
const char *a = (char *)w;
while (n--) {
if (*(--a) == (char)c) return (char *)a;
}
return NULL;
}
__attribute__((weak))
size_t strlen(const char *s) {
// strlen must stop as soon as it finds the terminator.
// Aligning ensures loads beyond the terminator don't fail.
uintptr_t align = (uintptr_t)s % sizeof(v128_t);
const v128_t *w = (v128_t *)(s - align);
for (;;) {
// 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)
// 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.
__builtin_assume(mask || align);
if (mask) {
return (char *)w - s + __builtin_ctz(mask);
}
}
align = 0;
w++;
}
}
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 strings may still be equal,
// if the terminator is found before that difference.
break;
}
// 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++;
}
// Baseline algorithm.
const unsigned char *u1 = (unsigned char *)w1;
const unsigned char *u2 = (unsigned char *)w2;
for (;;) {
if (*u1 != *u2) return *u1 - *u2;
if (*u1 == 0) break;
u1++;
u2++;
}
return 0;
}
static int __strcmp_s(const char *s1, const char *s2) {
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;
}
__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 strings may still be equal,
// if the terminator is found before that difference.
break;
}
// 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++;
}
// Baseline 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 don't fail.
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);
for (;;) {
const v128_t cmp = wasm_i8x16_eq(*w, (v128_t){}) | wasm_i8x16_eq(*w, wc);
// Bitmask is slow on AArch64, any_true is much faster.
if (wasm_v128_any_true(cmp)) {
// Clear the bits corresponding to alignment (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.
__builtin_assume(mask || align);
if (mask) {
return (char *)w + __builtin_ctz(mask);
}
}
align = 0;
w++;
}
}
__attribute__((weak, always_inline))
char *strchrnul(const char *s, int c) {
// For finding the terminator, strlen is faster.
if (__builtin_constant_p(c) && (char)c == 0) {
return (char *)s + strlen(s);
}
return __strchrnul(s, c);
}
__attribute__((weak, always_inline))
char *strchr(const char *s, int c) {
// For finding the terminator, strlen is faster.
if (__builtin_constant_p(c) && (char)c == 0) {
return (char *)s + strlen(s);
}
char *r = __strchrnul(s, c);
return *r == (char)c ? r : NULL;
}
__attribute__((weak, always_inline))
char *strrchr(const char *s, int c) {
// For finding the terminator, strlen is faster.
if (__builtin_constant_p(c) && (char)c == 0) {
return (char *)s + strlen(s);
}
// This could also be implemented in a single pass using strchr,
// advancing to the next match until no more matches are found.
// That would be suboptimal with lots of consecutive matches.
return (char *)memrchr(s, c, strlen(s) + 1);
}
// http://0x80.pl/notesen/2018-10-18-simd-byte-lookup.html
#define _WASM_SIMD128_BITMAP256_T \
struct { \
uint8_t l __attribute__((__vector_size__(16), __aligned__(16))); \
uint8_t h __attribute__((__vector_size__(16), __aligned__(16))); \
}
#define _WASM_SIMD128_SETBIT(bitmap, i) \
({ \
uint8_t _c = (uint8_t)(i); \
uint8_t _hi_nibble = _c >> 4; \
uint8_t _lo_nibble = _c & 0xf; \
bitmap.l[_lo_nibble] |= 1 << (_hi_nibble - 0); \
bitmap.h[_lo_nibble] |= 1 << (_hi_nibble - 8); \
})
#define _WASM_SIMD128_CHKBIT(bitmap, i) \
({ \
uint8_t _c = (uint8_t)(i); \
uint8_t _hi_nibble = _c >> 4; \
uint8_t _lo_nibble = _c & 0xf; \
uint8_t _bitmask = 1 << (_hi_nibble & 0x7); \
uint8_t _bitset = (_hi_nibble < 8 ? bitmap.l : bitmap.h)[_lo_nibble]; \
_bitmask & _bitset; \
})
#define _WASM_SIMD128_CHKBITS(bitmap, v) \
({ \
v128_t _w = v; \
v128_t _hi_nibbles = wasm_u8x16_shr(_w, 4); \
v128_t _lo_nibbles = _w & 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_swizzle(_bitmask_lookup, _hi_nibbles); \
v128_t _bitsets = wasm_v128_bitselect( \
wasm_i8x16_swizzle(bitmap.l, _lo_nibbles), \
wasm_i8x16_swizzle(bitmap.h, _lo_nibbles), \
wasm_i8x16_lt(_hi_nibbles, wasm_u8x16_const_splat(8))); \
\
wasm_i8x16_eq(_bitsets & _bitmask, _bitmask); \
})
__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;
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);
// Bitmask is slow on AArch64, all_true is much faster.
if (!wasm_i8x16_all_true(cmp)) {
size_t ctz = __builtin_ctz(~wasm_i8x16_bitmask(cmp));
return (char *)w + ctz - s;
}
w++;
}
// Baseline algorithm.
for (s = (char *)w; *s == *c; s++);
return s - a;
}
_WASM_SIMD128_BITMAP256_T bitmap = {};
for (; *c; c++) {
_WASM_SIMD128_SETBIT(bitmap, *c);
// Terminator IS NOT on the bitmap.
}
for (; N >= sizeof(v128_t); N -= sizeof(v128_t)) {
const v128_t cmp = _WASM_SIMD128_CHKBITS(bitmap, wasm_v128_load(w));
// Bitmask is slow on AArch64, all_true is much faster.
if (!wasm_i8x16_all_true(cmp)) {
size_t ctz = __builtin_ctz(~wasm_i8x16_bitmask(cmp));
return (char *)w + ctz - s;
}
w++;
}
// Baseline algorithm.
for (s = (char *)w; _WASM_SIMD128_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;
_WASM_SIMD128_BITMAP256_T bitmap = {};
for (;;) {
_WASM_SIMD128_SETBIT(bitmap, *c);
// Terminator IS on the bitmap.
if (!*c++) break;
}
for (; N >= sizeof(v128_t); N -= sizeof(v128_t)) {
const v128_t cmp = _WASM_SIMD128_CHKBITS(bitmap, wasm_v128_load(w));
// Bitmask is slow on AArch64, any_true is much faster.
if (wasm_v128_any_true(cmp)) {
size_t ctz = __builtin_ctz(wasm_i8x16_bitmask(cmp));
return (char *)w + ctz - s;
}
w++;
}
// Baseline algorithm.
for (s = (char *)w; !_WASM_SIMD128_CHKBIT(bitmap, *s); s++);
return s - a;
}
#undef _WASM_SIMD128_SETBIT
#undef _WASM_SIMD128_CHKBIT
#undef _WASM_SIMD128_CHKBITS
#undef _WASM_SIMD128_BITMAP256_T
// Given the above SIMD implementations,
// these are best implemented as
// small wrappers over those functions.
// Simple wrappers already in musl:
// - mempcpy
// - strcat
// - strdup
// - strndup
// - strnlen
// - strpbrk
// - strsep
// - strtok
__attribute__((weak))
void *memccpy(void *__restrict dest, const void *__restrict src, int c, size_t n) {
void *memchr(const void *v, int c, size_t n);
const void *m = memchr(src, c, n);
if (m != NULL) {
n = (char *)m - (char *)src + 1;
m = (char *)dest + n;
}
memcpy(dest, src, n);
return (void *)m;
}
__attribute__((weak))
char *strncat(char *__restrict dest, const char *__restrict src, size_t n) {
size_t strnlen(const char *s, size_t n);
size_t dlen = strlen(dest);
size_t slen = strnlen(src, n);
memcpy(dest + dlen, src, slen);
dest[dlen + slen] = 0;
return dest;
}
static char *__stpcpy(char *__restrict dest, const char *__restrict src) {
size_t slen = strlen(src);
memcpy(dest, src, slen + 1);
return dest + slen;
}
static char *__stpncpy(char *__restrict dest, const char *__restrict src, size_t n) {
size_t strnlen(const char *s, size_t n);
size_t slen = strnlen(src, n);
memcpy(dest, src, slen);
memset(dest + slen, 0, n - slen);
return dest + slen;
}
__attribute__((weak, always_inline))
char *stpcpy(char *__restrict dest, const char *__restrict src) {
return __stpcpy(dest, src);
}
char *strcpy(char *__restrict dest, const char *__restrict src) {
__stpcpy(dest, src);
return dest;
}
__attribute__((weak, always_inline))
char *stpncpy(char *__restrict dest, const char *__restrict src, size_t n) {
return __stpncpy(dest, src, n);
}
__attribute__((weak, always_inline))
char *strncpy(char *__restrict dest, const char *__restrict src, size_t n) {
__stpncpy(dest, src, n);
return dest;
}
#endif // __wasm_simd128__
#ifdef __cplusplus
} // extern "C"
#endif
#endif // _WASM_SIMD128_STRING_H

57
sqlite3/libc/strings.h Normal file
View File

@@ -0,0 +1,57 @@
#ifndef _WASM_SIMD128_STRINGS_H
#define _WASM_SIMD128_STRINGS_H
#include <stddef.h>
#include <wasm_simd128.h>
#include_next <strings.h> // the system strings.h
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __wasm_simd128__
__attribute__((weak))
int bcmp(const void *v1, const void *v2, size_t n) {
// bcmp is the same as memcmp but only compares for equality.
// Baseline 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 // __wasm_simd128__
#ifdef __cplusplus
} // extern "C"
#endif
#endif // _WASM_SIMD128_STRINGS_H

View File

@@ -10,12 +10,11 @@
#include "ext/spellfix.c"
#include "ext/uint.c"
// Bindings
#include "bind.c"
#include "column.c"
#include "func.c"
#include "hooks.c"
#include "pointer.c"
#include "result.c"
#include "stmt.c"
#include "text.c"
#include "time.c"
#include "vfs.c"
#include "vtab.c"

View File

@@ -1,14 +0,0 @@
#include <math.h>
#include <stdlib.h>
#include "sqlite3.h"
void sqlite3_result_text_go(sqlite3_context *ctx, const char *zData,
sqlite3_uint64 nData) {
sqlite3_result_text64(ctx, zData, nData, &sqlite3_free, SQLITE_UTF8);
}
void sqlite3_result_blob_go(sqlite3_context *ctx, const void *zData,
sqlite3_uint64 nData) {
sqlite3_result_blob64(ctx, zData, nData, &sqlite3_free);
}

View File

@@ -4,26 +4,24 @@
#define SQLITE_THREADSAFE 0
#define SQLITE_DEFAULT_WAL_SYNCHRONOUS 1
#define SQLITE_LIKE_DOESNT_MATCH_BLOBS
#define SQLITE_MAX_EXPR_DEPTH 0
#define SQLITE_STRICT_SUBTYPE 1
#define SQLITE_USE_ALLOCA
#define SQLITE_OMIT_DEPRECATED
#define SQLITE_OMIT_SHARED_CACHE
#define SQLITE_OMIT_AUTOINIT
// We need these:
// #define SQLITE_DEFAULT_MEMSTATUS 0
// #define SQLITE_MAX_EXPR_DEPTH 0
// #define SQLITE_USE_ALLOCA
// #define SQLITE_OMIT_DECLTYPE
// #define SQLITE_OMIT_PROGRESS_CALLBACK
// TODO add this:
// #define SQLITE_ENABLE_API_ARMOR
// Other Options
#define SQLITE_ALLOW_URI_AUTHORITY
#define SQLITE_TRUSTED_SCHEMA 0
#define SQLITE_DEFAULT_FOREIGN_KEYS 1
#define SQLITE_ENABLE_API_ARMOR
#define SQLITE_ENABLE_ATOMIC_WRITE
#define SQLITE_ENABLE_BATCH_ATOMIC_WRITE
#define SQLITE_ENABLE_COLUMN_METADATA

View File

@@ -2,6 +2,11 @@
#include "sqlite3.h"
int sqlite3_exec_go(sqlite3_stmt *stmt) {
while (sqlite3_step(stmt) == SQLITE_ROW);
return sqlite3_reset(stmt);
}
union sqlite3_data {
sqlite3_int64 i;
double d;
@@ -16,7 +21,7 @@ int sqlite3_columns_go(sqlite3_stmt *stmt, int nCol, char *aType,
if (nCol != sqlite3_column_count(stmt)) {
return SQLITE_MISUSE;
}
int rc = SQLITE_OK;
bool check = false;
for (int i = 0; i < nCol; ++i) {
const void *ptr = NULL;
switch (aType[i] = sqlite3_column_type(stmt, i)) {
@@ -36,16 +41,14 @@ int sqlite3_columns_go(sqlite3_stmt *stmt, int nCol, char *aType,
ptr = sqlite3_column_blob(stmt, i);
break;
}
if (ptr == NULL && rc == SQLITE_OK) {
rc = sqlite3_errcode(sqlite3_db_handle(stmt));
if (rc == SQLITE_ROW || rc == SQLITE_DONE) {
rc = SQLITE_OK;
}
}
aData[i].ptr = ptr;
aData[i].len = sqlite3_column_bytes(stmt, i);
if (ptr == NULL) check = true;
}
return rc;
if (check && SQLITE_NOMEM == sqlite3_errcode(sqlite3_db_handle(stmt))) {
return SQLITE_NOMEM;
}
return SQLITE_OK;
}
static_assert(offsetof(union sqlite3_data, i) == 0, "Unexpected offset");

View File

@@ -10,4 +10,14 @@ int sqlite3_bind_text_go(sqlite3_stmt *stmt, int i, const char *zData,
int sqlite3_bind_blob_go(sqlite3_stmt *stmt, int i, const char *zData,
sqlite3_uint64 nData) {
return sqlite3_bind_blob64(stmt, i, zData, nData, &sqlite3_free);
}
void sqlite3_result_text_go(sqlite3_context *ctx, const char *zData,
sqlite3_uint64 nData) {
sqlite3_result_text64(ctx, zData, nData, &sqlite3_free, SQLITE_UTF8);
}
void sqlite3_result_blob_go(sqlite3_context *ctx, const void *zData,
sqlite3_uint64 nData) {
sqlite3_result_blob64(ctx, zData, nData, &sqlite3_free);
}

39
sqlite3/tools.sh Executable file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail
cd -P -- "$(dirname -- "$0")"
ROOT=../
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
WASI_SDK="x86_64-windows"
BINARYEN="x86_64-windows"
elif [[ "$OSTYPE" == "linux"* ]]; then
if [[ "$(uname -m)" == "x86_64" ]]; then
WASI_SDK="x86_64-linux"
BINARYEN="x86_64-linux"
else
WASI_SDK="arm64-linux"
BINARYEN="aarch64-linux"
fi
elif [[ "$OSTYPE" == "darwin"* ]]; then
if [[ "$(uname -m)" == "x86_64" ]]; then
WASI_SDK="x86_64-macos"
BINARYEN="x86_64-macos"
else
WASI_SDK="arm64-macos"
BINARYEN="arm64-macos"
fi
fi
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-$WASI_SDK.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-$BINARYEN.tar.gz"
# Download tools
mkdir -p "$ROOT/tools"
[ -d "$ROOT/tools/wasi-sdk" ] || curl -#L "$WASI_SDK" | tar xzC "$ROOT/tools" &
[ -d "$ROOT/tools/binaryen" ] || curl -#L "$BINARYEN" | tar xzC "$ROOT/tools" &
wait
[ -d "$ROOT/tools/wasi-sdk" ] || mv "$ROOT/tools/wasi-sdk"* "$ROOT/tools/wasi-sdk"
[ -d "$ROOT/tools/binaryen" ] || mv "$ROOT/tools/binaryen"* "$ROOT/tools/binaryen"

View File

@@ -137,9 +137,10 @@ sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName) {
// Create a new C wrapper.
sqlite3_vfs *head = go_vfs_list;
go_vfs_list = malloc(sizeof(sqlite3_vfs) + strlen(zVfsName) + 1);
size_t vfsNameLen = strlen(zVfsName);
go_vfs_list = malloc(sizeof(sqlite3_vfs) + vfsNameLen + 1);
char *name = (char *)(go_vfs_list + 1);
strcpy(name, zVfsName);
memcpy(name, zVfsName, vfsNameLen + 1);
*go_vfs_list = (sqlite3_vfs){
.iVersion = 2,
.szOsFile = sizeof(struct go_file),

13
stmt.go
View File

@@ -110,10 +110,7 @@ func (s *Stmt) Step() bool {
s.err = INTERRUPT
return false
}
return s.step()
}
func (s *Stmt) step() bool {
rc := res_t(s.c.call("sqlite3_step", stk_t(s.handle)))
switch rc {
case _ROW:
@@ -141,10 +138,9 @@ func (s *Stmt) Exec() error {
if s.c.interrupt.Err() != nil {
return INTERRUPT
}
// TODO: implement this in C.
for s.step() {
}
return s.Reset()
rc := res_t(s.c.call("sqlite3_exec_go", stk_t(s.handle)))
s.err = nil
return s.c.error(rc)
}
// Status monitors the performance characteristics of prepared statements.
@@ -649,6 +645,7 @@ func (s *Stmt) ColumnValue(col int) Value {
// [FLOAT] as float64, [NULL] as nil,
// [TEXT] as string, and [BLOB] as []byte.
func (s *Stmt) Columns(dest ...any) error {
defer s.c.arena.mark()()
types, ptr, err := s.columns(int64(len(dest)))
if err != nil {
return err
@@ -701,6 +698,7 @@ func (s *Stmt) Columns(dest ...any) error {
// Any []byte are owned by SQLite and may be invalidated by
// subsequent calls to [Stmt] methods.
func (s *Stmt) ColumnsRaw(dest ...any) error {
defer s.c.arena.mark()()
types, ptr, err := s.columns(int64(len(dest)))
if err != nil {
return err
@@ -739,7 +737,6 @@ func (s *Stmt) ColumnsRaw(dest ...any) error {
}
func (s *Stmt) columns(count int64) ([]byte, ptr_t, error) {
defer s.c.arena.mark()()
typePtr := s.c.arena.new(count)
dataPtr := s.c.arena.new(count * 8)

View File

@@ -35,7 +35,7 @@ func TestMain(m *testing.M) {
log.Printf("%v (%d): %s", code, code, msg)
}
})
m.Run()
os.Exit(m.Run())
}
func Test_parallel(t *testing.T) {

View File

@@ -287,8 +287,9 @@ func TestConn_Transaction_busy(t *testing.T) {
go cancel()
_, err = db2.BeginExclusive()
if !errors.Is(err, sqlite3.BUSY) && !errors.Is(err, sqlite3.INTERRUPT) {
t.Errorf("got %v, want sqlite3.BUSY or sqlite3.INTERRUPT", err)
var terr interface{ Temporary() bool }
if !errors.As(err, &terr) || !terr.Temporary() {
t.Errorf("got %v, want temporary", err)
}
err = nil

View File

@@ -10,7 +10,9 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
trap 'rm -f sql3parse_table.tmp' EXIT
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -Oz \
-Wall -Wextra -o sql3parse_table.wasm main.c \
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
-o sql3parse_table.wasm main.c \
-I"$ROOT/sqlite3/libc" -I"$ROOT/sqlite3" \
-mexec-model=reactor \
-msimd128 -mmutable-globals -mmultivalue \
-mbulk-memory -mreference-types \
@@ -22,7 +24,7 @@ trap 'rm -f sql3parse_table.tmp' EXIT
"$BINARYEN/wasm-ctor-eval" -c _initialize sql3parse_table.wasm -o sql3parse_table.tmp
"$BINARYEN/wasm-opt" --strip --strip-debug --strip-producers -c -Oz \
sql3parse_table.tmp -o sql3parse_table.wasm \
sql3parse_table.tmp -o sql3parse_table.wasm --low-memory-unused \
--enable-simd --enable-mutable-globals --enable-multivalue \
--enable-bulk-memory --enable-reference-types \
--enable-nontrapping-float-to-int --enable-sign-ext

View File

@@ -9,7 +9,7 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
-o mptest.wasm main.c \
-I"$ROOT/sqlite3" \
-I"$ROOT/sqlite3/libc" -I"$ROOT/sqlite3" \
-msimd128 -mmutable-globals -mmultivalue \
-mbulk-memory -mreference-types \
-mnontrapping-fptoint -msign-ext \
@@ -26,7 +26,7 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
$(awk '{print "-Wl,--export="$0}' exports.txt)
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
mptest.wasm -o mptest.tmp \
mptest.wasm -o mptest.tmp --low-memory-unused \
--enable-simd --enable-mutable-globals --enable-multivalue \
--enable-bulk-memory --enable-reference-types \
--enable-nontrapping-float-to-int --enable-sign-ext

Binary file not shown.

View File

@@ -9,7 +9,7 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -g0 -O2 \
-o speedtest1.wasm main.c \
-I"$ROOT/sqlite3" \
-I"$ROOT/sqlite3/libc" -I"$ROOT/sqlite3" \
-msimd128 -mmutable-globals -mmultivalue \
-mbulk-memory -mreference-types \
-mnontrapping-fptoint -msign-ext \
@@ -21,7 +21,7 @@ WASI_SDK="$ROOT/tools/wasi-sdk/bin"
$(awk '{print "-Wl,--export="$0}' exports.txt)
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
speedtest1.wasm -o speedtest1.tmp \
speedtest1.wasm -o speedtest1.tmp --low-memory-unused \
--enable-simd --enable-mutable-globals --enable-multivalue \
--enable-bulk-memory --enable-reference-types \
--enable-nontrapping-float-to-int --enable-sign-ext