Skip to content

Commit 81bdfd0

Browse files
committed
gosqlite: replace uintptr handles with Go pointers
Removed the uintptr indirection for sqlite3_stmt, replacing the uintptr with a plain Go pointer. Since 34bef2d was authored the toolchain has changed such that the indirection is no longer necessary to avoid allocations. Restoring regular pointers will enable regular tooling to function again and simplifies the code. ``` goos: darwin goarch: arm64 pkg: github.com/tailscale/sqlite cpu: Apple M4 Max │ /tmp/uintptr-bench.txt │ /tmp/no-cstmt-bench.txt │ │ sec/op │ sec/op vs base │ WALHookAndExec-16 1.362µ ± 1% 1.345µ ± 1% -1.25% (p=0.000 n=10) Persist-16 1.186µ ± 3% 1.145µ ± 1% -3.46% (p=0.000 n=10) QueryRows100MixedTypes-16 20.05µ ± 1% 20.60µ ± 0% +2.73% (p=0.000 n=10) EmptyExec-16 219.8n ± 0% 222.8n ± 1% +1.39% (p=0.000 n=10) BeginTxNoop-16 5.226µ ± 0% 5.289µ ± 1% +1.21% (p=0.000 n=10) geomean 2.061µ 2.063µ +0.10% │ /tmp/uintptr-bench.txt │ /tmp/no-cstmt-bench.txt │ │ B/op │ B/op vs base │ WALHookAndExec-16 32.00 ± 0% 32.00 ± 0% ~ (p=1.000 n=10) ¹ Persist-16 504.0 ± 0% 504.0 ± 0% ~ (p=1.000 n=10) ¹ QueryRows100MixedTypes-16 2.766Ki ± 0% 2.766Ki ± 0% ~ (p=1.000 n=10) ¹ EmptyExec-16 32.00 ± 0% 32.00 ± 0% ~ (p=1.000 n=10) ¹ BeginTxNoop-16 640.0 ± 0% 640.0 ± 0% ~ (p=1.000 n=10) ¹ geomean 247.9 247.9 +0.00% ¹ all samples are equal │ /tmp/uintptr-bench.txt │ /tmp/no-cstmt-bench.txt │ │ allocs/op │ allocs/op vs base │ WALHookAndExec-16 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ Persist-16 11.00 ± 0% 11.00 ± 0% ~ (p=1.000 n=10) ¹ QueryRows100MixedTypes-16 107.0 ± 0% 107.0 ± 0% ~ (p=1.000 n=10) ¹ EmptyExec-16 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ BeginTxNoop-16 13.00 ± 0% 13.00 ± 0% ~ (p=1.000 n=10) ¹ geomean 6.870 6.870 +0.00% ¹ all samples are equal ``` Updates tailscale/corp#9919
1 parent 3ded194 commit 81bdfd0

File tree

2 files changed

+43
-122
lines changed

2 files changed

+43
-122
lines changed

cgosqlite/cgosqlite.go

Lines changed: 37 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ import (
5757
"github.com/tailscale/sqlite/sqliteh"
5858
)
5959

60+
var emptyStrPtr = (*C.char)(unsafe.Pointer(unsafe.StringData("")))
61+
6062
func init() {
6163
C.sqlite3_initialize()
6264
}
@@ -68,31 +70,10 @@ type DB struct {
6870
declTypes map[string]string
6971
}
7072

