diff --git a/compiler/compiler.go b/compiler/compiler.go index 9c5b5ac8..1216d6cf 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1654,6 +1654,34 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error { func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string) (llvm.Value, error) { switch callName { + case "append": + src, err := c.parseExpr(frame, args[0]) + if err != nil { + return llvm.Value{}, err + } + elems, err := c.parseExpr(frame, args[1]) + if err != nil { + return llvm.Value{}, err + } + srcBuf := c.builder.CreateExtractValue(src, 0, "append.srcBuf") + srcPtr := c.builder.CreateBitCast(srcBuf, c.i8ptrType, "append.srcPtr") + srcLen := c.builder.CreateExtractValue(src, 1, "append.srcLen") + srcCap := c.builder.CreateExtractValue(src, 2, "append.srcCap") + elemsBuf := c.builder.CreateExtractValue(elems, 0, "append.elemsBuf") + elemsPtr := c.builder.CreateBitCast(elemsBuf, c.i8ptrType, "append.srcPtr") + elemsLen := c.builder.CreateExtractValue(elems, 1, "append.elemsLen") + elemType := srcBuf.Type().ElementType() + elemSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(elemType), false) + result := c.createRuntimeCall("sliceAppend", []llvm.Value{srcPtr, elemsPtr, srcLen, srcCap, elemsLen, elemSize}, "append.new") + newPtr := c.builder.CreateExtractValue(result, 0, "append.newPtr") + newBuf := c.builder.CreateBitCast(newPtr, srcBuf.Type(), "append.newBuf") + newLen := c.builder.CreateExtractValue(result, 1, "append.newLen") + newCap := c.builder.CreateExtractValue(result, 2, "append.newCap") + newSlice := llvm.Undef(src.Type()) + newSlice = c.builder.CreateInsertValue(newSlice, newBuf, 0, "") + newSlice = c.builder.CreateInsertValue(newSlice, newLen, 1, "") + newSlice = c.builder.CreateInsertValue(newSlice, newCap, 2, "") + return newSlice, nil case "cap": value, err := c.parseExpr(frame, args[0]) if err != nil { diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index 65e89325..95176460 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -83,17 +83,6 @@ func memequal(x, y unsafe.Pointer, n uintptr) bool { return true } -// Builtin copy(dst, src) function: copy bytes from dst to src. -func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen lenType, elemSize uintptr) lenType { - // n = min(srcLen, dstLen) - n := srcLen - if n > dstLen { - n = dstLen - } - memmove(dst, src, uintptr(n)*elemSize) - return n -} - //go:linkname sleep time.Sleep func sleep(d int64) { sleepTicks(timeUnit(d / tickMicros)) diff --git a/src/runtime/slice.go b/src/runtime/slice.go new file mode 100644 index 00000000..def8df37 --- /dev/null +++ b/src/runtime/slice.go @@ -0,0 +1,53 @@ +package runtime + +// This file implements compiler builtins for slices: append() and copy(). + +import ( + "unsafe" +) + +// Builtin append(src, elements...) function: append elements to src and return +// the modified (possibly expanded) slice. +func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen lenType, elemSize uintptr) (unsafe.Pointer, lenType, lenType) { + if elemsLen == 0 { + // Nothing to append, return the input slice. + return srcBuf, srcLen, srcCap + } + + if srcLen+elemsLen > srcCap { + // Slice does not fit, allocate a new buffer that's large enough. + srcCap = srcCap * 2 + if srcCap == 0 { // e.g. zero slice + srcCap = 1 + } + for srcLen+elemsLen > srcCap { + // This algorithm may be made more memory-efficient: don't multiply + // by two but by 1.5 or something. As far as I can see, that's + // allowed by the Go language specification (but may be observed by + // programs). + srcCap *= 2 + } + buf := alloc(uintptr(srcCap) * elemSize) + + // Copy the old slice to the new slice. + if srcLen != 0 { + memmove(buf, srcBuf, uintptr(srcLen)*elemSize) + } + srcBuf = buf + } + + // The slice fits (after possibly allocating a new one), append it in-place. + memmove(unsafe.Pointer(uintptr(srcBuf)+uintptr(srcLen)*elemSize), elemsBuf, uintptr(elemsLen)*elemSize) + return srcBuf, srcLen + elemsLen, srcCap +} + +// Builtin copy(dst, src) function: copy bytes from dst to src. +func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen lenType, elemSize uintptr) lenType { + // n = min(srcLen, dstLen) + n := srcLen + if n > dstLen { + n = dstLen + } + memmove(dst, src, uintptr(n)*elemSize) + return n +} diff --git a/testdata/slice.go b/testdata/slice.go index 215c213e..c5e4a330 100644 --- a/testdata/slice.go +++ b/testdata/slice.go @@ -8,8 +8,32 @@ func main() { printslice("bar", bar) printslice("foo[1:2]", foo[1:2]) println("sum foo:", sum(foo)) + + // copy println("copy foo -> bar:", copy(bar, foo)) printslice("bar", bar) + + // append + var grow []int + printslice("grow", grow) + grow = append(grow, 42) + printslice("grow", grow) + grow = append(grow, -1, -2) + printslice("grow", grow) + grow = append(grow, foo...) + printslice("grow", grow) + grow = append(grow) + printslice("grow", grow) + grow = append(grow, grow...) + printslice("grow", grow) + + // append string to []bytes + bytes := append([]byte{1, 2, 3}, "foo"...) + print("bytes: len=", len(bytes), " cap=", cap(bytes), " data:") + for _, n := range bytes { + print(" ", n) + } + println() } func printslice(name string, s []int) { diff --git a/testdata/slice.txt b/testdata/slice.txt index d598d432..81973655 100644 --- a/testdata/slice.txt +++ b/testdata/slice.txt @@ -4,3 +4,10 @@ foo[1:2]: len=1 cap=3 data: 2 sum foo: 12 copy foo -> bar: 3 bar: len=3 cap=5 data: 1 2 4 +grow: len=0 cap=0 data: +grow: len=1 cap=1 data: 42 +grow: len=3 cap=4 data: 42 -1 -2 +grow: len=7 cap=8 data: 42 -1 -2 1 2 4 5 +grow: len=7 cap=8 data: 42 -1 -2 1 2 4 5 +grow: len=14 cap=16 data: 42 -1 -2 1 2 4 5 42 -1 -2 1 2 4 5 +bytes: len=6 cap=6 data: 1 2 3 102 111 111