compiler: add support for new unsafe slice/string functions
This adds support for unsafe.SliceData, unsafe.String, and unsafe.SringData that were introduced in Go 1.20.
Этот коммит содержится в:
		
							родитель
							
								
									33489d6344
								
							
						
					
					
						коммит
						c43958972c
					
				
					 6 изменённых файлов: 120 добавлений и 21 удалений
				
			
		|  | @ -89,10 +89,11 @@ func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen i | |||
| 	b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic") | ||||
| } | ||||
| 
 | ||||
| // createUnsafeSliceCheck inserts a runtime check used for unsafe.Slice. This | ||||
| // function must panic if the ptr/len parameters are invalid. | ||||
| func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, elementType llvm.Type, lenType *types.Basic) { | ||||
| 	// From the documentation of unsafe.Slice: | ||||
| // createUnsafeSliceStringCheck inserts a runtime check used for unsafe.Slice | ||||
| // and unsafe.String. This function must panic if the ptr/len parameters are | ||||
| // invalid. | ||||
| func (b *builder) createUnsafeSliceStringCheck(name string, ptr, len llvm.Value, elementType llvm.Type, lenType *types.Basic) { | ||||
| 	// From the documentation of unsafe.Slice and unsafe.String: | ||||
| 	//   > At run time, if len is negative, or if ptr is nil and len is not | ||||
| 	//   > zero, a run-time panic occurs. | ||||
| 	// However, in practice, it is also necessary to check that the length is | ||||
|  | @ -117,7 +118,7 @@ func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, elementType llvm.T | |||
| 	lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "") | ||||
| 	assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "") | ||||
| 	assert = b.CreateOr(assert, lenOutOfBounds, "") | ||||
| 	b.createRuntimeAssert(assert, "unsafe.Slice", "unsafeSlicePanic") | ||||
| 	b.createRuntimeAssert(assert, name, "unsafeSlicePanic") | ||||
| } | ||||
| 
 | ||||
