github.com/ncruces/go-sqlite3@v0.15.1-0.20240520133447-53eef1510ff0/internal/util/mmap.go (about)

     1  //go:build (darwin || linux) && (amd64 || arm64 || riscv64) && !(sqlite3_noshm || sqlite3_nosys)
     2  
     3  package util
     4  
     5  import (
     6  	"context"
     7  	"os"
     8  	"unsafe"
     9  
    10  	"github.com/tetratelabs/wazero/api"
    11  	"github.com/tetratelabs/wazero/experimental"
    12  	"golang.org/x/sys/unix"
    13  )
    14  
    15  func withAllocator(ctx context.Context) context.Context {
    16  	return experimental.WithMemoryAllocator(ctx,
    17  		experimental.MemoryAllocatorFunc(virtualAlloc))
    18  }
    19  
    20  type mmapState struct {
    21  	regions []*MappedRegion
    22  }
    23  
    24  func (s *mmapState) new(ctx context.Context, mod api.Module, size int32) *MappedRegion {
    25  	// Find unused region.
    26  	for _, r := range s.regions {
    27  		if !r.used && r.size == size {
    28  			return r
    29  		}
    30  	}
    31  
    32  	// Allocate page aligned memmory.
    33  	alloc := mod.ExportedFunction("aligned_alloc")
    34  	stack := [2]uint64{
    35  		uint64(unix.Getpagesize()),
    36  		uint64(size),
    37  	}
    38  	if err := alloc.CallWithStack(ctx, stack[:]); err != nil {
    39  		panic(err)
    40  	}
    41  	if stack[0] == 0 {
    42  		panic(OOMErr)
    43  	}
    44  
    45  	// Save the newly allocated region.
    46  	ptr := uint32(stack[0])
    47  	buf := View(mod, ptr, uint64(size))
    48  	addr := uintptr(unsafe.Pointer(&buf[0]))
    49  	s.regions = append(s.regions, &MappedRegion{
    50  		Ptr:  ptr,
    51  		addr: addr,
    52  		size: size,
    53  	})
    54  	return s.regions[len(s.regions)-1]
    55  }
    56  
    57  type MappedRegion struct {
    58  	addr uintptr
    59  	Ptr  uint32
    60  	size int32
    61  	used bool
    62  }
    63  
    64  func MapRegion(ctx context.Context, mod api.Module, f *os.File, offset int64, size int32, prot int) (*MappedRegion, error) {
    65  	s := ctx.Value(moduleKey{}).(*moduleState)
    66  	r := s.new(ctx, mod, size)
    67  	err := r.mmap(f, offset, prot)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	return r, nil
    72  }
    73  
    74  func (r *MappedRegion) Unmap() error {
    75  	// We can't munmap the region, otherwise it could be remaped.
    76  	// Instead, convert it to a protected, private, anonymous mapping.
    77  	// If successful, it can be reused for a subsequent mmap.
    78  	_, err := mmap(r.addr, uintptr(r.size),
    79  		unix.PROT_NONE, unix.MAP_PRIVATE|unix.MAP_ANON|unix.MAP_FIXED,
    80  		-1, 0)
    81  	r.used = err != nil
    82  	return err
    83  }
    84  
    85  func (r *MappedRegion) mmap(f *os.File, offset int64, prot int) error {
    86  	_, err := mmap(r.addr, uintptr(r.size),
    87  		prot, unix.MAP_SHARED|unix.MAP_FIXED,
    88  		int(f.Fd()), offset)
    89  	r.used = err == nil
    90  	return err
    91  }
    92  
    93  // We need the low level mmap for MAP_FIXED to work.
    94  // Bind the syscall version hoping that it is more stable.
    95  
    96  //go:linkname mmap syscall.mmap
    97  func mmap(addr, length uintptr, prot, flag, fd int, pos int64) (*byte, error)