about summary refs log tree commit diff
path: root/vendor/modernc.org/libc/etc.go
diff options
context:
space:
mode:
authorEmile <git@emile.space>2024-08-16 19:50:26 +0200
committerEmile <git@emile.space>2024-08-16 19:50:26 +0200
commit1a57267a17c2fc17fb6e104846fabc3e363c326c (patch)
tree1e574e3a80622086dc3c81ff9cba65ef7049b1a9 /vendor/modernc.org/libc/etc.go
initial commit
Diffstat (limited to 'vendor/modernc.org/libc/etc.go')
-rw-r--r--vendor/modernc.org/libc/etc.go953
1 files changed, 953 insertions, 0 deletions
diff --git a/vendor/modernc.org/libc/etc.go b/vendor/modernc.org/libc/etc.go
new file mode 100644
index 0000000..1f78cfb
--- /dev/null
+++ b/vendor/modernc.org/libc/etc.go
@@ -0,0 +1,953 @@
+// Copyright 2020 The Libc Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !(linux && (amd64 || arm64 || loong64))
+
+package libc // import "modernc.org/libc"
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"reflect"
+	"runtime"
+	"runtime/debug"
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+	"sync/atomic"
+	"syscall"
+	"time"
+	"unsafe"
+
+	"modernc.org/libc/errno"
+	"modernc.org/libc/signal"
+	"modernc.org/libc/sys/types"
+)
+
+const (
+	allocatorPageOverhead = 4 * unsafe.Sizeof(int(0))
+	stackHeaderSize       = unsafe.Sizeof(stackHeader{})
+	stackSegmentSize      = 1<<12 - allocatorPageOverhead
+	uintptrSize           = unsafe.Sizeof(uintptr(0))
+)
+
+var (
+	Covered  = map[uintptr]struct{}{}
+	CoveredC = map[string]struct{}{}
+	fToken   uintptr
+	tid      int32
+
+	atExit   []func()
+	atExitMu sync.Mutex
+
+	signals   [signal.NSIG]uintptr
+	signalsMu sync.Mutex
+
+	objectMu sync.Mutex
+	objects  = map[uintptr]interface{}{}
+
+	tlsBalance int32
+
+	_ = origin
+	_ = trc
+)
+
+func init() {
+	if n := stackHeaderSize; n%16 != 0 {
+		panic(fmt.Errorf("internal error: stackHeaderSize %v == %v (mod 16)", n, n%16))
+	}
+}
+
+func origin(skip int) string {
+	pc, fn, fl, _ := runtime.Caller(skip)
+	f := runtime.FuncForPC(pc)
+	var fns string
+	if f != nil {
+		fns = f.Name()
+		if x := strings.LastIndex(fns, "."); x > 0 {
+			fns = fns[x+1:]
+		}
+	}
+	return fmt.Sprintf("%s:%d:%s", filepath.Base(fn), fl, fns)
+}
+
+func trc(s string, args ...interface{}) string { //TODO-
+	switch {
+	case s == "":
+		s = fmt.Sprintf(strings.Repeat("%v ", len(args)), args...)
+	default:
+		s = fmt.Sprintf(s, args...)
+	}
+	r := fmt.Sprintf("%s: TRC %s", origin(2), s)
+	fmt.Fprintf(os.Stdout, "%s\n", r)
+	os.Stdout.Sync()
+	return r
+}
+
+func todo(s string, args ...interface{}) string { //TODO-
+	switch {
+	case s == "":
+		s = fmt.Sprintf(strings.Repeat("%v ", len(args)), args...)
+	default:
+		s = fmt.Sprintf(s, args...)
+	}
+	r := fmt.Sprintf("%s: TODOTODO %s", origin(2), s) //TODOOK
+	if dmesgs {
+		dmesg("%s", r)
+	}
+	fmt.Fprintf(os.Stdout, "%s\n", r)
+	fmt.Fprintf(os.Stdout, "%s\n", debug.Stack()) //TODO-
+	os.Stdout.Sync()
+	os.Exit(1)
+	panic("unrechable")
+}
+
+var coverPCs [1]uintptr //TODO not concurrent safe
+
+func Cover() {
+	runtime.Callers(2, coverPCs[:])
+	Covered[coverPCs[0]] = struct{}{}
+}
+
+func CoverReport(w io.Writer) error {
+	var a []string
+	pcs := make([]uintptr, 1)
+	for pc := range Covered {
+		pcs[0] = pc
+		frame, _ := runtime.CallersFrames(pcs).Next()
+		a = append(a, fmt.Sprintf("%s:%07d:%s", filepath.Base(frame.File), frame.Line, frame.Func.Name()))
+	}
+	sort.Strings(a)
+	_, err := fmt.Fprintf(w, "%s\n", strings.Join(a, "\n"))
+	return err
+}
+
+func CoverC(s string) {
+	CoveredC[s] = struct{}{}
+}
+
+func CoverCReport(w io.Writer) error {
+	var a []string
+	for k := range CoveredC {
+		a = append(a, k)
+	}
+	sort.Strings(a)
+	_, err := fmt.Fprintf(w, "%s\n", strings.Join(a, "\n"))
+	return err
+}
+
+func token() uintptr { return atomic.AddUintptr(&fToken, 1) }
+
+func addObject(o interface{}) uintptr {
+	t := token()
+	objectMu.Lock()
+	objects[t] = o
+	objectMu.Unlock()
+	return t
+}
+
+func getObject(t uintptr) interface{} {
+	objectMu.Lock()
+	o := objects[t]
+	if o == nil {
+		panic(todo("", t))
+	}
+
+	objectMu.Unlock()
+	return o
+}
+
+func removeObject(t uintptr) {
+	objectMu.Lock()
+	if _, ok := objects[t]; !ok {
+		panic(todo(""))
+	}
+
+	delete(objects, t)
+	objectMu.Unlock()
+}
+
+func (t *TLS) setErrno(err interface{}) {
+	if t == nil {
+		panic("nil TLS")
+	}
+
+	if memgrind {
+		if atomic.SwapInt32(&t.reentryGuard, 1) != 0 {
+			panic(todo("concurrent use of TLS instance %p", t))
+		}
+
+		defer func() {
+			if atomic.SwapInt32(&t.reentryGuard, 0) != 1 {
+				panic(todo("concurrent use of TLS instance %p", t))
+			}
+		}()
+	}
+	// if dmesgs {
+	// 	dmesg("%v: %T(%v)\n%s", origin(1), err, err, debug.Stack())
+	// }
+again:
+	switch x := err.(type) {
+	case int:
+		*(*int32)(unsafe.Pointer(t.errnop)) = int32(x)
+	case int32:
+		*(*int32)(unsafe.Pointer(t.errnop)) = x
+	case *os.PathError:
+		err = x.Err
+		goto again
+	case syscall.Errno:
+		*(*int32)(unsafe.Pointer(t.errnop)) = int32(x)
+	case *os.SyscallError:
+		err = x.Err
+		goto again
+	default:
+		panic(todo("%T", x))
+	}
+}
+
+// Close frees the resources of t.
+func (t *TLS) Close() {
+	t.Free(int(unsafe.Sizeof(int32(0))))
+	if memgrind {
+		if t.stackHeaderBalance != 0 {
+			panic(todo("non zero stack header balance: %d", t.stackHeaderBalance))
+		}
+
+		atomic.AddInt32(&tlsBalance, -1)
+	}
+	t.pthreadData.close(t)
+	*t = TLS{}
+}
+
+// Alloc allocates n bytes of thread-local storage. It must be paired with a
+// call to t.Free(n), using the same n. The order matters. This is ok:
+//
+//	t.Alloc(11)
+//		t.Alloc(22)
+//		t.Free(22)
+//	t.Free(11)
+//
+// This is not correct:
+//
+//	t.Alloc(11)
+//		t.Alloc(22)
+//		t.Free(11)
+//	t.Free(22)
+func (t *TLS) Alloc(n int) (r uintptr) {
+	if memgrind {
+		if atomic.SwapInt32(&t.reentryGuard, 1) != 0 {
+			panic(todo("concurrent use of TLS instance %p", t))
+		}
+
+		defer func() {
+			if atomic.SwapInt32(&t.reentryGuard, 0) != 1 {
+				panic(todo("concurrent use of TLS instance %p", t))
+			}
+		}()
+	}
+	n += 15
+	n &^= 15
+	if t.stack.free >= n {
+		r = t.stack.sp
+		t.stack.free -= n
+		t.stack.sp += uintptr(n)
+		return r
+	}
+	//if we have a next stack
+	if nstack := t.stack.next; nstack != 0 {
+		if (*stackHeader)(unsafe.Pointer(nstack)).free >= n {
+			*(*stackHeader)(unsafe.Pointer(t.stack.page)) = t.stack
+			t.stack = *(*stackHeader)(unsafe.Pointer(nstack))
+			r = t.stack.sp
+			t.stack.free -= n
+			t.stack.sp += uintptr(n)
+			return r
+		}
+		nstack := *(*stackHeader)(unsafe.Pointer(t.stack.next))
+		for ; ; nstack = *(*stackHeader)(unsafe.Pointer(nstack.next)) {
+			if memgrind {
+				if atomic.AddInt32(&t.stackHeaderBalance, -1) < 0 {
+					panic(todo("negative stack header balance"))
+				}
+			}
+			Xfree(t, nstack.page)
+			if nstack.next == 0 {
+				break
+			}
+		}
+		t.stack.next = 0
+	}
+
+	if t.stack.page != 0 {
+		*(*stackHeader)(unsafe.Pointer(t.stack.page)) = t.stack
+	}
+
+	rq := n + int(stackHeaderSize)
+	if rq%int(stackSegmentSize) != 0 {
+		rq -= rq % int(stackSegmentSize)
+		rq += int(stackSegmentSize)
+	}
+	t.stack.free = rq - int(stackHeaderSize)
+	t.stack.prev = t.stack.page
+
+	rq += 15
+	rq &^= 15
+	t.stack.page = Xmalloc(t, types.Size_t(rq))
+	if t.stack.page == 0 {
+		panic("OOM")
+	}
+
+	if memgrind {
+		atomic.AddInt32(&t.stackHeaderBalance, 1)
+	}
+	t.stack.sp = t.stack.page + stackHeaderSize
+
+	r = t.stack.sp
+	t.stack.free -= n
+	t.stack.sp += uintptr(n)
+	if t.stack.prev != 0 {
+		(*stackHeader)(unsafe.Pointer(t.stack.prev)).next = t.stack.page
+	}
+
+	return r
+}
+
+// this declares how many stack frames are kept alive before being freed
+const stackFrameKeepalive = 2
+
+// Free deallocates n bytes of thread-local storage. See TLS.Alloc for details
+// on correct usage.
+func (t *TLS) Free(n int) {
+	if memgrind {
+		if atomic.SwapInt32(&t.reentryGuard, 1) != 0 {
+			panic(todo("concurrent use of TLS instance %p", t))
+		}
+
+		defer func() {
+			if atomic.SwapInt32(&t.reentryGuard, 0) != 1 {
+				panic(todo("concurrent use of TLS instance %p", t))
+			}
+		}()
+	}
+	n += 15
+	n &^= 15
+	t.stack.free += n
+	t.stack.sp -= uintptr(n)
+	if t.stack.sp != t.stack.page+stackHeaderSize {
+		return
+	}
+
+	nstack := t.stack
+
+	//if we are the first one, just free all of them
+	if t.stack.prev == 0 {
+		for ; ; nstack = *(*stackHeader)(unsafe.Pointer(nstack.next)) {
+			if memgrind {
+				if atomic.AddInt32(&t.stackHeaderBalance, -1) < 0 {
+					panic(todo("negative stack header balance"))
+				}
+			}
+			Xfree(t, nstack.page)
+			if nstack.next == 0 {
+				break
+			}
+		}
+		t.stack = stackHeader{}
+		return
+	}
+
+	//look if we are in the last n stackframes (n=stackFrameKeepalive)
+	//if we find something just return and set the current stack pointer to the previous one
+	for i := 0; i < stackFrameKeepalive; i++ {
+		if nstack.next == 0 {
+			*((*stackHeader)(unsafe.Pointer(t.stack.page))) = t.stack
+			t.stack = *(*stackHeader)(unsafe.Pointer(t.stack.prev))
+			return
+		}
+		nstack = *(*stackHeader)(unsafe.Pointer(nstack.next))
+	}
+
+	//else only free the last
+	if memgrind {
+		if atomic.AddInt32(&t.stackHeaderBalance, -1) < 0 {
+			panic(todo("negative stack header balance"))
+		}
+	}
+	Xfree(t, nstack.page)
+	(*stackHeader)(unsafe.Pointer(nstack.prev)).next = 0
+	*(*stackHeader)(unsafe.Pointer(t.stack.page)) = t.stack
+	t.stack = *(*stackHeader)(unsafe.Pointer(t.stack.prev))
+}
+
+type stackHeader struct {
+	free int     // bytes left in page
+	page uintptr // stack page
+	prev uintptr // prev stack page = prev stack header
+	next uintptr // next stack page = next stack header
+	sp   uintptr // next allocation address
+	_    stackHeaderPadding
+}
+
+func cString(t *TLS, s string) uintptr { //TODO-
+	n := len(s)
+	p := Xmalloc(t, types.Size_t(n)+1)
+	if p == 0 {
+		panic("OOM")
+	}
+
+	copy((*RawMem)(unsafe.Pointer(p))[:n:n], s)
+	*(*byte)(unsafe.Pointer(p + uintptr(n))) = 0
+	return p
+}
+
+// VaList fills a varargs list at p with args and returns p.  The list must
+// have been allocated by caller and it must not be in Go managed memory, ie.
+// it must be pinned. Caller is responsible for freeing the list.
+//
+// Individual arguments must be one of int, uint, int32, uint32, int64, uint64,
+// float64, uintptr or Intptr. Other types will panic.
+//
+// This function supports code generated by ccgo/v3. For manually constructed
+// var args it's recommended to use the NewVaList function instead.
+//
+// Note: The C translated to Go varargs ABI alignment for all types is 8 on all
+// architectures.
+func VaList(p uintptr, args ...interface{}) (r uintptr) {
+	if p&7 != 0 {
+		panic("internal error")
+	}
+
+	r = p
+	for _, v := range args {
+		switch x := v.(type) {
+		case int:
+			*(*int64)(unsafe.Pointer(p)) = int64(x)
+		case int32:
+			*(*int64)(unsafe.Pointer(p)) = int64(x)
+		case int64:
+			*(*int64)(unsafe.Pointer(p)) = x
+		case uint:
+			*(*uint64)(unsafe.Pointer(p)) = uint64(x)
+		case uint16:
+			*(*uint64)(unsafe.Pointer(p)) = uint64(x)
+		case uint32:
+			*(*uint64)(unsafe.Pointer(p)) = uint64(x)
+		case uint64:
+			*(*uint64)(unsafe.Pointer(p)) = x
+		case float64:
+			*(*float64)(unsafe.Pointer(p)) = x
+		case uintptr:
+			*(*uintptr)(unsafe.Pointer(p)) = x
+		default:
+			sz := reflect.TypeOf(v).Size()
+			copy(unsafe.Slice((*byte)(unsafe.Pointer(p)), sz), unsafe.Slice((*byte)(unsafe.Pointer((*[2]uintptr)(unsafe.Pointer(&v))[1])), sz))
+			p += roundup(sz, 8)
+			continue
+		}
+		p += 8
+	}
+	return r
+}
+
+// NewVaListN returns a newly allocated va_list for n items. The caller of
+// NewVaListN is responsible for freeing the va_list.
+func NewVaListN(n int) (va_list uintptr) {
+	return Xmalloc(nil, types.Size_t(8*n))
+}
+
+// NewVaList is like VaList but automatically allocates the correct amount of
+// memory for all of the items in args.
+//
+// The va_list return value is used to pass the constructed var args to var
+// args accepting functions. The caller of NewVaList is responsible for freeing
+// the va_list.
+func NewVaList(args ...interface{}) (va_list uintptr) {
+	return VaList(NewVaListN(len(args)), args...)
+}
+
+func VaOther(app *uintptr, sz uint64) (r uintptr) {
+	ap := *(*uintptr)(unsafe.Pointer(app))
+	if ap == 0 {
+		return 0
+	}
+
+	r = ap
+	ap = roundup(ap+uintptr(sz), 8)
+	*(*uintptr)(unsafe.Pointer(app)) = ap
+	return r
+}
+
+func VaInt32(app *uintptr) int32 {
+	ap := *(*uintptr)(unsafe.Pointer(app))
+	if ap == 0 {
+		return 0
+	}
+
+	ap = roundup(ap, 8)
+	v := int32(*(*int64)(unsafe.Pointer(ap)))
+	ap += 8
+	*(*uintptr)(unsafe.Pointer(app)) = ap
+	return v
+}
+
+func VaUint32(app *uintptr) uint32 {
+	ap := *(*uintptr)(unsafe.Pointer(app))
+	if ap == 0 {
+		return 0
+	}
+
+	ap = roundup(ap, 8)
+	v := uint32(*(*uint64)(unsafe.Pointer(ap)))
+	ap += 8
+	*(*uintptr)(unsafe.Pointer(app)) = ap
+	return v
+}
+
+func VaInt64(app *uintptr) int64 {
+	ap := *(*uintptr)(unsafe.Pointer(app))
+	if ap == 0 {
+		return 0
+	}
+
+	ap = roundup(ap, 8)
+	v := *(*int64)(unsafe.Pointer(ap))
+	ap += 8
+	*(*uintptr)(unsafe.Pointer(app)) = ap
+	return v
+}
+
+func VaUint64(app *uintptr) uint64 {
+	ap := *(*uintptr)(unsafe.Pointer(app))
+	if ap == 0 {
+		return 0
+	}
+
+	ap = roundup(ap, 8)
+	v := *(*uint64)(unsafe.Pointer(ap))
+	ap += 8
+	*(*uintptr)(unsafe.Pointer(app)) = ap
+	return v
+}
+
+func VaFloat32(app *uintptr) float32 {
+	ap := *(*uintptr)(unsafe.Pointer(app))
+	if ap == 0 {
+		return 0
+	}
+
+	ap = roundup(ap, 8)
+	v := *(*float64)(unsafe.Pointer(ap))
+	ap += 8
+	*(*uintptr)(unsafe.Pointer(app)) = ap
+	return float32(v)
+}
+
+func VaFloat64(app *uintptr) float64 {
+	ap := *(*uintptr)(unsafe.Pointer(app))
+	if ap == 0 {
+		return 0
+	}
+
+	ap = roundup(ap, 8)
+	v := *(*float64)(unsafe.Pointer(ap))
+	ap += 8
+	*(*uintptr)(unsafe.Pointer(app)) = ap
+	return v
+}
+
+func VaUintptr(app *uintptr) uintptr {
+	ap := *(*uintptr)(unsafe.Pointer(app))
+	if ap == 0 {
+		return 0
+	}
+
+	ap = roundup(ap, 8)
+	v := *(*uintptr)(unsafe.Pointer(ap))
+	ap += 8
+	*(*uintptr)(unsafe.Pointer(app)) = ap
+	return v
+}
+
+func getVaList(va uintptr) []string {
+	r := []string{}
+
+	for p := va; ; p += 8 {
+		st := *(*uintptr)(unsafe.Pointer(p))
+		if st == 0 {
+			return r
+		}
+		r = append(r, GoString(st))
+	}
+	return r
+}
+
+func roundup(n, to uintptr) uintptr {
+	if r := n % to; r != 0 {
+		return n + to - r
+	}
+
+	return n
+}
+
+func GoString(s uintptr) string {
+	if s == 0 {
+		return ""
+	}
+
+	var buf []byte
+	for {
+		b := *(*byte)(unsafe.Pointer(s))
+		if b == 0 {
+			return string(buf)
+		}
+
+		buf = append(buf, b)
+		s++
+	}
+}
+
+// GoBytes returns a byte slice from a C char* having length len bytes.
+func GoBytes(s uintptr, len int) []byte {
+	if len == 0 {
+		return nil
+	}
+
+	return (*RawMem)(unsafe.Pointer(s))[:len:len]
+}
+
+func Bool(v bool) bool { return v }
+
+func Bool32(b bool) int32 {
+	if b {
+		return 1
+	}
+
+	return 0
+}
+
+func Bool64(b bool) int64 {
+	if b {
+		return 1
+	}
+
+	return 0
+}
+
+type sorter struct {
+	len  int
+	base uintptr
+	sz   uintptr
+	f    func(*TLS, uintptr, uintptr) int32
+	t    *TLS
+}
+
+func (s *sorter) Len() int { return s.len }
+
+func (s *sorter) Less(i, j int) bool {
+	return s.f(s.t, s.base+uintptr(i)*s.sz, s.base+uintptr(j)*s.sz) < 0
+}
+
+func (s *sorter) Swap(i, j int) {
+	p := uintptr(s.base + uintptr(i)*s.sz)
+	q := uintptr(s.base + uintptr(j)*s.sz)
+	for i := 0; i < int(s.sz); i++ {
+		*(*byte)(unsafe.Pointer(p)), *(*byte)(unsafe.Pointer(q)) = *(*byte)(unsafe.Pointer(q)), *(*byte)(unsafe.Pointer(p))
+		p++
+		q++
+	}
+}
+
+func CString(s string) (uintptr, error) {
+	n := len(s)
+	p := Xmalloc(nil, types.Size_t(n)+1)
+	if p == 0 {
+		return 0, fmt.Errorf("CString: cannot allocate %d bytes", n+1)
+	}
+
+	copy((*RawMem)(unsafe.Pointer(p))[:n:n], s)
+	*(*byte)(unsafe.Pointer(p + uintptr(n))) = 0
+	return p, nil
+}
+
+func GetEnviron() (r []string) {
+	for p := Environ(); ; p += unsafe.Sizeof(p) {
+		q := *(*uintptr)(unsafe.Pointer(p))
+		if q == 0 {
+			return r
+		}
+
+		r = append(r, GoString(q))
+	}
+}
+
+func strToUint64(t *TLS, s uintptr, base int32) (seenDigits, neg bool, next uintptr, n uint64, err int32) {
+	var c byte
+out:
+	for {
+		c = *(*byte)(unsafe.Pointer(s))
+		switch c {
+		case ' ', '\t', '\n', '\r', '\v', '\f':
+			s++
+		case '+':
+			s++
+			break out
+		case '-':
+			s++
+			neg = true
+			break out
+		default:
+			break out
+		}
+	}
+	for {
+		c = *(*byte)(unsafe.Pointer(s))
+		var digit uint64
+		switch base {
+		case 10:
+			switch {
+			case c >= '0' && c <= '9':
+				seenDigits = true
+				digit = uint64(c) - '0'
+			default:
+				return seenDigits, neg, s, n, 0
+			}
+		case 16:
+			if c >= 'A' && c <= 'F' {
+				c = c + ('a' - 'A')
+			}
+			switch {
+			case c >= '0' && c <= '9':
+				seenDigits = true
+				digit = uint64(c) - '0'
+			case c >= 'a' && c <= 'f':
+				seenDigits = true
+				digit = uint64(c) - 'a' + 10
+			default:
+				return seenDigits, neg, s, n, 0
+			}
+		default:
+			panic(todo("", base))
+		}
+		n0 := n
+		n = uint64(base)*n + digit
+		if n < n0 { // overflow
+			return seenDigits, neg, s, n0, errno.ERANGE
+		}
+
+		s++
+	}
+}
+
+func strToFloatt64(t *TLS, s uintptr, bits int) (n float64, errno int32) {
+	var b []byte
+	var neg bool
+
+	defer func() {
+		var err error
+		if n, err = strconv.ParseFloat(string(b), bits); err != nil {
+			panic(todo(""))
+		}
+
+		if neg {
+			n = -n
+		}
+	}()
+
+	var c byte
+out:
+	for {
+		c = *(*byte)(unsafe.Pointer(s))
+		switch c {
+		case ' ', '\t', '\n', '\r', '\v', '\f':
+			s++
+		case '+':
+			s++
+			break out
+		case '-':
+			s++
+			neg = true
+			break out
+		default:
+			break out
+		}
+	}
+	for {
+		c = *(*byte)(unsafe.Pointer(s))
+		switch {
+		case c >= '0' && c <= '9':
+			b = append(b, c)
+		case c == '.':
+			b = append(b, c)
+			s++
+			for {
+				c = *(*byte)(unsafe.Pointer(s))
+				switch {
+				case c >= '0' && c <= '9':
+					b = append(b, c)
+				case c == 'e' || c == 'E':
+					b = append(b, c)
+					s++
+					for {
+						c = *(*byte)(unsafe.Pointer(s))
+						switch {
+						case c == '+' || c == '-':
+							b = append(b, c)
+							s++
+							for {
+								c = *(*byte)(unsafe.Pointer(s))
+								switch {
+								case c >= '0' && c <= '9':
+									b = append(b, c)
+								default:
+									return
+								}
+
+								s++
+							}
+						default:
+							panic(todo("%q %q", b, string(c)))
+						}
+					}
+				default:
+					return
+				}
+
+				s++
+			}
+		default:
+			panic(todo("%q %q", b, string(c)))
+		}
+
+		s++
+	}
+}
+
+func parseZone(s string) (name string, off int) {
+	_, name, off, _ = parseZoneOffset(s, false)
+	return name, off
+}
+
+func parseZoneOffset(s string, offOpt bool) (string, string, int, bool) {
+	s0 := s
+	name := s
+	for len(s) != 0 {
+		switch c := s[0]; {
+		case c >= 'A' && c <= 'Z', c >= 'a' && c <= 'z', c == '_', c == '/':
+			s = s[1:]
+		default:
+			name = name[:len(name)-len(s)]
+			if len(name) < 3 {
+				panic(todo("%q", s0))
+			}
+
+			if offOpt {
+				if len(s) == 0 {
+					return "", name, 0, false
+				}
+
+				if c := s[0]; (c < '0' || c > '9') && c != '+' && c != '-' {
+					return s, name, 0, false
+				}
+			}
+
+			s, off := parseOffset(s)
+			return s, name, off, true
+		}
+	}
+	return "", s0, 0, true
+}
+
+// [+|-]hh[:mm[:ss]]
+func parseOffset(s string) (string, int) {
+	if len(s) == 0 {
+		panic(todo(""))
+	}
+
+	k := 1
+	switch s[0] {
+	case '+':
+		// nop
+		s = s[1:]
+	case '-':
+		k = -1
+		s = s[1:]
+	}
+	s, hh, ok := parseUint(s)
+	if !ok {
+		panic(todo(""))
+	}
+
+	n := hh * 3600
+	if len(s) == 0 || s[0] != ':' {
+		return s, k * n
+	}
+
+	s = s[1:] // ':'
+	if len(s) == 0 {
+		panic(todo(""))
+	}
+
+	s, mm, ok := parseUint(s)
+	if !ok {
+		panic(todo(""))
+	}
+
+	n += mm * 60
+	if len(s) == 0 || s[0] != ':' {
+		return s, k * n
+	}
+
+	s = s[1:] // ':'
+	if len(s) == 0 {
+		panic(todo(""))
+	}
+
+	s, ss, _ := parseUint(s)
+	return s, k * (n + ss)
+}
+
+func parseUint(s string) (string, int, bool) {
+	var ok bool
+	var r int
+	for len(s) != 0 {
+		switch c := s[0]; {
+		case c >= '0' && c <= '9':
+			ok = true
+			r0 := r
+			r = 10*r + int(c) - '0'
+			if r < r0 {
+				panic(todo(""))
+			}
+
+			s = s[1:]
+		default:
+			return s, r, ok
+		}
+	}
+	return s, r, ok
+}
+
+// https://stackoverflow.com/a/53052382
+//
+// isTimeDST returns true if time t occurs within daylight saving time
+// for its time zone.
+func isTimeDST(t time.Time) bool {
+	// If the most recent (within the last year) clock change
+	// was forward then assume the change was from std to dst.
+	hh, mm, _ := t.UTC().Clock()
+	tClock := hh*60 + mm
+	for m := -1; m > -12; m-- {
+		// assume dst lasts for at least one month
+		hh, mm, _ := t.AddDate(0, m, 0).UTC().Clock()
+		clock := hh*60 + mm
+		if clock != tClock {
+			return clock > tClock
+		}
+	}
+	// assume no dst
+	return false
+}