diff options
author | Ayke van Laethem <[email protected]> | 2019-08-02 12:19:23 +0200 |
---|---|---|
committer | Ron Evans <[email protected]> | 2019-08-04 17:51:16 +0200 |
commit | 4688664b4116283ffcc4d7652b6d8052f6dcdc60 (patch) | |
tree | b47587975da0fbf821238f017348ffc3c564dc1b | |
parent | 54169c714fb9e9142fed5e7a70897ff466f31d28 (diff) | |
download | tinygo-4688664b4116283ffcc4d7652b6d8052f6dcdc60.tar.gz tinygo-4688664b4116283ffcc4d7652b6d8052f6dcdc60.zip |
compiler: implement full slice expression
This feature was introduced in Go 1.2 and is used by some standard
library packages.
-rw-r--r-- | compiler/asserts.go | 18 | ||||
-rw-r--r-- | compiler/compiler.go | 59 | ||||
-rw-r--r-- | testdata/slice.go | 14 |
3 files changed, 75 insertions, 16 deletions
diff --git a/compiler/asserts.go b/compiler/asserts.go index 171dc8e63..b9f02e849 100644 --- a/compiler/asserts.go +++ b/compiler/asserts.go @@ -56,7 +56,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu // normal meaning) and for creating a new slice, where 'capacity' means the // biggest possible slice capacity, 'low' means len and 'high' means cap. The // logic is the same in both cases. -func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) { +func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) { if frame.fn.IsNoBounds() { // The //go:nobounds pragma was added to the function to avoid bounds // checking. @@ -71,6 +71,9 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() { capacityType = high.Type() } + if max.Type().IntTypeWidth() > capacityType.IntTypeWidth() { + capacityType = max.Type() + } if capacityType != capacity.Type() { capacity = c.builder.CreateZExt(capacity, capacityType, "") } @@ -90,6 +93,13 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V high = c.builder.CreateSExt(high, capacityType, "") } } + if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() { + if maxType.Info()&types.IsUnsigned != 0 { + max = c.builder.CreateZExt(max, capacityType, "") + } else { + max = c.builder.CreateSExt(max, capacityType, "") + } + } faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds") nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next") @@ -97,8 +107,10 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V // Now do the bounds check: low > high || high > capacity outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh") - outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap") - outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds") + outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax") + outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap") + outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax") + outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap") c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock) // Fail: this is a nil pointer, exit with a panic. diff --git a/compiler/compiler.go b/compiler/compiler.go index c12aec89d..6f25cac78 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1650,7 +1650,9 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { } // Bounds checking. - c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, expr.Len.Type().(*types.Basic), expr.Cap.Type().(*types.Basic)) + lenType := expr.Len.Type().(*types.Basic) + capType := expr.Cap.Type().(*types.Basic) + c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType) // Allocate the backing array. sliceCapCast, err := c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) @@ -1724,13 +1726,10 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { case *ssa.Select: return c.emitSelect(frame, expr), nil case *ssa.Slice: - if expr.Max != nil { - return llvm.Value{}, c.makeError(expr.Pos(), "todo: full slice expressions (with max): "+expr.Type().String()) - } value := c.getValue(frame, expr.X) - var lowType, highType *types.Basic - var low, high llvm.Value + var lowType, highType, maxType *types.Basic + var low, high, max llvm.Value if expr.Low != nil { lowType = expr.Low.Type().Underlying().(*types.Basic) @@ -1761,33 +1760,56 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { highType = types.Typ[types.Uintptr] } + if expr.Max != nil { + maxType = expr.Max.Type().Underlying().(*types.Basic) + max = c.getValue(frame, expr.Max) + if max.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() { + if maxType.Info()&types.IsUnsigned != 0 { + max = c.builder.CreateZExt(max, c.uintptrType, "") + } else { + max = c.builder.CreateSExt(max, c.uintptrType, "") + } + } + } else { + maxType = types.Typ[types.Uintptr] + } + switch typ := expr.X.Type().Underlying().(type) { case *types.Pointer: // pointer to array + if expr.Max != nil { + return llvm.Value{}, c.makeError(expr.Pos(), "todo: full slice expressions (with max): "+expr.Type().String()) + } // slice an array length := typ.Elem().Underlying().(*types.Array).Len() llvmLen := llvm.ConstInt(c.uintptrType, uint64(length), false) if high.IsNil() { high = llvmLen } + if max.IsNil() { + max = llvmLen + } indices := []llvm.Value{ llvm.ConstInt(c.ctx.Int32Type(), 0, false), low, } - c.emitSliceBoundsCheck(frame, llvmLen, low, high, lowType, highType) + c.emitSliceBoundsCheck(frame, llvmLen, low, high, max, lowType, highType, maxType) // Truncate ints bigger than uintptr. This is after the bounds // check so it's safe. + if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { + low = c.builder.CreateTrunc(low, c.uintptrType, "") + } if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { high = c.builder.CreateTrunc(high, c.uintptrType, "") } - if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { - low = c.builder.CreateTrunc(low, c.uintptrType, "") + if c.targetData.TypeAllocSize(max.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { + max = c.builder.CreateTrunc(max, c.uintptrType, "") } sliceLen := c.builder.CreateSub(high, low, "slice.len") slicePtr := c.builder.CreateInBoundsGEP(value, indices, "slice.ptr") - sliceCap := c.builder.CreateSub(llvmLen, low, "slice.cap") + sliceCap := c.builder.CreateSub(max, low, "slice.cap") slice := c.ctx.ConstStruct([]llvm.Value{ llvm.Undef(slicePtr.Type()), @@ -1807,8 +1829,11 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { if high.IsNil() { high = oldLen } + if max.IsNil() { + max = oldCap + } - c.emitSliceBoundsCheck(frame, oldCap, low, high, lowType, highType) + c.emitSliceBoundsCheck(frame, oldCap, low, high, max, lowType, highType, maxType) // Truncate ints bigger than uintptr. This is after the bounds // check so it's safe. @@ -1818,10 +1843,13 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { high = c.builder.CreateTrunc(high, c.uintptrType, "") } + if c.targetData.TypeAllocSize(max.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { + max = c.builder.CreateTrunc(max, c.uintptrType, "") + } newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "") newLen := c.builder.CreateSub(high, low, "") - newCap := c.builder.CreateSub(oldCap, low, "") + newCap := c.builder.CreateSub(max, low, "") slice := c.ctx.ConstStruct([]llvm.Value{ llvm.Undef(newPtr.Type()), llvm.Undef(c.uintptrType), @@ -1837,13 +1865,18 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { return llvm.Value{}, c.makeError(expr.Pos(), "unknown slice type: "+typ.String()) } // slice a string + if expr.Max != nil { + // This might as well be a panic, as the frontend should have + // handled this already. + return llvm.Value{}, c.makeError(expr.Pos(), "slicing a string with a max parameter is not allowed by the spec") + } oldPtr := c.builder.CreateExtractValue(value, 0, "") oldLen := c.builder.CreateExtractValue(value, 1, "") if high.IsNil() { high = oldLen } - c.emitSliceBoundsCheck(frame, oldLen, low, high, lowType, highType) + c.emitSliceBoundsCheck(frame, oldLen, low, high, high, lowType, highType, maxType) // Truncate ints bigger than uintptr. This is after the bounds // check so it's safe. diff --git a/testdata/slice.go b/testdata/slice.go index 61ac1fb4b..8c5bae418 100644 --- a/testdata/slice.go +++ b/testdata/slice.go @@ -65,6 +65,20 @@ func main() { assert(len(arr[uint64(1):uint64(3)]) == 2) assert(len(arr[uintptr(1):uintptr(3)]) == 2) + // slicing with max parameter (added in Go 1.2) + longfoo := []int{1, 2, 4, 5, 10, 11} + assert(cap(longfoo[int(1):int(3):int(5)]) == 4) + assert(cap(longfoo[int8(1):int8(3):int8(5)]) == 4) + assert(cap(longfoo[int16(1):int16(3):int16(5)]) == 4) + assert(cap(longfoo[int32(1):int32(3):int32(5)]) == 4) + assert(cap(longfoo[int64(1):int64(3):int64(5)]) == 4) + assert(cap(longfoo[uint(1):uint(3):uint(5)]) == 4) + assert(cap(longfoo[uint8(1):uint8(3):uint8(5)]) == 4) + assert(cap(longfoo[uint16(1):uint16(3):uint16(5)]) == 4) + assert(cap(longfoo[uint32(1):uint32(3):uint32(5)]) == 4) + assert(cap(longfoo[uint64(1):uint64(3):uint64(5)]) == 4) + assert(cap(longfoo[uintptr(1):uintptr(3):uintptr(5)]) == 4) + // copy println("copy foo -> bar:", copy(bar, foo)) printslice("bar", bar) |