diff --git a/cl/_testrt/slice2array/in.go b/cl/_testrt/slice2array/in.go new file mode 100644 index 00000000..9820e617 --- /dev/null +++ b/cl/_testrt/slice2array/in.go @@ -0,0 +1,8 @@ +package main + +func main() { + array := [4]byte{1, 2, 3, 4} + ptr := (*[4]byte)(array[:]) + println(array == *ptr) + println(*(*[2]byte)(array[:]) == [2]byte{1, 2}) +} diff --git a/cl/_testrt/slice2array/out.ll b/cl/_testrt/slice2array/out.ll new file mode 100644 index 00000000..9520f99a --- /dev/null +++ b/cl/_testrt/slice2array/out.ll @@ -0,0 +1,127 @@ +; ModuleID = 'main' +source_filename = "main" + +%"github.com/goplus/llgo/internal/runtime.Slice" = type { ptr, i64, i64 } + +@"main.init$guard" = global i1 false, align 1 +@__llgo_argc = global i32 0, align 4 +@__llgo_argv = global ptr null, align 8 + +define void @main.init() { +_llgo_0: + %0 = load i1, ptr @"main.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"main.init$guard", align 1 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +define i32 @main(i32 %0, ptr %1) { +_llgo_0: + store i32 %0, ptr @__llgo_argc, align 4 + store ptr %1, ptr @__llgo_argv, align 8 + call void @"github.com/goplus/llgo/internal/runtime.init"() + call void @main.init() + %2 = call ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64 4) + %3 = getelementptr inbounds i8, ptr %2, i64 0 + %4 = getelementptr inbounds i8, ptr %2, i64 1 + %5 = getelementptr inbounds i8, ptr %2, i64 2 + %6 = getelementptr inbounds i8, ptr %2, i64 3 + store i8 1, ptr %3, align 1 + store i8 2, ptr %4, align 1 + store i8 3, ptr %5, align 1 + store i8 4, ptr %6, align 1 + %7 = alloca %"github.com/goplus/llgo/internal/runtime.Slice", align 8 + %8 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %7, i32 0, i32 0 + store ptr %2, ptr %8, align 8 + %9 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %7, i32 0, i32 1 + store i64 4, ptr %9, align 4 + %10 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %7, i32 0, i32 2 + store i64 4, ptr %10, align 4 + %11 = load %"github.com/goplus/llgo/internal/runtime.Slice", ptr %7, align 8 + %12 = extractvalue %"github.com/goplus/llgo/internal/runtime.Slice" %11, 1 + %13 = icmp slt i64 %12, 4 + br i1 %13, label %_llgo_1, label %_llgo_2 + +_llgo_1: ; preds = %_llgo_0 + %14 = extractvalue %"github.com/goplus/llgo/internal/runtime.Slice" %11, 1 + call void @"github.com/goplus/llgo/internal/runtime.PanicSliceConvert"(i64 %14, i64 4) + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + %15 = extractvalue %"github.com/goplus/llgo/internal/runtime.Slice" %11, 0 + %16 = load [4 x i8], ptr %2, align 1 + %17 = load [4 x i8], ptr %15, align 1 + %18 = extractvalue [4 x i8] %16, 0 + %19 = extractvalue [4 x i8] %17, 0 + %20 = icmp eq i8 %18, %19 + %21 = and i1 true, %20 + %22 = extractvalue [4 x i8] %16, 1 + %23 = extractvalue [4 x i8] %17, 1 + %24 = icmp eq i8 %22, %23 + %25 = and i1 %21, %24 + %26 = extractvalue [4 x i8] %16, 2 + %27 = extractvalue [4 x i8] %17, 2 + %28 = icmp eq i8 %26, %27 + %29 = and i1 %25, %28 + %30 = extractvalue [4 x i8] %16, 3 + %31 = extractvalue [4 x i8] %17, 3 + %32 = icmp eq i8 %30, %31 + %33 = and i1 %29, %32 + call void @"github.com/goplus/llgo/internal/runtime.PrintBool"(i1 %33) + call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) + %34 = alloca %"github.com/goplus/llgo/internal/runtime.Slice", align 8 + %35 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %34, i32 0, i32 0 + store ptr %2, ptr %35, align 8 + %36 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %34, i32 0, i32 1 + store i64 4, ptr %36, align 4 + %37 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.Slice", ptr %34, i32 0, i32 2 + store i64 4, ptr %37, align 4 + %38 = load %"github.com/goplus/llgo/internal/runtime.Slice", ptr %34, align 8 + %39 = extractvalue %"github.com/goplus/llgo/internal/runtime.Slice" %38, 1 + %40 = icmp slt i64 %39, 2 + br i1 %40, label %_llgo_3, label %_llgo_4 + +_llgo_3: ; preds = %_llgo_2 + %41 = extractvalue %"github.com/goplus/llgo/internal/runtime.Slice" %38, 1 + call void @"github.com/goplus/llgo/internal/runtime.PanicSliceConvert"(i64 %41, i64 2) + br label %_llgo_4 + +_llgo_4: ; preds = %_llgo_3, %_llgo_2 + %42 = extractvalue %"github.com/goplus/llgo/internal/runtime.Slice" %38, 0 + %43 = load [2 x i8], ptr %42, align 1 + %44 = alloca [2 x i8], align 1 + %45 = call ptr @"github.com/goplus/llgo/internal/runtime.Zeroinit"(ptr %44, i64 2) + %46 = getelementptr inbounds i8, ptr %45, i64 0 + %47 = getelementptr inbounds i8, ptr %45, i64 1 + store i8 1, ptr %46, align 1 + store i8 2, ptr %47, align 1 + %48 = load [2 x i8], ptr %45, align 1 + %49 = extractvalue [2 x i8] %43, 0 + %50 = extractvalue [2 x i8] %48, 0 + %51 = icmp eq i8 %49, %50 + %52 = and i1 true, %51 + %53 = extractvalue [2 x i8] %43, 1 + %54 = extractvalue [2 x i8] %48, 1 + %55 = icmp eq i8 %53, %54 + %56 = and i1 %52, %55 + call void @"github.com/goplus/llgo/internal/runtime.PrintBool"(i1 %56) + call void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8 10) + ret i32 0 +} + +declare void @"github.com/goplus/llgo/internal/runtime.init"() + +declare ptr @"github.com/goplus/llgo/internal/runtime.AllocZ"(i64) + +declare void @"github.com/goplus/llgo/internal/runtime.PanicSliceConvert"(i64, i64) + +declare void @"github.com/goplus/llgo/internal/runtime.PrintBool"(i1) + +declare void @"github.com/goplus/llgo/internal/runtime.PrintByte"(i8) + +declare ptr @"github.com/goplus/llgo/internal/runtime.Zeroinit"(ptr, i64) diff --git a/cl/compile.go b/cl/compile.go index dd284261..58a841c4 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -605,6 +605,10 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue } } ret = b.Select(states, v.Blocking) + case *ssa.SliceToArrayPointer: + t := b.Prog.Type(v.Type(), llssa.InGo) + x := p.compileValue(b, v.X) + ret = b.SliceToArrayPointer(x, t) default: panic(fmt.Sprintf("compileInstrAndValue: unknown instr - %T\n", iv)) } diff --git a/internal/runtime/alg.go b/internal/runtime/alg.go index 4b2edd20..d11a586b 100644 --- a/internal/runtime/alg.go +++ b/internal/runtime/alg.go @@ -1,3 +1,7 @@ +// Copyright 2014 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 import ( diff --git a/internal/runtime/errors.go b/internal/runtime/errors.go new file mode 100644 index 00000000..68790dd5 --- /dev/null +++ b/internal/runtime/errors.go @@ -0,0 +1,116 @@ +// Copyright 2014 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 + +// A boundsError represents an indexing or slicing operation gone wrong. +type boundsError struct { + x int64 + y int + // Values in an index or slice expression can be signed or unsigned. + // That means we'd need 65 bits to encode all possible indexes, from -2^63 to 2^64-1. + // Instead, we keep track of whether x should be interpreted as signed or unsigned. + // y is known to be nonnegative and to fit in an int. + signed bool + code boundsErrorCode +} + +type boundsErrorCode uint8 + +const ( + boundsIndex boundsErrorCode = iota // s[x], 0 <= x < len(s) failed + + boundsSliceAlen // s[?:x], 0 <= x <= len(s) failed + boundsSliceAcap // s[?:x], 0 <= x <= cap(s) failed + boundsSliceB // s[x:y], 0 <= x <= y failed (but boundsSliceA didn't happen) + + boundsSlice3Alen // s[?:?:x], 0 <= x <= len(s) failed + boundsSlice3Acap // s[?:?:x], 0 <= x <= cap(s) failed + boundsSlice3B // s[?:x:y], 0 <= x <= y failed (but boundsSlice3A didn't happen) + boundsSlice3C // s[x:y:?], 0 <= x <= y failed (but boundsSlice3A/B didn't happen) + + boundsConvert // (*[x]T)(s), 0 <= x <= len(s) failed + // Note: in the above, len(s) and cap(s) are stored in y +) + +// boundsErrorFmts provide error text for various out-of-bounds panics. +// Note: if you change these strings, you should adjust the size of the buffer +// in boundsError.Error below as well. +var boundsErrorFmts = [...]string{ + boundsIndex: "index out of range [%x] with length %y", + boundsSliceAlen: "slice bounds out of range [:%x] with length %y", + boundsSliceAcap: "slice bounds out of range [:%x] with capacity %y", + boundsSliceB: "slice bounds out of range [%x:%y]", + boundsSlice3Alen: "slice bounds out of range [::%x] with length %y", + boundsSlice3Acap: "slice bounds out of range [::%x] with capacity %y", + boundsSlice3B: "slice bounds out of range [:%x:%y]", + boundsSlice3C: "slice bounds out of range [%x:%y:]", + boundsConvert: "cannot convert slice with length %y to array or pointer to array with length %x", +} + +// boundsNegErrorFmts are overriding formats if x is negative. In this case there's no need to report y. +var boundsNegErrorFmts = [...]string{ + boundsIndex: "index out of range [%x]", + boundsSliceAlen: "slice bounds out of range [:%x]", + boundsSliceAcap: "slice bounds out of range [:%x]", + boundsSliceB: "slice bounds out of range [%x:]", + boundsSlice3Alen: "slice bounds out of range [::%x]", + boundsSlice3Acap: "slice bounds out of range [::%x]", + boundsSlice3B: "slice bounds out of range [:%x:]", + boundsSlice3C: "slice bounds out of range [%x::]", +} + +func (e boundsError) RuntimeError() {} + +func appendIntStr(b []byte, v int64, signed bool) []byte { + if signed && v < 0 { + b = append(b, '-') + v = -v + } + var buf [20]byte + b = append(b, itoa(buf[:], uint64(v))...) + return b +} + +func (e boundsError) Error() string { + fmt := boundsErrorFmts[e.code] + if e.signed && e.x < 0 { + fmt = boundsNegErrorFmts[e.code] + } + // max message length is 99: "runtime error: slice bounds out of range [::%x] with capacity %y" + // x can be at most 20 characters. y can be at most 19. + b := make([]byte, 0, 100) + b = append(b, "runtime error: "...) + for i := 0; i < len(fmt); i++ { + c := fmt[i] + if c != '%' { + b = append(b, c) + continue + } + i++ + switch fmt[i] { + case 'x': + b = appendIntStr(b, e.x, e.signed) + case 'y': + b = appendIntStr(b, int64(e.y), true) + } + } + return string(b) +} + +func itoa(buf []byte, val uint64) []byte { + i := len(buf) - 1 + for val >= 10 { + buf[i] = byte(val%10 + '0') + i-- + val /= 10 + } + buf[i] = byte(val + '0') + return buf[i:] +} + +// failures in the conversion ([x]T)(s) or (*[x]T)(s), 0 <= x <= y, y == len(s) +func PanicSliceConvert(x int, y int) { + panic(boundsError{x: int64(x), signed: true, y: y, code: boundsConvert}) +} diff --git a/ssa/expr.go b/ssa/expr.go index 391f66e3..efd9c4a0 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -1030,6 +1030,34 @@ func (b Builder) compareSelect(op token.Token, x Expr, y ...Expr) Expr { return ret } +// The SliceToArrayPointer instruction yields the conversion of slice X to +// array pointer. +// +// Pos() returns the ast.CallExpr.Lparen, if the instruction arose +// from an explicit conversion in the source. +// +// Conversion may to be to or from a type parameter. All types in +// the type set of X.Type() must be a slice types that can be converted to +// all types in the type set of Type() which must all be pointer to array +// types. +// +// This operation can fail dynamically if the length of the slice is less +// than the length of the array. +// +// Example printed form: +// +// t1 = slice to array pointer *[4]byte <- []byte (t0) +func (b Builder) SliceToArrayPointer(x Expr, typ Type) (ret Expr) { + ret.Type = typ + max := b.Prog.IntVal(uint64(typ.RawType().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len()), b.Prog.Int()) + failed := Expr{llvm.CreateICmp(b.impl, llvm.IntSLT, b.SliceLen(x).impl, max.impl), b.Prog.Bool()} + b.IfThen(failed, func() { + b.InlineCall(b.Pkg.rtFunc("PanicSliceConvert"), b.SliceLen(x), max) + }) + ret.impl = b.SliceData(x).impl + return +} + // A Builtin represents a specific use of a built-in function, e.g. len. // // Builtins are immutable values. Builtins do not have addresses.