Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions gnovm/pkg/gnolang/op_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,12 @@ func (m *Machine) doOpExec(op Op) {
m.pushPanic(typedString("runtime error: nil pointer dereference"))
return
}
iv := TypedValue{T: IntType}
iv.SetInt(int64(bs.ListIndex))
ev := xv.GetPointerAtIndex(m, m.Realm, m.Alloc, m.Store, &iv).Deref()
ev, ok := xv.GetValueAtIntIndex(m.Store, bs.ListIndex)
if !ok {
iv := TypedValue{T: IntType}
iv.SetInt(int64(bs.ListIndex))
ev = xv.GetPointerAtIndex(m, m.Realm, m.Alloc, m.Store, &iv).Deref()
}
switch bs.Op {
case ASSIGN:
m.PopAsPointer(bs.Value).Assign2(m, m.Alloc, m.Store, m.Realm, ev, false)
Expand Down
4 changes: 4 additions & 0 deletions gnovm/pkg/gnolang/op_expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ func (m *Machine) doOpIndex1() {
}
}
default:
if res, ok := xv.GetValueAtIntIndex(m.Store, int(iv.ConvertGetInt())); ok {
*xv = res // reuse as result
return
}
// Read-only: pass nilRealm so map key attach DidUpdate is a no-op.
res := xv.GetPointerAtIndex(m, nilRealm, m.Alloc, m.Store, iv)
*xv = res.Deref() // reuse as result
Expand Down
54 changes: 33 additions & 21 deletions gnovm/pkg/gnolang/uverse.go
Original file line number Diff line number Diff line change
Expand Up @@ -877,8 +877,6 @@ func makeUverseNode() {
panic("should not happen")
}
// NOTE: this implementation is almost identical to the next one.
// note that in some cases optimization
// is possible if dstv.Data != nil.
dstl := dst.TV.GetLength()
srcl := src.TV.GetLength()
minl := min(srcl, dstl)
Expand All @@ -901,11 +899,17 @@ func makeUverseNode() {
// Assign2 fast-paths DataByteType (values.go:217): just SetDataByte
// + single DidUpdate. Per-byte cost lands in the Primitive tier.
m.incrCPU(OpCPUSlopeCopyPrimitive * int64(minl))
// TODO: consider an optimization if dstv.Data != nil.
for i := range minl {
dstev := dstv.GetPointerAtIndexInt2(m.Store, i, bdt.Elt)
srcev := src.TV.GetPointerAtIndexInt(m, m.Store, i)
dstev.Assign2(m, m.Alloc, m.Store, m.Realm, srcev.Deref(), false)
if dstBase.Data != nil {
// Copy string bytes directly into the Data-backed
// destination, instead of materializing a heap-allocated
// pointer box per element (see GetPointerAtIndexInt2).
copy(dstBase.Data[dstv.Offset:dstv.Offset+minl], src.TV.GetString())
} else {
for i := range minl {
dstev := dstv.GetPointerAtIndexInt2(m.Store, i, bdt.Elt)
srcev := src.TV.GetPointerAtIndexInt(m, m.Store, i)
dstev.Assign2(m, m.Alloc, m.Store, m.Realm, srcev.Deref(), false)
}
}
res0 := TypedValue{
T: IntType,
Expand Down Expand Up @@ -936,21 +940,29 @@ func makeUverseNode() {
srcStart := srcv.Offset
srcEnd := srcStart + minl

step := 1
start := 0
end := minl
// Overlap-safe copy: copy backward when dst starts after src to avoid clobbering.
requiresBackwardCopy := dstBase == srcBase && dstStart > srcStart && dstStart < srcEnd
if requiresBackwardCopy {
step = -1
start = minl - 1
end = -1
}
m.incrCPU(OpCPUSlopeCopyElement * int64(minl))
for i := start; i != end; i += step {
dstev := dstv.GetPointerAtIndexInt2(m.Store, i, bdt.Elt)
srcev := srcv.GetPointerAtIndexInt2(m.Store, i, bst.Elt)
dstev.Assign2(m, m.Alloc, m.Store, m.Realm, srcev.Deref(), false)
if dstBase.Data != nil && srcBase.Data != nil {
// Copy bytes directly between Data-backed slices, instead
// of materializing two heap-allocated pointer boxes per
// element (see GetPointerAtIndexInt2). Go's copy is
// overlap-safe in both directions.
copy(dstBase.Data[dstStart:dstStart+minl], srcBase.Data[srcStart:srcEnd])
} else {
step := 1
start := 0
end := minl
// Overlap-safe copy: copy backward when dst starts after src to avoid clobbering.
requiresBackwardCopy := dstBase == srcBase && dstStart > srcStart && dstStart < srcEnd
if requiresBackwardCopy {
step = -1
start = minl - 1
end = -1
}
for i := start; i != end; i += step {
dstev := dstv.GetPointerAtIndexInt2(m.Store, i, bdt.Elt)
srcev := srcv.GetPointerAtIndexInt2(m.Store, i, bst.Elt)
dstev.Assign2(m, m.Alloc, m.Store, m.Realm, srcev.Deref(), false)
}
}
res0 := TypedValue{
T: IntType,
Expand Down
52 changes: 52 additions & 0 deletions gnovm/pkg/gnolang/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -1985,6 +1985,58 @@ func (tv *TypedValue) GetPointerAtIndexInt(m *Machine, store Store, ii int) Poin
return tv.GetPointerAtIndex(m, nilRealm, fallbackAllocator, store, &iv)
}

// GetValueAtIntIndex is a read-only fast path of GetPointerAtIndex for
// strings and Data-backed (byte) arrays and slices: it returns the element
// value directly instead of materializing a heap-allocated pointer box
// (see GetPointerAtIndexInt2) that the caller immediately Derefs and
// discards. ok is false when tv is none of those (maps, List-backed arrays
// and slices), in which case the caller must use GetPointerAtIndex. Checks
// and panics mirror GetPointerAtIndex: explicit Exceptions for strings and
// slices; arrays rely on Go's implicit bounds panic, as there.
func (tv *TypedValue) GetValueAtIntIndex(store Store, ii int) (res TypedValue, ok bool) {
switch bt := baseOf(tv.T).(type) {
case PrimitiveType:
if bt == StringType || bt == UntypedStringType {
sv := tv.GetString()
if ii >= len(sv) {
panic(&Exception{Value: typedString(fmt.Sprintf("runtime error: index out of range [%d] with length %d", ii, len(sv)))})
}
if ii < 0 {
panic(&Exception{Value: typedString(fmt.Sprintf("runtime error: invalid slice index %d (index must be non-negative)", ii))})
}
res = TypedValue{T: Uint8Type}
res.SetUint8(sv[ii])
return res, true
}
case *ArrayType:
if av, aok := tv.V.(*ArrayValue); aok && av.Data != nil {
res = TypedValue{T: bt.Elt}
res.SetUint8(av.Data[ii])
return res, true
}
case *SliceType:
if tv.V == nil {
panic(&Exception{Value: typedString("runtime error: nil slice index (out of bounds)")})
}
if sv, sok := tv.V.(*SliceValue); sok {
if base := sv.GetBase(store); base.Data != nil {
if ii < 0 {
panic(&Exception{Value: typedString(fmt.Sprintf(
"runtime error: slice index out of bounds: %d", ii))})
} else if sv.Length <= ii {
panic(&Exception{Value: typedString(fmt.Sprintf(
"runtime error: slice index out of bounds: %d (len=%d)",
ii, sv.Length))})
}
res = TypedValue{T: bt.Elt}
res.SetUint8(base.Data[sv.Offset+ii])
return res, true
}
}
}
return TypedValue{}, false
}

func (tv *TypedValue) GetPointerAtIndex(m *Machine, rlm *Realm, alloc *Allocator, store Store, iv *TypedValue) PointerValue {
switch bt := baseOf(tv.T).(type) {
case PrimitiveType:
Expand Down
Loading