71-
// cStmt is a wrapper around an sqlite3 *sqlite3_stmt. Except rather than
72-
// storing it as a pointer, it's stored as uintptr to avoid allocations due to
73-
// poor interactions between cgo's pointer checker and Go's escape analysis.
74-
//
75-
// The ptr method returns the value as a pointer, for call sites that haven't
76-
// yet been optimized or don't need the optimization. This lets us migrate
77-
// incrementally.
78-
//
79-
// See http://go/corp/9919.
80-
type cStmt struct {
81-
v C.handle_sqlite3_stmt
82-
}
83-
84-
// cStmtFromPtr returns a cStmt from a C pointer.
85-
func cStmtFromPtr(p *C.sqlite3_stmt) cStmt {
86-
return cStmt{v: C.handle_sqlite3_stmt(uintptr(unsafe.Pointer(p)))}
87-
}
88-
89-
func (h cStmt) int() C.handle_sqlite3_stmt { return h.v }
90-
func (h cStmt) ptr() *C.sqlite3_stmt { return (*C.sqlite3_stmt)(unsafe.Pointer(uintptr(h.v))) }
91-
9273
// Stmt implements sqliteh.Stmt.
9374
type Stmt struct {
9475
db *DB
95-
stmt cStmt
76+
stmt *C.sqlite3_stmt
9677
start C.struct_timespec
9778

9879
// used as scratch space when calling into cgo
@@ -202,7 +183,7 @@ func (db *DB) Prepare(query string, prepFlags sqliteh.PrepareFlags) (stmt sqlite
202183
return nil, "", err
203184
}
204185
remainingQuery = query[len(query)-int(C.strlen(csqlTail)):]
205-
return &Stmt{db: db, stmt: cStmtFromPtr(cstmt)}, remainingQuery, nil
186+
return &Stmt{db: db, stmt: cstmt}, remainingQuery, nil
206187
}
207188

208189
func (db *DB) DisableFunction(name string, numArgs int) error {
@@ -212,45 +193,45 @@ func (db *DB) DisableFunction(name string, numArgs int) error {
212193
}
213194

214195
func (stmt *Stmt) DBHandle() sqliteh.DB {
215-
cdb := C.sqlite3_db_handle(stmt.stmt.ptr())
196+
cdb := C.sqlite3_db_handle(stmt.stmt)
216197
if cdb != nil {
217198
return &DB{db: cdb}
218199
}
219200
return nil
220201
}
221202

222203
func (stmt *Stmt) SQL() string {
223-
return C.GoString(C.sqlite3_sql(stmt.stmt.ptr()))
204+
return C.GoString(C.sqlite3_sql(stmt.stmt))
224205
}
225206

226207
func (stmt *Stmt) ExpandedSQL() string {
227208
// sqlite3_expanded_sql returns a string obtained by sqlite3_malloc, which
228209
// must be freed after use.
229-
cstr := C.sqlite3_expanded_sql(stmt.stmt.ptr())
210+
cstr := C.sqlite3_expanded_sql(stmt.stmt)
230211
defer C.sqlite3_free(unsafe.Pointer(cstr))
231212
return C.GoString(cstr)
232213
}
233214

234215
func (stmt *Stmt) Reset() error {
235-
return errCode(C.sqlite3_reset(stmt.stmt.ptr()))
216+
return errCode(C.sqlite3_reset(stmt.stmt))
236217
}
237218

238219
func (stmt *Stmt) Finalize() error {
239-
return errCode(C.sqlite3_finalize(stmt.stmt.ptr()))
220+
return errCode(C.sqlite3_finalize(stmt.stmt))
240221
}
241222

242223
func (stmt *Stmt) ClearBindings() error {
243-
return errCode(C.sqlite3_clear_bindings(stmt.stmt.ptr()))
224+
return errCode(C.sqlite3_clear_bindings(stmt.stmt))
244225
}
245226

246227
func (stmt *Stmt) ResetAndClear() (time.Duration, error) {
247228
if stmt.start != (C.struct_timespec{}) {
248229
stmt.duration = 0
249-
err := errCode(C.reset_and_clear(stmt.stmt.int(), &stmt.start, &stmt.duration))
230+
err := errCode(C.reset_and_clear(stmt.stmt, &stmt.start, &stmt.duration))
250231
return time.Duration(stmt.duration), err
251232
}
252-
if sp := stmt.stmt.int(); sp != 0 {
253-
return 0, errCode(C.reset_and_clear(stmt.stmt.int(), nil, nil))
233+
if stmt.stmt != nil {
234+
return 0, errCode(C.reset_and_clear(stmt.stmt, nil, nil))
254235
}
255236
// The statement was never initialized. This can happen if, for example, the
256237
// parser found only comments (so the statement was not empty, but did not
@@ -263,19 +244,19 @@ func (stmt *Stmt) StartTimer() {
263244
}
264245

265246
func (stmt *Stmt) ColumnDatabaseName(col int) string {
266-
return C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_database_name(stmt.stmt.ptr(), C.int(col)))))
247+
return C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_database_name(stmt.stmt, C.int(col)))))
267248
}
268249

