Skip to content

Commit 01bf8ed

Browse files
committed
Windows shared memory.
1 parent 04af9fe commit 01bf8ed

File tree

4 files changed

+316
-2
lines changed

4 files changed

+316
-2
lines changed

internal/util/mmap_windows.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//go:build !sqlite3_nosys
2+
3+
package util
4+
5+
import (
6+
"context"
7+
"os"
8+
"reflect"
9+
"unsafe"
10+
11+
"github.com/tetratelabs/wazero/api"
12+
"golang.org/x/sys/windows"
13+
)
14+
15+
type MappedRegion struct {
16+
windows.Handle
17+
Data []byte
18+
addr uintptr
19+
}
20+
21+
func MapRegion(ctx context.Context, mod api.Module, f *os.File, offset int64, size int32) (*MappedRegion, error) {
22+
h, err := windows.CreateFileMapping(windows.Handle(f.Fd()), nil, windows.PAGE_READWRITE, 0, 0, nil)
23+
if h == 0 {
24+
return nil, err
25+
}
26+
27+
a, err := windows.MapViewOfFile(h, windows.FILE_MAP_WRITE,
28+
uint32(offset>>32), uint32(offset), uintptr(size))
29+
if a == 0 {
30+
windows.CloseHandle(h)
31+
return nil, err
32+
}
33+
34+
res := &MappedRegion{Handle: h, addr: a}
35+
// SliceHeader, although deprecated, avoids a go vet warning.
36+
sh := (*reflect.SliceHeader)(unsafe.Pointer(&res.Data))
37+
sh.Len = int(size)
38+
sh.Cap = int(size)
39+
sh.Data = a
40+
return res, nil
41+
}
42+
43+
func (r *MappedRegion) Unmap() error {
44+
if r.Data == nil {
45+
return nil
46+
}
47+
err := windows.UnmapViewOfFile(r.addr)
48+
if err != nil {
49+
return err
50+
}
51+
r.Data = nil
52+
return windows.CloseHandle(r.Handle)
53+
}

