interp: implement runtime.sliceCopy
This implements the copy() built-in function. It may not work in all cases, but should work in most cases. This commit gets the following 3 packages to compile, according to tinygo-site/imports/main.go: * encoding/base32 * encoding/base64 * encoding/pem (was blocked by encoding/base64)
Этот коммит содержится в:
родитель
4ea1559d46
коммит
582457b81e
8 изменённых файлов: 201 добавлений и 7 удалений
|
@ -316,6 +316,49 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0})
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1})
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
||||
case callee.Name() == "runtime.sliceCopy":
|
||||
elementSize := fr.getLocal(inst.Operand(4)).(*LocalValue).Value().ZExtValue()
|
||||
dstArray := fr.getLocal(inst.Operand(0)).(*LocalValue).stripPointerCasts()
|
||||
srcArray := fr.getLocal(inst.Operand(1)).(*LocalValue).stripPointerCasts()
|
||||
dstLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
||||
srcLen := fr.getLocal(inst.Operand(3)).(*LocalValue)
|
||||
if elementSize != 1 && dstArray.Type().ElementType().TypeKind() == llvm.ArrayTypeKind && srcArray.Type().ElementType().TypeKind() == llvm.ArrayTypeKind {
|
||||
// Slice data pointers are created by adding a global array
|
||||
// and getting the address of the first element using a GEP.
|
||||
// However, before the compiler can pass it to
|
||||
// runtime.sliceCopy, it has to perform a bitcast to a *i8,
|
||||
// to make it a unsafe.Pointer. Now, when the IR builder
|
||||
// sees a bitcast of a GEP with zero indices, it will make
|
||||
// a bitcast of the original array instead of the GEP,
|
||||
// which breaks our assumptions.
|
||||
// Re-add this GEP, in the hope that it it is then of the correct type...
|
||||
dstArray = dstArray.GetElementPtr([]uint32{0, 0}).(*LocalValue)
|
||||
srcArray = srcArray.GetElementPtr([]uint32{0, 0}).(*LocalValue)
|
||||
}
|
||||
if fr.Eval.TargetData.TypeAllocSize(dstArray.Type().ElementType()) != elementSize {
|
||||
return nil, nil, errors.New("interp: slice dst element size does not match pointer type")
|
||||
}
|
||||
if fr.Eval.TargetData.TypeAllocSize(srcArray.Type().ElementType()) != elementSize {
|
||||
return nil, nil, errors.New("interp: slice src element size does not match pointer type")
|
||||
}
|
||||
if dstArray.Type() != srcArray.Type() {
|
||||
return nil, nil, errors.New("interp: slice element types don't match")
|
||||
}
|
||||
length := dstLen.Value().SExtValue()
|
||||
if srcLength := srcLen.Value().SExtValue(); srcLength < length {
|
||||
length = srcLength
|
||||
}
|
||||
if length < 0 {
|
||||
return nil, nil, errors.New("interp: trying to copy a slice with negative length?")
|
||||
}
|
||||
for i := int64(0); i < length; i++ {
|
||||
// *dst = *src
|
||||
dstArray.Store(srcArray.Load())
|
||||
// dst++
|
||||
dstArray = dstArray.GetElementPtr([]uint32{1}).(*LocalValue)
|
||||
// src++
|
||||
srcArray = srcArray.GetElementPtr([]uint32{1}).(*LocalValue)
|
||||
}
|
||||
case callee.Name() == "runtime.stringToBytes":
|
||||
// convert a string to a []byte
|
||||
bufPtr := fr.getLocal(inst.Operand(0))
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
func TestInterp(t *testing.T) {
|
||||
for _, name := range []string{
|
||||
"basic",
|
||||
"slice-copy",
|
||||
} {
|
||||
name := name // make tc local to this closure
|
||||
t.Run(name, func(t *testing.T) {
|
||||
|
|
|
@ -35,6 +35,8 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
|||
return &sideEffectResult{severity: sideEffectLimited}
|
||||
case "runtime.interfaceImplements":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "runtime.sliceCopy":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "runtime.trackPointer":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "llvm.dbg.value":
|
||||
|
|
86
interp/testdata/slice-copy.ll
предоставленный
Обычный файл
86
interp/testdata/slice-copy.ll
предоставленный
Обычный файл
|
@ -0,0 +1,86 @@
|
|||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@main.uint8SliceSrc.buf = internal global [2 x i8] c"\03d"
|
||||
@main.uint8SliceSrc = internal unnamed_addr global { i8*, i64, i64 } { i8* getelementptr inbounds ([2 x i8], [2 x i8]* @main.uint8SliceSrc.buf, i32 0, i32 0), i64 2, i64 2 }
|
||||
@main.uint8SliceDst = internal unnamed_addr global { i8*, i64, i64 } zeroinitializer
|
||||
@main.int16SliceSrc.buf = internal global [3 x i16] [i16 5, i16 123, i16 1024]
|
||||
@main.int16SliceSrc = internal unnamed_addr global { i16*, i64, i64 } { i16* getelementptr inbounds ([3 x i16], [3 x i16]* @main.int16SliceSrc.buf, i32 0, i32 0), i64 3, i64 3 }
|
||||
@main.int16SliceDst = internal unnamed_addr global { i16*, i64, i64 } zeroinitializer
|
||||
|
||||
declare i64 @runtime.sliceCopy(i8* %dst, i8* %src, i64 %dstLen, i64 %srcLen, i64 %elemSize) unnamed_addr
|
||||
|
||||
declare i8* @runtime.alloc(i64) unnamed_addr
|
||||
|
||||
declare void @runtime.printuint8(i8)
|
||||
|
||||
declare void @runtime.printint16(i16)
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @main.init()
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
; print(uintSliceSrc[0])
|
||||
%uint8SliceSrc.buf = load i8*, i8** getelementptr inbounds ({ i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceSrc, i64 0, i32 0)
|
||||
%uint8SliceSrc.val = load i8, i8* %uint8SliceSrc.buf
|
||||
call void @runtime.printuint8(i8 %uint8SliceSrc.val)
|
||||
|
||||
; print(uintSliceDst[0])
|
||||
%uint8SliceDst.buf = load i8*, i8** getelementptr inbounds ({ i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceDst, i64 0, i32 0)
|
||||
%uint8SliceDst.val = load i8, i8* %uint8SliceDst.buf
|
||||
call void @runtime.printuint8(i8 %uint8SliceDst.val)
|
||||
|
||||
; print(int16SliceSrc[0])
|
||||
%int16SliceSrc.buf = load i16*, i16** getelementptr inbounds ({ i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceSrc, i64 0, i32 0)
|
||||
%int16SliceSrc.val = load i16, i16* %int16SliceSrc.buf
|
||||
call void @runtime.printint16(i16 %int16SliceSrc.val)
|
||||
|
||||
; print(int16SliceDst[0])
|
||||
%int16SliceDst.buf = load i16*, i16** getelementptr inbounds ({ i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceDst, i64 0, i32 0)
|
||||
%int16SliceDst.val = load i16, i16* %int16SliceDst.buf
|
||||
call void @runtime.printint16(i16 %int16SliceDst.val)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init() unnamed_addr {
|
||||
entry:
|
||||
; equivalent of:
|
||||
; uint8SliceDst = make([]uint8, len(uint8SliceSrc))
|
||||
%uint8SliceSrc = load { i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceSrc
|
||||
%uint8SliceSrc.len = extractvalue { i8*, i64, i64 } %uint8SliceSrc, 1
|
||||
%uint8SliceDst.buf = call i8* @runtime.alloc(i64 %uint8SliceSrc.len)
|
||||
%0 = insertvalue { i8*, i64, i64 } undef, i8* %uint8SliceDst.buf, 0
|
||||
%1 = insertvalue { i8*, i64, i64 } %0, i64 %uint8SliceSrc.len, 1
|
||||
%2 = insertvalue { i8*, i64, i64 } %1, i64 %uint8SliceSrc.len, 2
|
||||
store { i8*, i64, i64 } %2, { i8*, i64, i64 }* @main.uint8SliceDst
|
||||
|
||||
; equivalent of:
|
||||
; copy(uint8SliceDst, uint8SliceSrc)
|
||||
%uint8SliceSrc.buf = extractvalue { i8*, i64, i64 } %uint8SliceSrc, 0
|
||||
%copy.n = call i64 @runtime.sliceCopy(i8* %uint8SliceDst.buf, i8* %uint8SliceSrc.buf, i64 %uint8SliceSrc.len, i64 %uint8SliceSrc.len, i64 1)
|
||||
|
||||
; equivalent of:
|
||||
; int16SliceDst = make([]int16, len(int16SliceSrc))
|
||||
%int16SliceSrc = load { i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceSrc
|
||||
%int16SliceSrc.len = extractvalue { i16*, i64, i64 } %int16SliceSrc, 1
|
||||
%int16SliceSrc.len.bytes = mul i64 %int16SliceSrc.len, 2
|
||||
%int16SliceDst.buf.raw = call i8* @runtime.alloc(i64 %int16SliceSrc.len.bytes)
|
||||
%int16SliceDst.buf = bitcast i8* %int16SliceDst.buf.raw to i16*
|
||||
%3 = insertvalue { i16*, i64, i64 } undef, i16* %int16SliceDst.buf, 0
|
||||
%4 = insertvalue { i16*, i64, i64 } %3, i64 %int16SliceSrc.len, 1
|
||||
%5 = insertvalue { i16*, i64, i64 } %4, i64 %int16SliceSrc.len, 2
|
||||
store { i16*, i64, i64 } %5, { i16*, i64, i64 }* @main.int16SliceDst
|
||||
|
||||
; equivalent of:
|
||||
; copy(int16SliceDst, int16SliceSrc)
|
||||
%int16SliceSrc.buf = extractvalue { i16*, i64, i64 } %int16SliceSrc, 0
|
||||
%int16SliceSrc.buf.i8ptr = bitcast i16* %int16SliceSrc.buf to i8*
|
||||
%int16SliceDst.buf.i8ptr = bitcast i16* %int16SliceDst.buf to i8*
|
||||
%copy.n2 = call i64 @runtime.sliceCopy(i8* %int16SliceDst.buf.i8ptr, i8* %int16SliceSrc.buf.i8ptr, i64 %int16SliceSrc.len, i64 %int16SliceSrc.len, i64 2)
|
||||
|
||||
ret void
|
||||
}
|
20
interp/testdata/slice-copy.out.ll
предоставленный
Обычный файл
20
interp/testdata/slice-copy.out.ll
предоставленный
Обычный файл
|
@ -0,0 +1,20 @@
|
|||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
declare void @runtime.printuint8(i8) local_unnamed_addr
|
||||
|
||||
declare void @runtime.printint16(i16) local_unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.printuint8(i8 3)
|
||||
call void @runtime.printuint8(i8 3)
|
||||
call void @runtime.printint16(i16 5)
|
||||
call void @runtime.printint16(i16 5)
|
||||
ret void
|
||||
}
|
|
@ -98,13 +98,33 @@ func (v *LocalValue) GetElementPtr(indices []uint32) Value {
|
|||
gep := llvm.ConstGEP(v.Underlying, getLLVMIndices(int32Type, indices))
|
||||
return &LocalValue{v.Eval, gep}
|
||||
}
|
||||
switch v.Underlying.Opcode() {
|
||||
case llvm.GetElementPtr, llvm.IntToPtr, llvm.BitCast:
|
||||
int32Type := v.Underlying.Type().Context().Int32Type()
|
||||
llvmIndices := getLLVMIndices(int32Type, indices)
|
||||
return &LocalValue{v.Eval, llvm.ConstGEP(v.Underlying, llvmIndices)}
|
||||
default:
|
||||
panic("interp: GEP on a constant")
|
||||
if !v.Underlying.IsAConstantExpr().IsNil() {
|
||||
switch v.Underlying.Opcode() {
|
||||
case llvm.GetElementPtr, llvm.IntToPtr, llvm.BitCast:
|
||||
int32Type := v.Underlying.Type().Context().Int32Type()
|
||||
llvmIndices := getLLVMIndices(int32Type, indices)
|
||||
return &LocalValue{v.Eval, llvm.ConstGEP(v.Underlying, llvmIndices)}
|
||||
}
|
||||
}
|
||||
panic("interp: unknown GEP")
|
||||
}
|
||||
|
||||
// stripPointerCasts removes all const bitcasts from pointer values, if there
|
||||
// are any.
|
||||
func (v *LocalValue) stripPointerCasts() *LocalValue {
|
||||
value := v.Underlying
|
||||
for {
|
||||
if !value.IsAConstantExpr().IsNil() {
|
||||
switch value.Opcode() {
|
||||
case llvm.BitCast:
|
||||
value = value.Operand(0)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return &LocalValue{
|
||||
Eval: v.Eval,
|
||||
Underlying: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
18
testdata/init.go
предоставленный
18
testdata/init.go
предоставленный
|
@ -13,6 +13,11 @@ func main() {
|
|||
println("v5:", len(v5), v5 == nil)
|
||||
println("v6:", v6)
|
||||
println("v7:", cap(v7), string(v7))
|
||||
|
||||
println(uint8SliceSrc[0])
|
||||
println(uint8SliceDst[0])
|
||||
println(intSliceSrc[0])
|
||||
println(intSliceDst[0])
|
||||
}
|
||||
|
||||
type (
|
||||
|
@ -30,4 +35,17 @@ var (
|
|||
v5 = map[string]int{}
|
||||
v6 = float64(v1) < 2.6
|
||||
v7 = []byte("foo")
|
||||
|
||||
uint8SliceSrc = []uint8{3, 100}
|
||||
uint8SliceDst []uint8
|
||||
intSliceSrc = []int16{5, 123, 1024}
|
||||
intSliceDst []int16
|
||||
)
|
||||
|
||||
func init() {
|
||||
uint8SliceDst = make([]uint8, len(uint8SliceSrc))
|
||||
copy(uint8SliceDst, uint8SliceSrc)
|
||||
|
||||
intSliceDst = make([]int16, len(intSliceSrc))
|
||||
copy(intSliceDst, intSliceSrc)
|
||||
}
|
||||
|
|
4
testdata/init.txt
предоставленный
4
testdata/init.txt
предоставленный
|
@ -7,3 +7,7 @@ v4: 0 true
|
|||
v5: 0 false
|
||||
v6: false
|
||||
v7: 3 foo
|
||||
3
|
||||
3
|
||||
5
|
||||
5
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче