From f26311c60e22e25700e644f6cb4cf3a8323888c0 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Fri, 21 Jun 2024 03:29:24 +0800 Subject: [PATCH] patch reflect: Append/Index; Int fix --- _demo/reflect/reflect.go | 13 + internal/abi/type.go | 16 +- internal/lib/reflect/makefunc.go | 182 +++++++++ internal/lib/reflect/type.go | 248 ++++++++++++- internal/lib/reflect/unsafeheader.go | 5 - internal/lib/reflect/value.go | 532 ++++++++++++++++++++++++--- internal/runtime/mbarrier.go | 16 +- internal/runtime/slice.go | 40 -- internal/runtime/z_slice.go | 46 ++- 9 files changed, 969 insertions(+), 129 deletions(-) create mode 100644 _demo/reflect/reflect.go create mode 100644 internal/lib/reflect/makefunc.go delete mode 100644 internal/runtime/slice.go diff --git a/_demo/reflect/reflect.go b/_demo/reflect/reflect.go new file mode 100644 index 000000000..1c0ab027d --- /dev/null +++ b/_demo/reflect/reflect.go @@ -0,0 +1,13 @@ +package main + +import "reflect" + +func main() { + tyIntSlice := reflect.SliceOf(reflect.TypeOf(0)) + v := reflect.Zero(tyIntSlice) + v = reflect.Append(v, reflect.ValueOf(1), reflect.ValueOf(2), reflect.ValueOf(3)) + for i, n := 0, v.Len(); i < n; i++ { + item := v.Index(i) + println(item.Int()) + } +} diff --git a/internal/abi/type.go b/internal/abi/type.go index a02f3be78..b2a018332 100644 --- a/internal/abi/type.go +++ b/internal/abi/type.go @@ -440,20 +440,20 @@ func (t *Type) Len() int { // Elem returns the element type for t if t is an array, channel, map, pointer, or slice, otherwise nil. func (t *Type) Elem() *Type { switch t.Kind() { - case Array: - tt := (*ArrayType)(unsafe.Pointer(t)) + case Pointer: + tt := (*PtrType)(unsafe.Pointer(t)) return tt.Elem - case Chan: - tt := (*ChanType)(unsafe.Pointer(t)) + case Slice: + tt := (*SliceType)(unsafe.Pointer(t)) return tt.Elem case Map: tt := (*MapType)(unsafe.Pointer(t)) return tt.Elem - case Pointer: - tt := (*PtrType)(unsafe.Pointer(t)) + case Array: + tt := (*ArrayType)(unsafe.Pointer(t)) return tt.Elem - case Slice: - tt := (*SliceType)(unsafe.Pointer(t)) + case Chan: + tt := (*ChanType)(unsafe.Pointer(t)) return tt.Elem } return nil diff --git a/internal/lib/reflect/makefunc.go b/internal/lib/reflect/makefunc.go new file mode 100644 index 000000000..06b0ae92d --- /dev/null +++ b/internal/lib/reflect/makefunc.go @@ -0,0 +1,182 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// MakeFunc implementation. + +package reflect + +/* +import ( + "unsafe" +) + +// makeFuncImpl is the closure value implementing the function +// returned by MakeFunc. +// The first three words of this type must be kept in sync with +// methodValue and runtime.reflectMethodValue. +// Any changes should be reflected in all three. +type makeFuncImpl struct { + makeFuncCtxt + ftyp *funcType + fn func([]Value) []Value +} + +// MakeFunc returns a new function of the given Type +// that wraps the function fn. When called, that new function +// does the following: +// +// - converts its arguments to a slice of Values. +// - runs results := fn(args). +// - returns the results as a slice of Values, one per formal result. +// +// The implementation fn can assume that the argument Value slice +// has the number and type of arguments given by typ. +// If typ describes a variadic function, the final Value is itself +// a slice representing the variadic arguments, as in the +// body of a variadic function. The result Value slice returned by fn +// must have the number and type of results given by typ. +// +// The Value.Call method allows the caller to invoke a typed function +// in terms of Values; in contrast, MakeFunc allows the caller to implement +// a typed function in terms of Values. +// +// The Examples section of the documentation includes an illustration +// of how to use MakeFunc to build a swap function for different types. +func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { + if typ.Kind() != Func { + panic("reflect: call of MakeFunc with non-Func type") + } + + t := typ.common() + ftyp := (*funcType)(unsafe.Pointer(t)) + + code := abi.FuncPCABI0(makeFuncStub) + + // makeFuncImpl contains a stack map for use by the runtime + _, _, abid := funcLayout(ftyp, nil) + + impl := &makeFuncImpl{ + makeFuncCtxt: makeFuncCtxt{ + fn: code, + stack: abid.stackPtrs, + argLen: abid.stackCallArgsSize, + regPtrs: abid.inRegPtrs, + }, + ftyp: ftyp, + fn: fn, + } + + return Value{t, unsafe.Pointer(impl), flag(Func)} +} + +// makeFuncStub is an assembly function that is the code half of +// the function returned from MakeFunc. It expects a *callReflectFunc +// as its context register, and its job is to invoke callReflect(ctxt, frame) +// where ctxt is the context register and frame is a pointer to the first +// word in the passed-in argument frame. +func makeFuncStub() + +// The first 3 words of this type must be kept in sync with +// makeFuncImpl and runtime.reflectMethodValue. +// Any changes should be reflected in all three. +type methodValue struct { + makeFuncCtxt + method int + rcvr Value +} +*/ + +// makeMethodValue converts v from the rcvr+method index representation +// of a method value to an actual method func value, which is +// basically the receiver value with a special bit set, into a true +// func value - a value holding an actual func. The output is +// semantically equivalent to the input as far as the user of package +// reflect can tell, but the true func representation can be handled +// by code like Convert and Interface and Assign. +func makeMethodValue(op string, v Value) Value { + /* + if v.flag&flagMethod == 0 { + panic("reflect: internal error: invalid use of makeMethodValue") + } + + // Ignoring the flagMethod bit, v describes the receiver, not the method type. + fl := v.flag & (flagRO | flagAddr | flagIndir) + fl |= flag(v.typ().Kind()) + rcvr := Value{v.typ(), v.ptr, fl} + + // v.Type returns the actual type of the method value. + ftyp := (*funcType)(unsafe.Pointer(v.Type().(*rtype))) + + code := methodValueCallCodePtr() + + // methodValue contains a stack map for use by the runtime + _, _, abid := funcLayout(ftyp, nil) + fv := &methodValue{ + makeFuncCtxt: makeFuncCtxt{ + fn: code, + stack: abid.stackPtrs, + argLen: abid.stackCallArgsSize, + regPtrs: abid.inRegPtrs, + }, + method: int(v.flag) >> flagMethodShift, + rcvr: rcvr, + } + + // Cause panic if method is not appropriate. + // The panic would still happen during the call if we omit this, + // but we want Interface() and other operations to fail early. + methodReceiver(op, fv.rcvr, fv.method) + + return Value{ftyp.Common(), unsafe.Pointer(fv), v.flag&flagRO | flag(Func)} + */ + panic("todo") +} + +/* +func methodValueCallCodePtr() uintptr { + return abi.FuncPCABI0(methodValueCall) +} + +// methodValueCall is an assembly function that is the code half of +// the function returned from makeMethodValue. It expects a *methodValue +// as its context register, and its job is to invoke callMethod(ctxt, frame) +// where ctxt is the context register and frame is a pointer to the first +// word in the passed-in argument frame. +func methodValueCall() + +// This structure must be kept in sync with runtime.reflectMethodValue. +// Any changes should be reflected in all both. +type makeFuncCtxt struct { + fn uintptr + stack *bitVector // ptrmap for both stack args and results + argLen uintptr // just args + regPtrs abi.IntArgRegBitmap +} + +// moveMakeFuncArgPtrs uses ctxt.regPtrs to copy integer pointer arguments +// in args.Ints to args.Ptrs where the GC can see them. +// +// This is similar to what reflectcallmove does in the runtime, except +// that happens on the return path, whereas this happens on the call path. +// +// nosplit because pointers are being held in uintptr slots in args, so +// having our stack scanned now could lead to accidentally freeing +// memory. +// +//go:nosplit +func moveMakeFuncArgPtrs(ctxt *makeFuncCtxt, args *abi.RegArgs) { + for i, arg := range args.Ints { + // Avoid write barriers! Because our write barrier enqueues what + // was there before, we might enqueue garbage. + if ctxt.regPtrs.Get(i) { + *(*uintptr)(unsafe.Pointer(&args.Ptrs[i])) = arg + } else { + // We *must* zero this space ourselves because it's defined in + // assembly code and the GC will scan these pointers. Otherwise, + // there will be garbage here. + *(*uintptr)(unsafe.Pointer(&args.Ptrs[i])) = 0 + } + } +} +*/ diff --git a/internal/lib/reflect/type.go b/internal/lib/reflect/type.go index 0a5a6e715..8dfce54a1 100644 --- a/internal/lib/reflect/type.go +++ b/internal/lib/reflect/type.go @@ -726,6 +726,17 @@ func (t *rtype) IsVariadic() bool { panic("todo") } +// add returns p+x. +// +// The whySafe string is ignored, so that the function still inlines +// as efficiently as p+x, but all call sites should use the string to +// record why the addition is safe, which is to say why the addition +// does not cause x to advance to the very end of p's allocation +// and therefore point incorrectly at the next block in memory. +func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer { + return unsafe.Pointer(uintptr(p) + x) +} + // A StructField describes a single field in a struct. type StructField struct { // Name is the field name. @@ -837,13 +848,13 @@ func TypeOf(i any) Type { return toType((*abi.Type)(unsafe.Pointer(eface.typ))) } -/* TODO(xsw): // rtypeOf directly extracts the *rtype of the provided value. func rtypeOf(i any) *abi.Type { eface := *(*emptyInterface)(unsafe.Pointer(&i)) return eface.typ } +/* TODO(xsw): // ptrMap is the cache for PointerTo. var ptrMap sync.Map // map[*rtype]*ptrType */ @@ -959,6 +970,241 @@ func (t *rtype) Comparable() bool { return t.t.Equal != nil } +// implements reports whether the type V implements the interface type T. +func implements(T, V *abi.Type) bool { + if T.Kind() != abi.Interface { + return false + } + t := (*interfaceType)(unsafe.Pointer(T)) + if len(t.Methods) == 0 { + return true + } + + /* + // The same algorithm applies in both cases, but the + // method tables for an interface type and a concrete type + // are different, so the code is duplicated. + // In both cases the algorithm is a linear scan over the two + // lists - T's methods and V's methods - simultaneously. + // Since method tables are stored in a unique sorted order + // (alphabetical, with no duplicate method names), the scan + // through V's methods must hit a match for each of T's + // methods along the way, or else V does not implement T. + // This lets us run the scan in overall linear time instead of + // the quadratic time a naive search would require. + // See also ../runtime/iface.go. + if V.Kind() == abi.Interface { + v := (*interfaceType)(unsafe.Pointer(V)) + i := 0 + for j := 0; j < len(v.Methods); j++ { + tm := &t.Methods[i] + tmName := t.nameOff(tm.Name) + vm := &v.Methods[j] + vmName := nameOffFor(V, vm.Name) + if vmName.Name() == tmName.Name() && typeOffFor(V, vm.Typ) == t.typeOff(tm.Typ) { + if !tmName.IsExported() { + tmPkgPath := pkgPath(tmName) + if tmPkgPath == "" { + tmPkgPath = t.PkgPath.Name() + } + vmPkgPath := pkgPath(vmName) + if vmPkgPath == "" { + vmPkgPath = v.PkgPath.Name() + } + if tmPkgPath != vmPkgPath { + continue + } + } + if i++; i >= len(t.Methods) { + return true + } + } + } + return false + } + + v := V.Uncommon() + if v == nil { + return false + } + i := 0 + vmethods := v.Methods() + for j := 0; j < int(v.Mcount); j++ { + tm := &t.Methods[i] + tmName := t.nameOff(tm.Name) + vm := vmethods[j] + vmName := nameOffFor(V, vm.Name) + if vmName.Name() == tmName.Name() && typeOffFor(V, vm.Mtyp) == t.typeOff(tm.Typ) { + if !tmName.IsExported() { + tmPkgPath := pkgPath(tmName) + if tmPkgPath == "" { + tmPkgPath = t.PkgPath.Name() + } + vmPkgPath := pkgPath(vmName) + if vmPkgPath == "" { + vmPkgPath = nameOffFor(V, v.PkgPath).Name() + } + if tmPkgPath != vmPkgPath { + continue + } + } + if i++; i >= len(t.Methods) { + return true + } + } + } + return false + */ + panic("todo") +} + +// specialChannelAssignability reports whether a value x of channel type V +// can be directly assigned (using memmove) to another channel type T. +// https://golang.org/doc/go_spec.html#Assignability +// T and V must be both of Chan kind. +func specialChannelAssignability(T, V *abi.Type) bool { + /* + // Special case: + // x is a bidirectional channel value, T is a channel type, + // x's type V and T have identical element types, + // and at least one of V or T is not a defined type. + return V.ChanDir() == abi.BothDir && (nameFor(T) == "" || nameFor(V) == "") && haveIdenticalType(T.Elem(), V.Elem(), true) + */ + panic("todo") +} + +// directlyAssignable reports whether a value x of type V can be directly +// assigned (using memmove) to a value of type T. +// https://golang.org/doc/go_spec.html#Assignability +// Ignoring the interface rules (implemented elsewhere) +// and the ideal constant rules (no ideal constants at run time). +func directlyAssignable(T, V *abi.Type) bool { + // x's type V is identical to T? + if T == V { + return true + } + + // Otherwise at least one of T and V must not be defined + // and they must have the same kind. + if T.HasName() && V.HasName() || T.Kind() != V.Kind() { + return false + } + + if T.Kind() == abi.Chan && specialChannelAssignability(T, V) { + return true + } + + // x's type T and V must have identical underlying types. + return haveIdenticalUnderlyingType(T, V, true) +} + +func haveIdenticalType(T, V *abi.Type, cmpTags bool) bool { + if cmpTags { + return T == V + } + + if nameFor(T) != nameFor(V) || T.Kind() != V.Kind() || pkgPathFor(T) != pkgPathFor(V) { + return false + } + + return haveIdenticalUnderlyingType(T, V, false) +} + +func haveIdenticalUnderlyingType(T, V *abi.Type, cmpTags bool) bool { + if T == V { + return true + } + + kind := Kind(T.Kind()) + if kind != Kind(V.Kind()) { + return false + } + + // Non-composite types of equal kind have same underlying type + // (the predefined instance of the type). + if Bool <= kind && kind <= Complex128 || kind == String || kind == UnsafePointer { + return true + } + + /* + // Composite types. + switch kind { + case Array: + return T.Len() == V.Len() && haveIdenticalType(T.Elem(), V.Elem(), cmpTags) + + case Chan: + return V.ChanDir() == T.ChanDir() && haveIdenticalType(T.Elem(), V.Elem(), cmpTags) + + case Func: + t := (*funcType)(unsafe.Pointer(T)) + v := (*funcType)(unsafe.Pointer(V)) + if t.OutCount != v.OutCount || t.InCount != v.InCount { + return false + } + for i := 0; i < t.NumIn(); i++ { + if !haveIdenticalType(t.In(i), v.In(i), cmpTags) { + return false + } + } + for i := 0; i < t.NumOut(); i++ { + if !haveIdenticalType(t.Out(i), v.Out(i), cmpTags) { + return false + } + } + return true + + case Interface: + t := (*interfaceType)(unsafe.Pointer(T)) + v := (*interfaceType)(unsafe.Pointer(V)) + if len(t.Methods) == 0 && len(v.Methods) == 0 { + return true + } + // Might have the same methods but still + // need a run time conversion. + return false + + case Map: + return haveIdenticalType(T.Key(), V.Key(), cmpTags) && haveIdenticalType(T.Elem(), V.Elem(), cmpTags) + + case Pointer, Slice: + return haveIdenticalType(T.Elem(), V.Elem(), cmpTags) + + case Struct: + t := (*structType)(unsafe.Pointer(T)) + v := (*structType)(unsafe.Pointer(V)) + if len(t.Fields) != len(v.Fields) { + return false + } + if t.PkgPath.Name() != v.PkgPath.Name() { + return false + } + for i := range t.Fields { + tf := &t.Fields[i] + vf := &v.Fields[i] + if tf.Name.Name() != vf.Name.Name() { + return false + } + if !haveIdenticalType(tf.Typ, vf.Typ, cmpTags) { + return false + } + if cmpTags && tf.Name.Tag() != vf.Name.Tag() { + return false + } + if tf.Offset != vf.Offset { + return false + } + if tf.Embedded() != vf.Embedded() { + return false + } + } + return true + } + + return false + */ + panic("todo") +} + // SliceOf returns the slice type with element type t. // For example, if t represents int, SliceOf(t) represents []int. func SliceOf(t Type) Type { diff --git a/internal/lib/reflect/unsafeheader.go b/internal/lib/reflect/unsafeheader.go index 15da82ea1..0ff3cb906 100644 --- a/internal/lib/reflect/unsafeheader.go +++ b/internal/lib/reflect/unsafeheader.go @@ -2,11 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package unsafeheader contains header declarations for the Go runtime's slice -// and string implementations. -// -// This package allows packages that cannot import "reflect" to use types that -// are tested to be equivalent to reflect.SliceHeader and reflect.StringHeader. package reflect import ( diff --git a/internal/lib/reflect/value.go b/internal/lib/reflect/value.go index f2add569d..1306ee20b 100644 --- a/internal/lib/reflect/value.go +++ b/internal/lib/reflect/value.go @@ -193,6 +193,28 @@ func (e *ValueError) Error() string { return "reflect: call of " + e.Method + " on " + e.Kind.String() + " Value" } +// valueMethodName returns the name of the exported calling method on Value. +func valueMethodName() string { + /* TODO(xsw): + var pc [5]uintptr + n := runtime.Callers(1, pc[:]) + frames := runtime.CallersFrames(pc[:n]) + var frame runtime.Frame + for more := true; more; { + const prefix = "reflect.Value." + frame, more = frames.Next() + name := frame.Function + if len(name) > len(prefix) && name[:len(prefix)] == prefix { + methodName := name[len(prefix):] + if len(methodName) > 0 && 'A' <= methodName[0] && methodName[0] <= 'Z' { + return name + } + } + } + */ + return "unknown method" +} + // emptyInterface is the header for an interface{} value. type emptyInterface struct { typ *abi.Type @@ -212,6 +234,125 @@ type nonEmptyInterface struct { word unsafe.Pointer } +// mustBe panics if f's kind is not expected. +// Making this a method on flag instead of on Value +// (and embedding flag in Value) means that we can write +// the very clear v.mustBe(Bool) and have it compile into +// v.flag.mustBe(Bool), which will only bother to copy the +// single important word for the receiver. +func (f flag) mustBe(expected Kind) { + // TODO(mvdan): use f.kind() again once mid-stack inlining gets better + if Kind(f&flagKindMask) != expected { + panic(&ValueError{valueMethodName(), f.kind()}) + } +} + +// mustBeExported panics if f records that the value was obtained using +// an unexported field. +func (f flag) mustBeExported() { + if f == 0 || f&flagRO != 0 { + f.mustBeExportedSlow() + } +} + +func (f flag) mustBeExportedSlow() { + if f == 0 { + panic(&ValueError{valueMethodName(), Invalid}) + } + if f&flagRO != 0 { + panic("reflect: " + valueMethodName() + " using value obtained using unexported field") + } +} + +// mustBeAssignable panics if f records that the value is not assignable, +// which is to say that either it was obtained using an unexported field +// or it is not addressable. +func (f flag) mustBeAssignable() { + if f&flagRO != 0 || f&flagAddr == 0 { + f.mustBeAssignableSlow() + } +} + +func (f flag) mustBeAssignableSlow() { + if f == 0 { + panic(&ValueError{valueMethodName(), Invalid}) + } + // Assignable if addressable and not read-only. + if f&flagRO != 0 { + panic("reflect: " + valueMethodName() + " using value obtained using unexported field") + } + if f&flagAddr == 0 { + panic("reflect: " + valueMethodName() + " using unaddressable value") + } +} + +// Addr returns a pointer value representing the address of v. +// It panics if CanAddr() returns false. +// Addr is typically used to obtain a pointer to a struct field +// or slice element in order to call a method that requires a +// pointer receiver. +func (v Value) Addr() Value { + if v.flag&flagAddr == 0 { + panic("reflect.Value.Addr of unaddressable value") + } + // Preserve flagRO instead of using v.flag.ro() so that + // v.Addr().Elem() is equivalent to v (#32772) + fl := v.flag & flagRO + return Value{ptrTo(v.typ()), v.ptr, fl | flag(Pointer)} +} + +// Bool returns v's underlying value. +// It panics if v's kind is not Bool. +func (v Value) Bool() bool { + // panicNotBool is split out to keep Bool inlineable. + if v.kind() != Bool { + v.panicNotBool() + } + return *(*bool)(v.ptr) +} + +func (v Value) panicNotBool() { + v.mustBe(Bool) +} + +var bytesType = rtypeOf(([]byte)(nil)) + +// Bytes returns v's underlying value. +// It panics if v's underlying value is not a slice of bytes or +// an addressable array of bytes. +func (v Value) Bytes() []byte { + // bytesSlow is split out to keep Bytes inlineable for unnamed []byte. + if v.typ_ == bytesType { // ok to use v.typ_ directly as comparison doesn't cause escape + return *(*[]byte)(v.ptr) + } + return v.bytesSlow() +} + +func (v Value) bytesSlow() []byte { + /* + switch v.kind() { + case Slice: + if v.typ().Elem().Kind() != abi.Uint8 { + panic("reflect.Value.Bytes of non-byte slice") + } + // Slice is always bigger than a word; assume flagIndir. + return *(*[]byte)(v.ptr) + case Array: + if v.typ().Elem().Kind() != abi.Uint8 { + panic("reflect.Value.Bytes of non-byte array") + } + if !v.CanAddr() { + panic("reflect.Value.Bytes of unaddressable byte array") + } + p := (*byte)(v.ptr) + n := int((*arrayType)(unsafe.Pointer(v.typ())).Len) + return unsafe.Slice(p, n) + } + panic(&ValueError{"reflect.Value.Bytes", v.kind()}) + */ + panic("todo") +} + // CanFloat reports whether Float can be used without panicking. func (v Value) CanFloat() bool { switch v.kind() { @@ -235,56 +376,52 @@ func (v Value) Float() float64 { panic(&ValueError{"reflect.Value.Float", v.kind()}) } -// TODO(xsw): -// var uint8Type = rtypeOf(uint8(0)) +var uint8Type = rtypeOf(uint8(0)) // Index returns v's i'th element. // It panics if v's Kind is not Array, Slice, or String or i is out of range. func (v Value) Index(i int) Value { - /* - switch v.kind() { - case Array: - tt := (*arrayType)(unsafe.Pointer(v.typ())) - if uint(i) >= uint(tt.Len) { - panic("reflect: array index out of range") - } - typ := tt.Elem - offset := uintptr(i) * typ.Size() - - // Either flagIndir is set and v.ptr points at array, - // or flagIndir is not set and v.ptr is the actual array data. - // In the former case, we want v.ptr + offset. - // In the latter case, we must be doing Index(0), so offset = 0, - // so v.ptr + offset is still the correct address. - val := add(v.ptr, offset, "same as &v[i], i < tt.len") - fl := v.flag&(flagIndir|flagAddr) | v.flag.ro() | flag(typ.Kind()) // bits same as overall array - return Value{typ, val, fl} + switch v.kind() { + case Slice: + // Element flag same as Elem of Pointer. + // Addressable, indirect, possibly read-only. + s := (*unsafeheaderSlice)(v.ptr) + if uint(i) >= uint(s.Len) { + panic("reflect: slice index out of range") + } + tt := (*sliceType)(unsafe.Pointer(v.typ())) + typ := tt.Elem + val := arrayAt(s.Data, i, typ.Size(), "i < s.Len") + fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind()) + return Value{typ, val, fl} - case Slice: - // Element flag same as Elem of Pointer. - // Addressable, indirect, possibly read-only. - s := (*unsafeheader.Slice)(v.ptr) - if uint(i) >= uint(s.Len) { - panic("reflect: slice index out of range") - } - tt := (*sliceType)(unsafe.Pointer(v.typ())) - typ := tt.Elem - val := arrayAt(s.Data, i, typ.Size(), "i < s.Len") - fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind()) - return Value{typ, val, fl} + case String: + s := (*unsafeheaderString)(v.ptr) + if uint(i) >= uint(s.Len) { + panic("reflect: string index out of range") + } + p := arrayAt(s.Data, i, 1, "i < s.Len") + fl := v.flag.ro() | flag(Uint8) | flagIndir + return Value{uint8Type, p, fl} - case String: - s := (*unsafeheader.String)(v.ptr) - if uint(i) >= uint(s.Len) { - panic("reflect: string index out of range") - } - p := arrayAt(s.Data, i, 1, "i < s.Len") - fl := v.flag.ro() | flag(Uint8) | flagIndir - return Value{uint8Type, p, fl} + case Array: + tt := (*arrayType)(unsafe.Pointer(v.typ())) + if uint(i) >= uint(tt.Len) { + panic("reflect: array index out of range") } - panic(&ValueError{"reflect.Value.Index", v.kind()}) - */ - panic("todo") + typ := tt.Elem + offset := uintptr(i) * typ.Size() + + // Either flagIndir is set and v.ptr points at array, + // or flagIndir is not set and v.ptr is the actual array data. + // In the former case, we want v.ptr + offset. + // In the latter case, we must be doing Index(0), so offset = 0, + // so v.ptr + offset is still the correct address. + val := add(v.ptr, offset, "same as &v[i], i < tt.len") + fl := v.flag&(flagIndir|flagAddr) | v.flag.ro() | flag(typ.Kind()) // bits same as overall array + return Value{typ, val, fl} + } + panic(&ValueError{"reflect.Value.Index", v.kind()}) } // CanInt reports whether Int can be used without panicking. @@ -300,22 +437,33 @@ func (v Value) CanInt() bool { // Int returns v's underlying value, as an int64. // It panics if v's Kind is not Int, Int8, Int16, Int32, or Int64. func (v Value) Int() int64 { - k := v.kind() + f := v.flag + k := f.kind() p := v.ptr - switch k { - case Int: - return int64(uintptr(p)) - case Int8: - return int64(uintptr(p)) - case Int16: - return int64(uintptr(p)) - case Int32: - return int64(uintptr(p)) - case Int64: - if unsafe.Sizeof(uintptr(0)) == 8 { + if f&flagAddr != 0 { + switch k { + case Int: + return int64(*(*int)(p)) + case Int8: + return int64(*(*int8)(p)) + case Int16: + return int64(*(*int16)(p)) + case Int32: + return int64(*(*int32)(p)) + case Int64: + return *(*int64)(p) + } + } else if unsafe.Sizeof(uintptr(0)) == 8 { + if k >= Int && k <= Int64 { return int64(uintptr(p)) } - return *(*int64)(p) + } else { + if k >= Int && k <= Int32 { + return int64(uintptr(p)) + } + if k == Int64 { + return *(*int64)(p) + } } panic(&ValueError{"reflect.Value.Int", v.kind()}) } @@ -594,6 +742,107 @@ func (v Value) lenNonSlice() int { panic("todo") } +// Set assigns x to the value v. +// It panics if CanSet returns false. +// As in Go, x's value must be assignable to v's type and +// must not be derived from an unexported field. +func (v Value) Set(x Value) { + v.mustBeAssignable() + x.mustBeExported() // do not let unexported x leak + var target unsafe.Pointer + if v.kind() == Interface { + target = v.ptr + } + x = x.assignTo("reflect.Set", v.typ(), target) + if x.flag&flagIndir != 0 { + if x.ptr == unsafe.Pointer(&runtime.ZeroVal[0]) { + typedmemclr(v.typ(), v.ptr) + } else { + typedmemmove(v.typ(), v.ptr, x.ptr) + } + } else { + *(*unsafe.Pointer)(v.ptr) = x.ptr + } +} + +// SetBool sets v's underlying value. +// It panics if v's Kind is not Bool or if CanSet() is false. +func (v Value) SetBool(x bool) { + v.mustBeAssignable() + v.mustBe(Bool) + *(*bool)(v.ptr) = x +} + +// SetBytes sets v's underlying value. +// It panics if v's underlying value is not a slice of bytes. +func (v Value) SetBytes(x []byte) { + v.mustBeAssignable() + v.mustBe(Slice) + if toRType(v.typ()).Elem().Kind() != Uint8 { // TODO add Elem method, fix mustBe(Slice) to return slice. + panic("reflect.Value.SetBytes of non-byte slice") + } + *(*[]byte)(v.ptr) = x +} + +// setRunes sets v's underlying value. +// It panics if v's underlying value is not a slice of runes (int32s). +func (v Value) setRunes(x []rune) { + v.mustBeAssignable() + v.mustBe(Slice) + if v.typ().Elem().Kind() != abi.Int32 { + panic("reflect.Value.setRunes of non-rune slice") + } + *(*[]rune)(v.ptr) = x +} + +// SetComplex sets v's underlying value to x. +// It panics if v's Kind is not Complex64 or Complex128, or if CanSet() is false. +func (v Value) SetComplex(x complex128) { + v.mustBeAssignable() + switch k := v.kind(); k { + default: + panic(&ValueError{"reflect.Value.SetComplex", v.kind()}) + case Complex64: + *(*complex64)(v.ptr) = complex64(x) + case Complex128: + *(*complex128)(v.ptr) = x + } +} + +// SetFloat sets v's underlying value to x. +// It panics if v's Kind is not Float32 or Float64, or if CanSet() is false. +func (v Value) SetFloat(x float64) { + v.mustBeAssignable() + switch k := v.kind(); k { + default: + panic(&ValueError{"reflect.Value.SetFloat", v.kind()}) + case Float32: + *(*float32)(v.ptr) = float32(x) + case Float64: + *(*float64)(v.ptr) = x + } +} + +// SetInt sets v's underlying value to x. +// It panics if v's Kind is not Int, Int8, Int16, Int32, or Int64, or if CanSet() is false. +func (v Value) SetInt(x int64) { + v.mustBeAssignable() + switch k := v.kind(); k { + default: + panic(&ValueError{"reflect.Value.SetInt", v.kind()}) + case Int: + *(*int)(v.ptr) = int(x) + case Int8: + *(*int8)(v.ptr) = int8(x) + case Int16: + *(*int16)(v.ptr) = int16(x) + case Int32: + *(*int32)(v.ptr) = int32(x) + case Int64: + *(*int64)(v.ptr) = x + } +} + //go:linkname unsafe_New github.com/goplus/llgo/internal/runtime.New func unsafe_New(*abi.Type) unsafe.Pointer @@ -610,6 +859,94 @@ func ValueOf(i any) Value { return unpackEface(i) } +// arrayAt returns the i-th element of p, +// an array whose elements are eltSize bytes wide. +// The array pointed at by p must have at least i+1 elements: +// it is invalid (but impossible to check here) to pass i >= len, +// because then the result will point outside the array. +// whySafe must explain why i < len. (Passing "i < len" is fine; +// the benefit is to surface this assumption at the call site.) +func arrayAt(p unsafe.Pointer, i int, eltSize uintptr, whySafe string) unsafe.Pointer { + return add(p, uintptr(i)*eltSize, "i < len") +} + +// Grow increases the slice's capacity, if necessary, to guarantee space for +// another n elements. After Grow(n), at least n elements can be appended +// to the slice without another allocation. +// +// It panics if v's Kind is not a Slice or if n is negative or too large to +// allocate the memory. +func (v Value) Grow(n int) { + v.mustBeAssignable() + v.mustBe(Slice) + v.grow(n) +} + +// grow is identical to Grow but does not check for assignability. +func (v Value) grow(n int) { + p := (*unsafeheaderSlice)(v.ptr) + oldLen := p.Len + switch { + case n < 0: + panic("reflect.Value.Grow: negative len") + case oldLen+n < 0: + panic("reflect.Value.Grow: slice overflow") + case oldLen+n > p.Cap: + t := v.typ().Elem() + *p = growslice(*p, n, int(t.Size_)) + p.Len = oldLen // set oldLen back + } +} + +// extendSlice extends a slice by n elements. +// +// Unlike Value.grow, which modifies the slice in place and +// does not change the length of the slice in place, +// extendSlice returns a new slice value with the length +// incremented by the number of specified elements. +func (v Value) extendSlice(n int) Value { + v.mustBeExported() + v.mustBe(Slice) + + // Shallow copy the slice header to avoid mutating the source slice. + sh := *(*unsafeheaderSlice)(v.ptr) + s := &sh + v.ptr = unsafe.Pointer(s) + v.flag = flagIndir | flag(Slice) // equivalent flag to MakeSlice + + v.grow(n) // fine to treat as assignable since we allocate a new slice header + s.Len += n + return v +} + +// Append appends the values x to a slice s and returns the resulting slice. +// As in Go, each x's value must be assignable to the slice's element type. +func Append(s Value, x ...Value) Value { + s.mustBe(Slice) + n := s.Len() + s = s.extendSlice(len(x)) + for i, v := range x { + s.Index(n + i).Set(v) + } + return s +} + +// AppendSlice appends a slice t to a slice s and returns the resulting slice. +// The slices s and t must have the same element type. +func AppendSlice(s, t Value) Value { + /* + s.mustBe(Slice) + t.mustBe(Slice) + typesMustMatch("reflect.AppendSlice", s.Type().Elem(), t.Type().Elem()) + ns := s.Len() + nt := t.Len() + s = s.extendSlice(nt) + Copy(s.Slice(ns, ns+nt), t) + return s + */ + panic("todo") +} + // Zero returns a Value representing the zero value for the specified type. // The result is different from the zero value of the Value struct, // which represents no value at all. @@ -637,6 +974,79 @@ func Zero(typ Type) Value { // must match declarations in runtime/map.go. const maxZero = runtime.MaxZero +// New returns a Value representing a pointer to a new zero value +// for the specified type. That is, the returned Value's Type is PointerTo(typ). +func New(typ Type) Value { + /* + if typ == nil { + panic("reflect: New(nil)") + } + t := &typ.(*rtype).t + pt := ptrTo(t) + if ifaceIndir(pt) { + // This is a pointer to a not-in-heap type. + panic("reflect: New of type that may not be allocated in heap (possibly undefined cgo C type)") + } + ptr := unsafe_New(t) + fl := flag(Pointer) + return Value{pt, ptr, fl} + */ + panic("todo") +} + +// NewAt returns a Value representing a pointer to a value of the +// specified type, using p as that pointer. +func NewAt(typ Type, p unsafe.Pointer) Value { + fl := flag(Pointer) + t := typ.(*rtype) + return Value{t.ptrTo(), p, fl} +} + +// assignTo returns a value v that can be assigned directly to dst. +// It panics if v is not assignable to dst. +// For a conversion to an interface type, target, if not nil, +// is a suggested scratch space to use. +// target must be initialized memory (or nil). +func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Value { + if v.flag&flagMethod != 0 { + v = makeMethodValue(context, v) + } + + switch { + case directlyAssignable(dst, v.typ()): + // Overwrite type so that they match. + // Same memory layout, so no harm done. + fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() + fl |= flag(dst.Kind()) + return Value{dst, v.ptr, fl} + + case implements(dst, v.typ()): + if v.Kind() == Interface && v.IsNil() { + // A nil ReadWriter passed to nil Reader is OK, + // but using ifaceE2I below will panic. + // Avoid the panic by returning a nil dst (e.g., Reader) explicitly. + return Value{dst, nil, flag(Interface)} + } + /* TODO(xsw): + x := valueInterface(v, false) + if target == nil { + target = unsafe_New(dst) + } + if dst.NumMethod() == 0 { + *(*any)(target) = x + } else { + ifaceE2I(dst, x, target) + } + return Value{dst, target, flagIndir | flag(Interface)} + */ + } + + // Failed. + // TODO(xsw): + // panic(context + ": value of type " + stringFor(v.typ()) + " is not assignable to type " + stringFor(dst)) + panic("todo") +} + // memmove copies size bytes to dst from src. No write barriers are used. // //go:linkname memmove C.memmove @@ -647,12 +1057,12 @@ func memmove(dst, src unsafe.Pointer, size uintptr) //go:linkname typedmemmove github.com/goplus/llgo/internal/runtime.Typedmemmove func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) -/* TODO(xsw): // typedmemclr zeros the value at ptr of type t. // -//go:noescape +//go:linkname typedmemclr github.com/goplus/llgo/internal/runtime.Typedmemclr func typedmemclr(t *abi.Type, ptr unsafe.Pointer) +/* TODO(xsw): // typedmemclrpartial is like typedmemclr but assumes that // dst points off bytes into the value and only clears size bytes. // @@ -675,7 +1085,7 @@ func typedarrayclear(elemType *abi.Type, ptr unsafe.Pointer, len int) func typehash(t *abi.Type, p unsafe.Pointer, h uintptr) uintptr func verifyNotInHeapPtr(p uintptr) bool - -//go:noescape -func growslice(t *abi.Type, old unsafeheaderSlice, num int) unsafeheaderSlice */ + +//go:linkname growslice github.com/goplus/llgo/internal/runtime.GrowSlice +func growslice(src unsafeheaderSlice, num, etSize int) unsafeheaderSlice diff --git a/internal/runtime/mbarrier.go b/internal/runtime/mbarrier.go index 765970c16..103438d4a 100644 --- a/internal/runtime/mbarrier.go +++ b/internal/runtime/mbarrier.go @@ -286,6 +286,7 @@ func reflect_typedslicecopy(elemType *_type, dst, src slice) int { } return typedslicecopy(elemType, dst.array, dst.len, src.array, src.len) } +*/ // typedmemclr clears the typed memory at ptr with type typ. The // memory at ptr must already be initialized (and hence in type-safe @@ -296,20 +297,11 @@ func reflect_typedslicecopy(elemType *_type, dst, src slice) int { // call memclrHasPointers. // // TODO: A "go:nosplitrec" annotation would be perfect for this. -// -//go:nosplit -func typedmemclr(typ *_type, ptr unsafe.Pointer) { - if writeBarrier.needed && typ.PtrBytes != 0 { - bulkBarrierPreWrite(uintptr(ptr), 0, typ.PtrBytes) - } - memclrNoHeapPointers(ptr, typ.Size_) -} - -//go:linkname reflect_typedmemclr reflect.typedmemclr -func reflect_typedmemclr(typ *_type, ptr unsafe.Pointer) { - typedmemclr(typ, ptr) +func Typedmemclr(typ *Type, ptr unsafe.Pointer) { + c.Memset(ptr, 0, typ.Size_) } +/* //go:linkname reflect_typedmemclrpartial reflect.typedmemclrpartial func reflect_typedmemclrpartial(typ *_type, ptr unsafe.Pointer, off, size uintptr) { if writeBarrier.needed && typ.PtrBytes != 0 { diff --git a/internal/runtime/slice.go b/internal/runtime/slice.go deleted file mode 100644 index d692a7106..000000000 --- a/internal/runtime/slice.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package runtime - -// nextslicecap computes the next appropriate slice length. -func nextslicecap(newLen, oldCap int) int { - newcap := oldCap - doublecap := newcap + newcap - if newLen > doublecap { - return newLen - } - - const threshold = 256 - if oldCap < threshold { - return doublecap - } - for { - // Transition from growing 2x for small slices - // to growing 1.25x for large slices. This formula - // gives a smooth-ish transition between the two. - newcap += (newcap + 3*threshold) >> 2 - - // We need to check `newcap >= newLen` and whether `newcap` overflowed. - // newLen is guaranteed to be larger than zero, hence - // when newcap overflows then `uint(newcap) > uint(newLen)`. - // This allows to check for both with the same comparison. - if uint(newcap) >= uint(newLen) { - break - } - } - - // Set newcap to the requested cap when - // the newcap calculation overflowed. - if newcap <= 0 { - return newLen - } - return newcap -} diff --git a/internal/runtime/z_slice.go b/internal/runtime/z_slice.go index f29348227..3c31710fd 100644 --- a/internal/runtime/z_slice.go +++ b/internal/runtime/z_slice.go @@ -51,7 +51,15 @@ func SliceAppend(src Slice, data unsafe.Pointer, num, etSize int) Slice { return src } oldLen := src.len - newLen := src.len + num + src = GrowSlice(src, num, etSize) + c.Memcpy(c.Advance(src.data, oldLen*etSize), data, uintptr(num*etSize)) + return src +} + +// GrowSlice grows slice and returns the grown slice. +func GrowSlice(src Slice, num, etSize int) Slice { + oldLen := src.len + newLen := oldLen + num if newLen > src.cap { newCap := nextslicecap(newLen, src.cap) p := AllocZ(uintptr(newCap * etSize)) @@ -62,10 +70,44 @@ func SliceAppend(src Slice, data unsafe.Pointer, num, etSize int) Slice { src.cap = newCap } src.len = newLen - c.Memcpy(c.Advance(src.data, oldLen*etSize), data, uintptr(num*etSize)) return src } +// nextslicecap computes the next appropriate slice length. +func nextslicecap(newLen, oldCap int) int { + newcap := oldCap + doublecap := newcap + newcap + if newLen > doublecap { + return newLen + } + + const threshold = 256 + if oldCap < threshold { + return doublecap + } + for { + // Transition from growing 2x for small slices + // to growing 1.25x for large slices. This formula + // gives a smooth-ish transition between the two. + newcap += (newcap + 3*threshold) >> 2 + + // We need to check `newcap >= newLen` and whether `newcap` overflowed. + // newLen is guaranteed to be larger than zero, hence + // when newcap overflows then `uint(newcap) > uint(newLen)`. + // This allows to check for both with the same comparison. + if uint(newcap) >= uint(newLen) { + break + } + } + + // Set newcap to the requested cap when + // the newcap calculation overflowed. + if newcap <= 0 { + return newLen + } + return newcap +} + // SliceCopy copy data to slice and returns a slice. func SliceCopy(dst Slice, data unsafe.Pointer, num int, etSize int) int { n := dst.len