vfs/shm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build ((linux || darwin || freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_nosys) || sqlite3_flock || sqlite3_dotlk
1+
//go:build ((linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_nosys) || sqlite3_flock || sqlite3_dotlk
22

33
package vfs
44

vfs/shm_other.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build !(((linux || darwin || freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_nosys) || sqlite3_flock || sqlite3_dotlk)
1+
//go:build !(((linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_nosys) || sqlite3_flock || sqlite3_dotlk)
22

33
package vfs
44

vfs/shm_windows.go

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
//go:build (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !(sqlite3_dotlk || sqlite3_nosys)
2+
3+
package vfs
4+
5+
import (
6+
"context"
7+
"io"
8+
"os"
9+
"sync"
10+
"time"
11+
"unsafe"
12+
13+
"github.com/tetratelabs/wazero/api"
14+
"golang.org/x/sys/windows"
15+
16+
"github.com/ncruces/go-sqlite3/internal/util"
17+
"github.com/ncruces/go-sqlite3/util/osutil"
18+
)
19+
20+
type vfsShm struct {
21+
*os.File
22+
mod api.Module
23+
alloc api.Function
24+
free api.Function
25+
path string
26+
regions []*util.MappedRegion
27+
shared [][]byte
28+
shadow []byte
29+
ptrs []uint32
30+
stack [1]uint64
31+
blocking bool
32+
sync.Mutex
33+
}
34+
35+
var _ blockingSharedMemory = &vfsShm{}
36+
37+
func (s *vfsShm) Close() error {
38+
// Unmap regions.
39+
for _, r := range s.regions {
40+
r.Unmap()
41+
}
42+
s.regions = nil
43+
44+
// Close the file.
45+
s.File = nil
46+
return s.File.Close()
47+
}
48+
49+
func (s *vfsShm) shmOpen() _ErrorCode {
50+
if s.File == nil {
51+
f, err := osutil.OpenFile(s.path, os.O_RDWR|os.O_CREATE, 0666)
52+
if err != nil {
53+
return _CANTOPEN
54+
}
55+
s.File = f
56+
}
57+
58+
// Dead man's switch.
59+
if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc == _OK {
60+
err := s.Truncate(0)
61+
osUnlock(s.File, _SHM_DMS, 1)
62+
if err != nil {
63+
return _IOERR_SHMOPEN
64+
}
65+
}
66+
return osReadLock(s.File, _SHM_DMS, 1, time.Millisecond)
67+
}
68+
69+
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, _ErrorCode) {
70+
// Ensure size is a multiple of the OS page size.
71+
if size != _WALINDEX_PGSZ || (windows.Getpagesize()-1)&_WALINDEX_PGSZ != 0 {
72+
return 0, _IOERR_SHMMAP
73+
}
74+
if s.mod == nil {
75+
s.mod = mod
76+
s.free = mod.ExportedFunction("sqlite3_free")
77+
s.alloc = mod.ExportedFunction("sqlite3_malloc64")
78+
}
79+
if rc := s.shmOpen(); rc != _OK {
80+
return 0, rc
81+
}
82+
83+
defer s.shmAcquire()
84+
85+
// Check if file is big enough.
86+
o, err := s.Seek(0, io.SeekEnd)
87+
if err != nil {
88+
return 0, _IOERR_SHMSIZE
89+
}
90+
if n := (int64(id) + 1) * int64(size); n > o {
91+
if !extend {
92+
return 0, _OK
93+
}
94+
if osAllocate(s.File, n) != nil {
95+
return 0, _IOERR_SHMSIZE
96+
}
97+
}
98+
99+
// Map the file into memory.
100+
r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size)
101+
if err != nil {
102+
return 0, _IOERR_SHMMAP
103+
}
104+
s.regions = append(s.regions, r)
105+
106+
if int(id) >= len(s.shared) {
107+
s.shared = append(s.shared, make([][]byte, int(id)-len(s.shared))...)
108+
}
109+
s.shared[id] = r.Data
110+
111+
// Allocate shadow memory.
112+
if n := (int(id) + 1) * int(size); n > len(s.shadow) {
113+
s.shadow = append(s.shadow, make([]byte, n-len(s.shadow))...)
114+
}
115+
116+
// Allocate local memory.
117+
for int(id) >= len(s.ptrs) {
118+
s.stack[0] = uint64(size)
119+
if err := s.alloc.CallWithStack(ctx, s.stack[:]); err != nil {
120+
panic(err)
121+
}
122+
if s.stack[0] == 0 {
123+
panic(util.OOMErr)
124+
}
125+
clear(util.View(s.mod, uint32(s.stack[0]), _WALINDEX_PGSZ))
126+
s.ptrs = append(s.ptrs, uint32(s.stack[0]))
127+
}
128+
129+
return s.ptrs[id], _OK
130+
}
131+
132+
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
133+
// Argument check.
134+
if n <= 0 || offset < 0 || offset+n > _SHM_NLOCK {
135+
panic(util.AssertErr())
136+
}
137+
switch flags {
138+
case
139+
_SHM_LOCK | _SHM_SHARED,
140+
_SHM_LOCK | _SHM_EXCLUSIVE,
141+
_SHM_UNLOCK | _SHM_SHARED,
142+
_SHM_UNLOCK | _SHM_EXCLUSIVE:
143+
//
144+
default:
145+
panic(util.AssertErr())
146+
}
147+
if n != 1 && flags&_SHM_EXCLUSIVE == 0 {
148+
panic(util.AssertErr())
149+
}
150+
151+
switch {
152+
case flags&_SHM_LOCK != 0:
153+
defer s.shmAcquire()
154+
case flags&_SHM_EXCLUSIVE != 0:
155+
s.shmRelease()
156+
}
157+
158+
var timeout time.Duration
159+
if s.blocking {
160+
timeout = time.Millisecond
161+
}
162+
163+
switch {
164+
case flags&_SHM_UNLOCK != 0:
165+
return osUnlock(s.File, _SHM_BASE+uint32(offset), uint32(n))
166+
case flags&_SHM_SHARED != 0:
167+
return osReadLock(s.File, _SHM_BASE+uint32(offset), uint32(n), timeout)
168+
case flags&_SHM_EXCLUSIVE != 0:
169+
return osWriteLock(s.File, _SHM_BASE+uint32(offset), uint32(n), timeout)
170+
default:
171+
panic(util.AssertErr())
172+
}
173+
}
174+
175+
func (s *vfsShm) shmUnmap(delete bool) {
176+
if s.File == nil {
177+
return
178+
}
179+
180+
s.shmRelease()
181+
182+
// Free local memory.
183+
for _, p := range s.ptrs {
184+
s.stack[0] = uint64(p)
185+
if err := s.free.CallWithStack(context.Background(), s.stack[:]); err != nil {
186+
panic(err)
187+
}
188+
}
189+
s.ptrs = nil
190+
s.shadow = nil
191+
s.shared = nil
192+
193+
// Close the file.
194+
if delete {
195+
os.Remove(s.path)
196+
}
197+
s.Close()
198+
s.File = nil
199+
}
200+
201+
func (s *vfsShm) shmBarrier() {
202+
s.Lock()
203+
s.shmAcquire()
204+
s.shmRelease()
205+
s.Unlock()
206+
}
207+
208+
const _WALINDEX_PGSZ = 32768
209+
210+
func (s *vfsShm) shmAcquire() {
211+
// Copies modified words from shared to private memory.
212+
for id, p := range s.ptrs {
213+
i0 := id * _WALINDEX_PGSZ
214+
i1 := i0 + _WALINDEX_PGSZ
215+
shared := shmPage(s.shared[id])
216+
shadow := shmPage(s.shadow[i0:i1])
217+
privat := shmPage(util.View(s.mod, p, _WALINDEX_PGSZ))
218+
if shmPageEq(shadow, shared) {
219+
continue
220+
}
221+
for i, shared := range shared {
222+
if shadow[i] != shared {
223+
shadow[i] = shared
224+
privat[i] = shared
225+
}
226+
}
227+
}
228+
}
229+
230+
func (s *vfsShm) shmRelease() {
231+
// Copies modified words from private to shared memory.
232+
for id, p := range s.ptrs {
233+
i0 := id * _WALINDEX_PGSZ
234+
i1 := i0 + _WALINDEX_PGSZ
235+
shared := shmPage(s.shared[id])
236+
shadow := shmPage(s.shadow[i0:i1])
237+
privat := shmPage(util.View(s.mod, p, _WALINDEX_PGSZ))
238+
if shmPageEq(shadow, privat) {
239+
continue
240+
}
241+
for i, privat := range privat {
242+
if shadow[i] != privat {
243+
shadow[i] = privat
244+
shared[i] = privat
245+
}
246+
}
247+
}
248+
}
249+
250+
func shmPage(s []byte) *[_WALINDEX_PGSZ / 4]uint32 {
251+
p := (*uint32)(unsafe.Pointer(unsafe.SliceData(s)))
252+
return (*[_WALINDEX_PGSZ / 4]uint32)(unsafe.Slice(p, _WALINDEX_PGSZ/4))
253+
}
254+
255+
func shmPageEq(p1, p2 *[_WALINDEX_PGSZ / 4]uint32) bool {
256+
return *(*[_WALINDEX_PGSZ / 8]uint32)(p1[:]) == *(*[_WALINDEX_PGSZ / 8]uint32)(p2[:])
257+
}
258+
259+
func (s *vfsShm) shmEnableBlocking(block bool) {
260+
s.blocking = block
261+
}

0 commit comments

Comments
 (0)