269250
func (stmt *Stmt) ColumnTableName(col int) string {
270-
return C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_table_name(stmt.stmt.ptr(), C.int(col)))))
251+
return C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_table_name(stmt.stmt, C.int(col)))))
271252
}
272253

273254
func (stmt *Stmt) Step(colType []sqliteh.ColumnType) (row bool, err error) {
274255
var ptr *C.char
275256
if len(colType) > 0 {
276257
ptr = (*C.char)(unsafe.Pointer(&colType[0]))
277258
}
278-
res := C.ts_sqlite3_step(stmt.stmt.int(), ptr, C.int(len(colType)))
259+
res := C.ts_sqlite3_step(stmt.stmt, ptr, C.int(len(colType)))
279260
switch res {
280261
case C.SQLITE_ROW:
281262
return true, nil
@@ -288,7 +269,7 @@ func (stmt *Stmt) Step(colType []sqliteh.ColumnType) (row bool, err error) {
288269

289270
func (stmt *Stmt) StepResult() (row bool, lastInsertRowID, changes int64, d time.Duration, err error) {
290271
stmt.rowid, stmt.changes, stmt.duration = 0, 0, 0
291-
res := C.step_result(stmt.stmt.int(), &stmt.rowid, &stmt.changes, &stmt.duration)
272+
res := C.step_result(stmt.stmt, &stmt.rowid, &stmt.changes, &stmt.duration)
292273
lastInsertRowID = int64(stmt.rowid)
293274
changes = int64(stmt.changes)
294275
d = time.Duration(stmt.duration)
@@ -304,51 +285,51 @@ func (stmt *Stmt) StepResult() (row bool, lastInsertRowID, changes int64, d time
304285
}
305286

306287
func (stmt *Stmt) BindDouble(col int, val float64) error {
307-
return errCode(C.ts_sqlite3_bind_double(stmt.stmt.int(), C.int(col), C.double(val)))
288+
return errCode(C.sqlite3_bind_double(stmt.stmt, C.int(col), C.double(val)))
308289
}
309290

310291
func (stmt *Stmt) BindInt64(col int, val int64) error {
311-
return errCode(C.ts_sqlite3_bind_int64(stmt.stmt.int(), C.int(col), C.sqlite3_int64(val)))
292+
return errCode(C.sqlite3_bind_int64(stmt.stmt, C.int(col), C.sqlite3_int64(val)))
312293
}
313294

314295
func (stmt *Stmt) BindNull(col int) error {
315-
return errCode(C.ts_sqlite3_bind_null(stmt.stmt.int(), C.int(col)))
296+
return errCode(C.sqlite3_bind_null(stmt.stmt, C.int(col)))
316297
}
317298

318299
func (stmt *Stmt) BindText64(col int, val string) error {
319300
if len(val) == 0 {
320-
return errCode(C.bind_text64_empty(stmt.stmt.int(), C.int(col)))
301+
return errCode(C.sqlite3_bind_text64(stmt.stmt, C.int(col), emptyStrPtr, 0, C.SQLITE_STATIC, C.SQLITE_UTF8))
321302
}
322303
v := C.CString(val) // freed by sqlite
323-
return errCode(C.bind_text64(stmt.stmt.int(), C.int(col), v, C.sqlite3_uint64(len(val))))
304+
return errCode(C.sqlite3_bind_text64(stmt.stmt, C.int(col), v, C.sqlite3_uint64(len(val)), (*[0]byte)(C.free), C.SQLITE_UTF8))
324305
}
325306

326307
func (stmt *Stmt) BindZeroBlob64(col int, n uint64) error {
327-
return errCode(C.sqlite3_bind_zeroblob64(stmt.stmt.ptr(), C.int(col), C.sqlite3_uint64(n)))
308+
return errCode(C.sqlite3_bind_zeroblob64(stmt.stmt, C.int(col), C.sqlite3_uint64(n)))
328309
}
329310

330311
func (stmt *Stmt) BindBlob64(col int, val []byte) error {
331312
var str *C.char
332313
if len(val) > 0 {
333314
str = (*C.char)(unsafe.Pointer(&val[0]))
334315
}
335-
return errCode(C.bind_blob64(stmt.stmt.int(), C.int(col), str, C.sqlite3_uint64(len(val))))
316+
return errCode(C.sqlite3_bind_blob64(stmt.stmt, C.int(col), unsafe.Pointer(str), C.sqlite3_uint64(len(val)), C.SQLITE_TRANSIENT))
336317
}
337318

338319
func (stmt *Stmt) BindParameterCount() int {
339-
return int(C.sqlite3_bind_parameter_count(stmt.stmt.ptr()))
320+
return int(C.sqlite3_bind_parameter_count(stmt.stmt))
340321
}
341322

342323
func (stmt *Stmt) BindParameterName(col int) string {
343-
cstr := C.sqlite3_bind_parameter_name(stmt.stmt.ptr(), C.int(col))
324+
cstr := C.sqlite3_bind_parameter_name(stmt.stmt, C.int(col))
344325
if cstr == nil {
345326
return ""
346327
}
347328
return C.GoString(cstr)
348329
}
349330

350331
func (stmt *Stmt) BindParameterIndex(name string) int {
351-
return int(C.bind_parameter_index(stmt.stmt.int(), name))
332+
return int(C.bind_parameter_index(stmt.stmt, name))
352333
}
353334

354335
func (stmt *Stmt) BindParameterIndexSearch(name string) int {
@@ -363,45 +344,45 @@ func (stmt *Stmt) BindParameterIndexSearch(name string) int {
363344
}
364345

365346
func (stmt *Stmt) ColumnCount() int {
366-
return int(C.sqlite3_column_count(stmt.stmt.ptr()))
347+
return int(C.sqlite3_column_count(stmt.stmt))
367348
}
368349

369350
func (stmt *Stmt) ColumnName(col int) string {
370-
return C.GoString(C.sqlite3_column_name(stmt.stmt.ptr(), C.int(col)))
351+
return C.GoString(C.sqlite3_column_name(stmt.stmt, C.int(col)))
371352
}
372353

373354
func (stmt *Stmt) ColumnText(col int) string {
374-
str := (*C.char)(unsafe.Pointer(C.ts_sqlite3_column_text(stmt.stmt.int(), C.int(col))))
375-
n := C.ts_sqlite3_column_bytes(stmt.stmt.int(), C.int(col))
355+
str := (*C.char)(unsafe.Pointer(C.sqlite3_column_text(stmt.stmt, C.int(col))))
356+
n := C.sqlite3_column_bytes(stmt.stmt, C.int(col))
376357
if str == nil || n == 0 {
377358
return ""
378359
}
379360
return C.GoStringN(str, n)
380361
}
381362

382363
func (stmt *Stmt) ColumnBlob(col int) []byte {
383-
res := C.ts_sqlite3_column_blob(stmt.stmt.int(), C.int(col))
364+
res := C.sqlite3_column_blob(stmt.stmt, C.int(col))
384365
if res == nil {
385366
return nil
386367
}
387-
n := int(C.ts_sqlite3_column_bytes(stmt.stmt.int(), C.int(col)))
368+
n := int(C.sqlite3_column_bytes(stmt.stmt, C.int(col)))
388369
return unsafe.Slice((*byte)(unsafe.Pointer(res)), n)
389370
}
390371

391372
func (stmt *Stmt) ColumnDouble(col int) float64 {
392-
return float64(C.ts_sqlite3_column_double(stmt.stmt.int(), C.int(col)))
373+
return float64(C.sqlite3_column_double(stmt.stmt, C.int(col)))
393374
}
394375

395376
func (stmt *Stmt) ColumnInt64(col int) int64 {
396-
return int64(C.ts_sqlite3_column_int64(stmt.stmt.int(), C.int(col)))
377+
return int64(C.sqlite3_column_int64(stmt.stmt, C.int(col)))
397378
}
398379

399380
func (stmt *Stmt) ColumnType(col int) sqliteh.ColumnType {
400-
return sqliteh.ColumnType(C.ts_sqlite3_column_type(stmt.stmt.int(), C.int(col)))
381+
return sqliteh.ColumnType(C.sqlite3_column_type(stmt.stmt, C.int(col)))
401382
}
402383

403384
func (stmt *Stmt) ColumnDeclType(col int) string {
404-
cstr := C.sqlite3_column_decltype(stmt.stmt.ptr(), C.int(col))
385+
cstr := C.sqlite3_column_decltype(stmt.stmt, C.int(col))
405386
if cstr == nil {
406387
return ""
407388
}
@@ -418,8 +399,6 @@ func (stmt *Stmt) ColumnDeclType(col int) string {
418399
return res
419400
}
420401

421-
var emptyCStr = C.CString("")
422-
423402
func errCode(code C.int) error { return sqliteh.CodeAsError(sqliteh.Code(code)) }
424403

425404
// internCache contains interned strings.

cgosqlite/cgosqlite.h

Lines changed: 6 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,10 @@
55
#include <string.h>
66
#include <time.h>
77

8-
// uintptr versions of sqlite3 pointer types, to avoid allocations
9-
// in cgo code. (go/corp/9919)
10-
typedef uintptr_t handle_sqlite3_stmt; // a *sqlite3_stmt
11-
typedef uintptr_t handle_sqlite3; // a *sqlite3 (DB conn)
12-
13-
// Helper methods to deal with int <-> pointer pain.
14-
15-
static int bind_text64(handle_sqlite3_stmt stmt, int col, const char* str, sqlite3_uint64 len) {
16-
return sqlite3_bind_text64((sqlite3_stmt*)(stmt), col, str, len, free, SQLITE_UTF8);
17-
}
18-
19-
static int bind_text64_empty(handle_sqlite3_stmt stmt, int col) {
20-
return sqlite3_bind_text64((sqlite3_stmt*)(stmt), col, "", 0, SQLITE_STATIC, SQLITE_UTF8);
21-
}
22-
23-
static int bind_blob64(handle_sqlite3_stmt stmt, int col, char* str, sqlite3_uint64 n) {
24-
return sqlite3_bind_blob64((sqlite3_stmt*)(stmt), col, str, n, SQLITE_TRANSIENT);
25-
}
26-
27-
static int ts_sqlite3_bind_double(handle_sqlite3_stmt stmt, int col, double v) {
28-
return sqlite3_bind_double((sqlite3_stmt*)(stmt), col, v);
29-
}
30-
31-
static int ts_sqlite3_bind_int64(handle_sqlite3_stmt stmt, int col, sqlite3_int64 v) {
32-
return sqlite3_bind_int64((sqlite3_stmt*)(stmt), col, v);
33-
}
34-
35-
static int ts_sqlite3_bind_null(handle_sqlite3_stmt stmt, int col) {
36-
return sqlite3_bind_null((sqlite3_stmt*)(stmt), col);
37-
}
38-
398
// We only need the Go string's memory for the duration of the call,
409
// and the GC pins it for us if we pass the gostring_t to C, so we
4110
// do the conversion here instead of with C.CString.
42-
static int bind_parameter_index(handle_sqlite3_stmt stmt, _GoString_ s) {
11+
static int bind_parameter_index(sqlite3_stmt* stmt, _GoString_ s) {
4312
size_t n = _GoStringLen(s);
4413
const char *p = (const char *)_GoStringPtr(s);
4514

@@ -49,7 +18,7 @@ static int bind_parameter_index(handle_sqlite3_stmt stmt, _GoString_ s) {
4918
return 0;
5019
}
5120
memmove(zName, p, n);
52-
return sqlite3_bind_parameter_index((sqlite3_stmt*)(stmt), zName);
21+
return sqlite3_bind_parameter_index(stmt, zName);
5322
}
5423

5524
static void monotonic_clock_gettime(struct timespec* t) {
@@ -65,8 +34,7 @@ static int64_t ns_since(const struct timespec t1)
6534
}
6635

6736
// step_result combines several cgo calls to save overhead.
68-
static int step_result(handle_sqlite3_stmt stmth, sqlite3_int64* rowid, sqlite3_int64* changes, int64_t* duration_ns) {
69-
sqlite3_stmt* stmt = (sqlite3_stmt*)(stmth);
37+
static int step_result(sqlite3_stmt* stmt, sqlite3_int64* rowid, sqlite3_int64* changes, int64_t* duration_ns) {
7038
struct timespec t1;
7139
if (duration_ns) {
7240
monotonic_clock_gettime(&t1);
@@ -84,8 +52,7 @@ static int step_result(handle_sqlite3_stmt stmth, sqlite3_int64* rowid, sqlite3_
8452
}
8553

8654
// reset_and_clear combines two cgo calls to save overhead.
87-
static int reset_and_clear(handle_sqlite3_stmt stmth, struct timespec* start, int64_t* duration_ns) {
88-
sqlite3_stmt* stmt = (sqlite3_stmt*)(stmth);
55+
static int reset_and_clear(sqlite3_stmt* stmt, struct timespec* start, int64_t* duration_ns) {
8956
int ret = sqlite3_reset(stmt);
9057
int ret2 = sqlite3_clear_bindings(stmt);
9158
if (duration_ns) {
@@ -111,8 +78,7 @@ static void ts_sqlite3_wal_hook_go(sqlite3* db) {
11178
sqlite3_wal_hook(db, wal_callback_into_go, 0);
11279
}
11380

114-
static int ts_sqlite3_step(handle_sqlite3_stmt stmth, char* outType , int outTypeLen) {
115-
sqlite3_stmt* stmt = (sqlite3_stmt*)(stmth);
81+
static int ts_sqlite3_step(sqlite3_stmt* stmt, char* outType , int outTypeLen) {
11682
int res = sqlite3_step(stmt);
11783
if (res == SQLITE_ROW && outTypeLen > 0) {
11884
int cols = sqlite3_column_count(stmt);
@@ -123,30 +89,6 @@ static int ts_sqlite3_step(handle_sqlite3_stmt stmth, char* outType , int outTyp
12389
return res;
12490
}
12591

126-
static const unsigned char *ts_sqlite3_column_text(handle_sqlite3_stmt stmt, int iCol) {
127-
return sqlite3_column_text((sqlite3_stmt*)(stmt), iCol);
128-
}
129-
130-
static const unsigned char *ts_sqlite3_column_blob(handle_sqlite3_stmt stmt, int iCol) {
131-
return sqlite3_column_blob((sqlite3_stmt*)(stmt), iCol);
132-
}
133-
134-
static int ts_sqlite3_column_type(handle_sqlite3_stmt stmt, int iCol) {
135-
return sqlite3_column_type((sqlite3_stmt*)(stmt), iCol);
136-
}
137-
138-
static int ts_sqlite3_column_bytes(handle_sqlite3_stmt stmt, int iCol) {
139-
return sqlite3_column_bytes((sqlite3_stmt*)(stmt), iCol);
140-
}
141-
142-
static double ts_sqlite3_column_double(handle_sqlite3_stmt stmt, int iCol) {
143-
return sqlite3_column_double((sqlite3_stmt*)(stmt), iCol);
144-
}
145-
146-
static sqlite3_int64 ts_sqlite3_column_int64(handle_sqlite3_stmt stmt, int iCol) {
147-
return sqlite3_column_int64((sqlite3_stmt*)(stmt), iCol);
148-
}
149-
15092
static int ts_sqlite3_disable_function(sqlite3 *db, const char *zFunctionName, int nArg) {
15193
return sqlite3_create_function(db, zFunctionName, nArg, SQLITE_ANY, NULL, NULL, NULL, NULL);
152-
}
94+
}

0 commit comments

Comments
 (0)