| // createChanBoundsCheck creates a bounds check before creating a new channel to | ||||
|  |  | |||
|  | @ -1646,20 +1646,20 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c | |||
| 	case "Sizeof": // unsafe.Sizeof | ||||
| 		size := b.targetData.TypeAllocSize(argValues[0].Type()) | ||||
| 		return llvm.ConstInt(b.uintptrType, size, false), nil | ||||
| 	case "Slice": // unsafe.Slice | ||||
| 		// This creates a slice from a pointer and a length. | ||||
| 	case "Slice", "String": // unsafe.Slice, unsafe.String | ||||
| 		// This creates a slice or string from a pointer and a length. | ||||
| 		// Note that the exception mentioned in the documentation (if the | ||||
| 		// pointer and length are nil, the slice is also nil) is trivially | ||||
| 		// already the case. | ||||
| 		ptr := argValues[0] | ||||
| 		len := argValues[1] | ||||
| 		slice := llvm.Undef(b.ctx.StructType([]llvm.Type{ | ||||
| 			ptr.Type(), | ||||
| 			b.uintptrType, | ||||
| 			b.uintptrType, | ||||
| 		}, false)) | ||||
| 		elementType := b.getLLVMType(argTypes[0].Underlying().(*types.Pointer).Elem()) | ||||
| 		b.createUnsafeSliceCheck(ptr, len, elementType, argTypes[1].Underlying().(*types.Basic)) | ||||
| 		var elementType llvm.Type | ||||
| 		if callName == "Slice" { | ||||
| 			elementType = b.getLLVMType(argTypes[0].Underlying().(*types.Pointer).Elem()) | ||||
| 		} else { | ||||
| 			elementType = b.ctx.Int8Type() | ||||
| 		} | ||||
| 		b.createUnsafeSliceStringCheck("unsafe."+callName, ptr, len, elementType, argTypes[1].Underlying().(*types.Basic)) | ||||
| 		if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() { | ||||
| 			// Too small, zero-extend len. | ||||
| 			len = b.CreateZExt(len, b.uintptrType, "") | ||||
|  | @ -1667,10 +1667,24 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c | |||
| 			// Too big, truncate len. | ||||
| 			len = b.CreateTrunc(len, b.uintptrType, "") | ||||
| 		} | ||||
| 		if callName == "Slice" { | ||||
| 			slice := llvm.Undef(b.ctx.StructType([]llvm.Type{ | ||||
| 				ptr.Type(), | ||||
| 				b.uintptrType, | ||||
| 				b.uintptrType, | ||||
| 			}, false)) | ||||
| 			slice = b.CreateInsertValue(slice, ptr, 0, "") | ||||
| 			slice = b.CreateInsertValue(slice, len, 1, "") | ||||
| 			slice = b.CreateInsertValue(slice, len, 2, "") | ||||
| 			return slice, nil | ||||
| 		} else { | ||||
| 			str := llvm.Undef(b.getLLVMRuntimeType("_string")) | ||||
| 			str = b.CreateInsertValue(str, argValues[0], 0, "") | ||||
| 			str = b.CreateInsertValue(str, len, 1, "") | ||||
| 			return str, nil | ||||
| 		} | ||||
| 	case "SliceData", "StringData": // unsafe.SliceData, unsafe.StringData | ||||
| 		return b.CreateExtractValue(argValues[0], 0, "slice.data"), nil | ||||
| 	default: | ||||
| 		return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName) | ||||
| 	} | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import ( | |||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/tinygo-org/tinygo/compileopts" | ||||
| 	"github.com/tinygo-org/tinygo/goenv" | ||||
| 	"github.com/tinygo-org/tinygo/loader" | ||||
| 	"tinygo.org/x/go-llvm" | ||||
| ) | ||||
|  | @ -27,6 +28,12 @@ type testCase struct { | |||
| func TestCompiler(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 
 | ||||
| 	// Determine Go minor version (e.g. 16 in go1.16.3). | ||||
| 	_, goMinor, err := goenv.GetGorootVersion(goenv.Get("GOROOT")) | ||||
| 	if err != nil { | ||||
| 		t.Fatal("could not read Go version:", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Determine which tests to run, depending on the Go and LLVM versions. | ||||
| 	tests := []testCase{ | ||||
| 		{"basic.go", "", ""}, | ||||
|  | @ -43,6 +50,9 @@ func TestCompiler(t *testing.T) { | |||
| 		{"channel.go", "", ""}, | ||||
| 		{"gc.go", "", ""}, | ||||
| 	} | ||||
| 	if goMinor >= 20 { | ||||
| 		tests = append(tests, testCase{"go1.20.go", "", ""}) | ||||
| 	} | ||||
| 
 | ||||
| 	for _, tc := range tests { | ||||
| 		name := tc.file | ||||
|  |  | |||
							
								
								
									
										15
									
								
								compiler/testdata/go1.20.go
									
										
									
									
										предоставленный
									
									
										Обычный файл
									
								
							
							
						
						
									
										15
									
								
								compiler/testdata/go1.20.go
									
										
									
									
										предоставленный
									
									
										Обычный файл
									
								
							|  | @ -0,0 +1,15 @@ | |||
| package main | ||||
| 
 | ||||
| import "unsafe" | ||||
| 
 | ||||
| func unsafeSliceData(s []int) *int { | ||||
| 	return unsafe.SliceData(s) | ||||
| } | ||||
| 
 | ||||
| func unsafeString(ptr *byte, len int16) string { | ||||
| 	return unsafe.String(ptr, len) | ||||
| } | ||||
| 
 | ||||
| func unsafeStringData(s string) *byte { | ||||
| 	return unsafe.StringData(s) | ||||
| } | ||||
							
								
								
									
										58
									
								
								compiler/testdata/go1.20.ll
									
										
									
									
										предоставленный
									
									
										Обычный файл
									
								
							
							
						
						
									
										58
									
								
								compiler/testdata/go1.20.ll
									
										
									
									
										предоставленный
									
									
										Обычный файл
									
								
							|  | @ -0,0 +1,58 @@ | |||
| ; ModuleID = 'go1.20.go' | ||||
| source_filename = "go1.20.go" | ||||
| target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" | ||||
| target triple = "wasm32-unknown-wasi" | ||||
| 
 | ||||
| %runtime._string = type { ptr, i32 } | ||||
| 
 | ||||
| declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 | ||||
| 
 | ||||
| declare void @runtime.trackPointer(ptr nocapture readonly, ptr) #0 | ||||
| 
 | ||||
| ; Function Attrs: nounwind | ||||
| define hidden void @main.init(ptr %context) unnamed_addr #1 { | ||||
| entry: | ||||
|   ret void | ||||
| } | ||||
| 
 | ||||
| ; Function Attrs: nounwind | ||||
| define hidden ptr @main.unsafeSliceData(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #1 { | ||||
| entry: | ||||
|   call void @runtime.trackPointer(ptr %s.data, ptr undef) #2 | ||||
|   ret ptr %s.data | ||||
| } | ||||
| 
 | ||||
| ; Function Attrs: nounwind | ||||
| define hidden %runtime._string @main.unsafeString(ptr dereferenceable_or_null(1) %ptr, i16 %len, ptr %context) unnamed_addr #1 { | ||||
| entry: | ||||
|   %0 = icmp slt i16 %len, 0 | ||||
|   %1 = icmp eq ptr %ptr, null | ||||
|   %2 = icmp ne i16 %len, 0 | ||||
|   %3 = and i1 %1, %2 | ||||
|   %4 = or i1 %3, %0 | ||||
|   br i1 %4, label %unsafe.String.throw, label %unsafe.String.next | ||||
| 
 | ||||
| unsafe.String.next:                               ; preds = %entry | ||||
|   %5 = zext i16 %len to i32 | ||||
|   %6 = insertvalue %runtime._string undef, ptr %ptr, 0 | ||||
|   %7 = insertvalue %runtime._string %6, i32 %5, 1 | ||||
|   call void @runtime.trackPointer(ptr %ptr, ptr undef) #2 | ||||
|   ret %runtime._string %7 | ||||
| 
 | ||||
| unsafe.String.throw:                              ; preds = %entry | ||||
|   call void @runtime.unsafeSlicePanic(ptr undef) #2 | ||||
|   unreachable | ||||
| } | ||||
| 
 | ||||
| declare void @runtime.unsafeSlicePanic(ptr) #0 | ||||
| 
 | ||||
| ; Function Attrs: nounwind | ||||
| define hidden ptr @main.unsafeStringData(ptr %s.data, i32 %s.len, ptr %context) unnamed_addr #1 { | ||||
| entry: | ||||
|   call void @runtime.trackPointer(ptr %s.data, ptr undef) #2 | ||||
|   ret ptr %s.data | ||||
| } | ||||
| 
 | ||||
| attributes #0 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } | ||||
| attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } | ||||
| attributes #2 = { nounwind } | ||||
|  | @ -143,10 +143,11 @@ func sliceToArrayPointerPanic() { | |||
| 	runtimePanic("slice smaller than array") | ||||
| } | ||||
| 
 | ||||
| // Panic when calling unsafe.Slice() (Go 1.17+) with a len that's too large | ||||
| // (which includes if the ptr is nil and len is nonzero). | ||||
| // Panic when calling unsafe.Slice() (Go 1.17+) or unsafe.String() (Go 1.20+) | ||||
| // with a len that's too large (which includes if the ptr is nil and len is | ||||
| // nonzero). | ||||
| func unsafeSlicePanic() { | ||||
| 	runtimePanic("unsafe.Slice: len out of range") | ||||
| 	runtimePanic("unsafe.Slice/String: len out of range") | ||||
| } | ||||
| 
 | ||||
| // Panic when trying to create a new channel that is too big. | ||||
|  |  | |||
		Загрузка…
	
	Создание таблицы
		
		Сослаться в новой задаче
	
	 Ayke van Laethem
						Ayke van Laethem