Move interface method calls in Go from LLVM IR + documentation
This commit moves the itfmethod call implemented directly in LLVM IR to a Go implementation in the runtime. Additionally, it fixes variable names to be more obvious and adds a lot of documentation to explain how interfaces actually work in TinyGo. Code size changes for src/examples/hello: nrf: -144 unix: -93 I'm guessing this code size reduction is a result of removing the 'noinline' function attribute.
Этот коммит содержится в:
		
							родитель
							
								
									309de00fd6
								
							
						
					
					
						коммит
						539de9db9e
					
				
					 3 изменённых файлов: 88 добавлений и 69 удалений
				
			
		
							
								
								
									
										56
									
								
								compiler.go
									
										
									
									
									
								
							
							
						
						
									
										56
									
								
								compiler.go
									
										
									
									
									
								
							|  | @ -368,23 +368,24 @@ func (c *Compiler) Parse(mainPath string, buildTags []string) error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Initialize runtime type information, for interfaces. | 	// Initialize runtime type information, for interfaces. | ||||||
|  | 	// See src/runtime/interface.go for more details. | ||||||
| 	dynamicTypes := c.ir.AllDynamicTypes() | 	dynamicTypes := c.ir.AllDynamicTypes() | ||||||
| 	numDynamicTypes := 0 | 	numDynamicTypes := 0 | ||||||
| 	for _, meta := range dynamicTypes { | 	for _, meta := range dynamicTypes { | ||||||
| 		numDynamicTypes += len(meta.Methods) | 		numDynamicTypes += len(meta.Methods) | ||||||
| 	} | 	} | ||||||
| 	tuples := make([]llvm.Value, 0, len(dynamicTypes)) | 	ranges := make([]llvm.Value, 0, len(dynamicTypes)) | ||||||
| 	funcPointers := make([]llvm.Value, 0, numDynamicTypes) | 	funcPointers := make([]llvm.Value, 0, numDynamicTypes) | ||||||
| 	signatures := make([]llvm.Value, 0, numDynamicTypes) | 	signatures := make([]llvm.Value, 0, numDynamicTypes) | ||||||
| 	startIndex := 0 | 	startIndex := 0 | ||||||
| 	tupleType := c.mod.GetTypeByName("interface_tuple") | 	rangeType := c.mod.GetTypeByName("runtime.methodSetRange") | ||||||
| 	for _, meta := range dynamicTypes { | 	for _, meta := range dynamicTypes { | ||||||
| 		tupleValues := []llvm.Value{ | 		rangeValues := []llvm.Value{ | ||||||
| 			llvm.ConstInt(llvm.Int32Type(), uint64(startIndex), false), | 			llvm.ConstInt(llvm.Int32Type(), uint64(startIndex), false), | ||||||
| 			llvm.ConstInt(llvm.Int32Type(), uint64(len(meta.Methods)), false), | 			llvm.ConstInt(llvm.Int32Type(), uint64(len(meta.Methods)), false), | ||||||
| 		} | 		} | ||||||
| 		tuple := llvm.ConstNamedStruct(tupleType, tupleValues) | 		rangeValue := llvm.ConstNamedStruct(rangeType, rangeValues) | ||||||
| 		tuples = append(tuples, tuple) | 		ranges = append(ranges, rangeValue) | ||||||
| 		for _, method := range meta.Methods { | 		for _, method := range meta.Methods { | ||||||
| 			f := c.ir.GetFunction(program.MethodValue(method)) | 			f := c.ir.GetFunction(program.MethodValue(method)) | ||||||
| 			if f.llvmFn.IsNil() { | 			if f.llvmFn.IsNil() { | ||||||
|  | @ -398,33 +399,34 @@ func (c *Compiler) Parse(mainPath string, buildTags []string) error { | ||||||
| 		} | 		} | ||||||
| 		startIndex += len(meta.Methods) | 		startIndex += len(meta.Methods) | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	// Replace the pre-created arrays with the generated arrays. | 	// Replace the pre-created arrays with the generated arrays. | ||||||
| 	tupleArray := llvm.ConstArray(tupleType, tuples) | 	rangeArray := llvm.ConstArray(rangeType, ranges) | ||||||
| 	tupleArrayNewGlobal := llvm.AddGlobal(c.mod, tupleArray.Type(), "interface_tuples.tmp") | 	rangeArrayNewGlobal := llvm.AddGlobal(c.mod, rangeArray.Type(), "runtime.methodSetRanges.tmp") | ||||||
| 	tupleArrayNewGlobal.SetInitializer(tupleArray) | 	rangeArrayNewGlobal.SetInitializer(rangeArray) | ||||||
| 	tupleArrayNewGlobal.SetLinkage(llvm.PrivateLinkage) | 	rangeArrayNewGlobal.SetLinkage(llvm.PrivateLinkage) | ||||||
| 	tupleArrayOldGlobal := c.mod.NamedGlobal("interface_tuples") | 	rangeArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetRanges") | ||||||
| 	tupleArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(tupleArrayNewGlobal, tupleArrayOldGlobal.Type())) | 	rangeArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(rangeArrayNewGlobal, rangeArrayOldGlobal.Type())) | ||||||
| 	tupleArrayOldGlobal.EraseFromParentAsGlobal() | 	rangeArrayOldGlobal.EraseFromParentAsGlobal() | ||||||
| 	tupleArrayNewGlobal.SetName("interface_tuples") | 	rangeArrayNewGlobal.SetName("runtime.methodSetRanges") | ||||||
| 	funcArray := llvm.ConstArray(c.i8ptrType, funcPointers) | 	funcArray := llvm.ConstArray(c.i8ptrType, funcPointers) | ||||||
| 	funcArrayNewGlobal := llvm.AddGlobal(c.mod, funcArray.Type(), "interface_functions.tmp") | 	funcArrayNewGlobal := llvm.AddGlobal(c.mod, funcArray.Type(), "runtime.methodSetFunctions.tmp") | ||||||
| 	funcArrayNewGlobal.SetInitializer(funcArray) | 	funcArrayNewGlobal.SetInitializer(funcArray) | ||||||
| 	funcArrayNewGlobal.SetLinkage(llvm.PrivateLinkage) | 	funcArrayNewGlobal.SetLinkage(llvm.PrivateLinkage) | ||||||
| 	funcArrayOldGlobal := c.mod.NamedGlobal("interface_functions") | 	funcArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetFunctions") | ||||||
| 	funcArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(funcArrayNewGlobal, funcArrayOldGlobal.Type())) | 	funcArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(funcArrayNewGlobal, funcArrayOldGlobal.Type())) | ||||||
| 	funcArrayOldGlobal.EraseFromParentAsGlobal() | 	funcArrayOldGlobal.EraseFromParentAsGlobal() | ||||||
| 	funcArrayNewGlobal.SetName("interface_functions") | 	funcArrayNewGlobal.SetName("runtime.methodSetFunctions") | ||||||
| 	signatureArray := llvm.ConstArray(llvm.Int32Type(), signatures) | 	signatureArray := llvm.ConstArray(llvm.Int32Type(), signatures) | ||||||
| 	signatureArrayNewGlobal := llvm.AddGlobal(c.mod, signatureArray.Type(), "interface_signatures.tmp") | 	signatureArrayNewGlobal := llvm.AddGlobal(c.mod, signatureArray.Type(), "runtime.methodSetSignatures.tmp") | ||||||
| 	signatureArrayNewGlobal.SetInitializer(signatureArray) | 	signatureArrayNewGlobal.SetInitializer(signatureArray) | ||||||
| 	signatureArrayNewGlobal.SetLinkage(llvm.PrivateLinkage) | 	signatureArrayNewGlobal.SetLinkage(llvm.PrivateLinkage) | ||||||
| 	signatureArrayOldGlobal := c.mod.NamedGlobal("interface_signatures") | 	signatureArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetSignatures") | ||||||
| 	signatureArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(signatureArrayNewGlobal, signatureArrayOldGlobal.Type())) | 	signatureArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(signatureArrayNewGlobal, signatureArrayOldGlobal.Type())) | ||||||
| 	signatureArrayOldGlobal.EraseFromParentAsGlobal() | 	signatureArrayOldGlobal.EraseFromParentAsGlobal() | ||||||
| 	signatureArrayNewGlobal.SetName("interface_signatures") | 	signatureArrayNewGlobal.SetName("runtime.methodSetSignatures") | ||||||
| 
 | 
 | ||||||
| 	c.mod.NamedGlobal("first_interface_num").SetInitializer(llvm.ConstInt(llvm.Int32Type(), uint64(c.ir.FirstDynamicType()), false)) | 	c.mod.NamedGlobal("runtime.firstInterfaceNum").SetInitializer(llvm.ConstInt(llvm.Int32Type(), uint64(c.ir.FirstDynamicType()), false)) | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  | @ -461,7 +463,7 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) { | ||||||
| 			return llvm.Type{}, errors.New("todo: unknown basic type: " + typ.String()) | 			return llvm.Type{}, errors.New("todo: unknown basic type: " + typ.String()) | ||||||
| 		} | 		} | ||||||
| 	case *types.Interface: | 	case *types.Interface: | ||||||
| 		return c.mod.GetTypeByName("interface"), nil | 		return c.mod.GetTypeByName("runtime._interface"), nil | ||||||
| 	case *types.Map: | 	case *types.Map: | ||||||
| 		return llvm.PointerType(c.mod.GetTypeByName("runtime.hashmap"), 0), nil | 		return llvm.PointerType(c.mod.GetTypeByName("runtime.hashmap"), 0), nil | ||||||
| 	case *types.Named: | 	case *types.Named: | ||||||
|  | @ -500,7 +502,7 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) { | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return llvm.Type{}, err | 				return llvm.Type{}, err | ||||||
| 			} | 			} | ||||||
| 			if recv.StructName() == "interface" { | 			if recv.StructName() == "runtime._interface" { | ||||||
| 				recv = c.i8ptrType | 				recv = c.i8ptrType | ||||||
| 			} | 			} | ||||||
| 			paramTypes = append(paramTypes, recv) | 			paramTypes = append(paramTypes, recv) | ||||||
|  | @ -748,7 +750,7 @@ func (c *Compiler) getInterpretedValue(value Value) (llvm.Value, error) { | ||||||
| 			llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false), | 			llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false), | ||||||
| 			llvm.Undef(c.i8ptrType), | 			llvm.Undef(c.i8ptrType), | ||||||
| 		} | 		} | ||||||
| 		itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("interface"), fields) | 		itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), fields) | ||||||
| 		return itf, nil | 		return itf, nil | ||||||
| 
 | 
 | ||||||
| 	case *MapValue: | 	case *MapValue: | ||||||
|  | @ -1334,7 +1336,7 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon, parentHandle l | ||||||
| 			itf, | 			itf, | ||||||
| 			llvm.ConstInt(llvm.Int32Type(), uint64(c.ir.MethodNum(instr.Method)), false), | 			llvm.ConstInt(llvm.Int32Type(), uint64(c.ir.MethodNum(instr.Method)), false), | ||||||
| 		} | 		} | ||||||
| 		fn := c.builder.CreateCall(c.mod.NamedFunction("itfmethod"), values, "invoke.func") | 		fn := c.builder.CreateCall(c.mod.NamedFunction("runtime.itfmethod"), values, "invoke.func") | ||||||
| 		fnCast := c.builder.CreateBitCast(fn, llvmFnType, "invoke.func.cast") | 		fnCast := c.builder.CreateBitCast(fn, llvmFnType, "invoke.func.cast") | ||||||
| 		receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver") | 		receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver") | ||||||
| 		args := []llvm.Value{receiverValue} | 		args := []llvm.Value{receiverValue} | ||||||
|  | @ -1501,7 +1503,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { | ||||||
| 		// TODO: runtime.lookupBoundsCheck is undefined in packages imported by | 		// TODO: runtime.lookupBoundsCheck is undefined in packages imported by | ||||||
| 		// package runtime, so we have to remove it. This should be fixed. | 		// package runtime, so we have to remove it. This should be fixed. | ||||||
| 		lookupBoundsCheck := c.mod.NamedFunction("runtime.lookupBoundsCheck") | 		lookupBoundsCheck := c.mod.NamedFunction("runtime.lookupBoundsCheck") | ||||||
| 		if !lookupBoundsCheck.IsNil() { | 		if !lookupBoundsCheck.IsNil() && frame.fn.llvmFn.Name() != "runtime.itfmethod" { | ||||||
| 			c.builder.CreateCall(lookupBoundsCheck, []llvm.Value{buflen, index}, "") | 			c.builder.CreateCall(lookupBoundsCheck, []llvm.Value{buflen, index}, "") | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -1607,7 +1609,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		itfTypeNum, _ := c.ir.TypeNum(expr.X.Type()) | 		itfTypeNum, _ := c.ir.TypeNum(expr.X.Type()) | ||||||
| 		itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("interface"), []llvm.Value{llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false), llvm.Undef(c.i8ptrType)}) | 		itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), []llvm.Value{llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false), llvm.Undef(c.i8ptrType)}) | ||||||
| 		itf = c.builder.CreateInsertValue(itf, itfValue, 1, "") | 		itf = c.builder.CreateInsertValue(itf, itfValue, 1, "") | ||||||
| 		return itf, nil | 		return itf, nil | ||||||
| 	case *ssa.MakeMap: | 	case *ssa.MakeMap: | ||||||
|  | @ -1938,7 +1940,7 @@ func (c *Compiler) parseConst(expr *ssa.Const) (llvm.Value, error) { | ||||||
| 			llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false), | 			llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false), | ||||||
| 			llvm.Undef(c.i8ptrType), | 			llvm.Undef(c.i8ptrType), | ||||||
| 		} | 		} | ||||||
| 		itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("interface"), fields) | 		itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), fields) | ||||||
| 		return itf, nil | 		return itf, nil | ||||||
| 	case *types.Pointer: | 	case *types.Pointer: | ||||||
| 		if expr.Value != nil { | 		if expr.Value != nil { | ||||||
|  |  | ||||||
							
								
								
									
										59
									
								
								src/runtime/interface.go
									
										
									
									
									
										Обычный файл
									
								
							
							
						
						
									
										59
									
								
								src/runtime/interface.go
									
										
									
									
									
										Обычный файл
									
								
							|  | @ -0,0 +1,59 @@ | ||||||
|  | package runtime | ||||||
|  | 
 | ||||||
|  | // This file implements Go interfaces. | ||||||
|  | // | ||||||
|  | // Interfaces are represented as a pair of {typecode, value}, where value can be | ||||||
|  | // anything (including non-pointers). | ||||||
|  | // | ||||||
|  | // Signatures itself are not matched on strings, but on uniqued numbers that | ||||||
|  | // contain the name and the signature of the function (to save space), think of | ||||||
|  | // signatures as interned strings at compile time. | ||||||
|  | // | ||||||
|  | // The typecode is a small number unique for the Go type. All typecodes < | ||||||
|  | // firstInterfaceNum do not have any methods and typecodes >= firstInterfaceNum | ||||||
|  | // all have at least one method. This means that methodSetRanges does not need | ||||||
|  | // to contain types without methods and is thus indexed starting at a typecode | ||||||
|  | // with number firstInterfaceNum. | ||||||
|  | // | ||||||
|  | // To further conserve some space, the methodSetRange (as the name indicates) | ||||||
|  | // doesn't contain a list of methods and function pointers directly, but instead | ||||||
|  | // just indexes into methodSetSignatures and methodSetFunctions which contains | ||||||
|  | // the mapping from uniqued signature to function pointer. | ||||||
|  | 
 | ||||||
|  | type _interface struct { | ||||||
|  | 	typecode uint32 | ||||||
|  | 	value    *uint8 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // This struct indicates the range of methods in the methodSetSignatures and | ||||||
|  | // methodSetFunctions arrays that belong to this named type. | ||||||
|  | type methodSetRange struct { | ||||||
|  | 	index  uint32 // start index into interfaceSignatures and interfaceFunctions | ||||||
|  | 	length uint32 // number of methods | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Global constants that will be set by the compiler. The arrays are of size 0, | ||||||
|  | // which is a dummy value, but will be bigger after the compiler has filled them | ||||||
|  | // in. | ||||||
|  | var ( | ||||||
|  | 	methodSetRanges     [0]methodSetRange // indexes into methodSetSignatures and methodSetFunctions | ||||||
|  | 	methodSetSignatures [0]uint32         // uniqued method ID | ||||||
|  | 	methodSetFunctions  [0]*uint8         // function pointer of method | ||||||
|  | 	firstInterfaceNum   uint32            // the lowest typecode that has at least one method | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Get the function pointer for the method on the interface. | ||||||
|  | // This is a compiler intrinsic. | ||||||
|  | func itfmethod(itf _interface, method uint32) *uint8 { | ||||||
|  | 	// This function doesn't do bounds checking as the supplied method must be | ||||||
|  | 	// in the list of signatures. The compiler will only emit runtime.itfmethod | ||||||
|  | 	// calls when the method actually exists on this interface (proven by the | ||||||
|  | 	// typechecker). | ||||||
|  | 	i := methodSetRanges[itf.typecode-firstInterfaceNum].index | ||||||
|  | 	for { | ||||||
|  | 		if methodSetSignatures[i] == method { | ||||||
|  | 			return methodSetFunctions[i] | ||||||
|  | 		} | ||||||
|  | 		i++ | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -1,7 +1,5 @@ | ||||||
| source_filename = "runtime/runtime.ll" | source_filename = "runtime/runtime.ll" | ||||||
| 
 | 
 | ||||||
| %interface = type { i32, i8* } |  | ||||||
| 
 |  | ||||||
| declare void @runtime.initAll() | declare void @runtime.initAll() | ||||||
| declare void @main.main() | declare void @main.main() | ||||||
| declare i8* @main.main$async(i8*) | declare i8* @main.main$async(i8*) | ||||||
|  | @ -10,15 +8,6 @@ declare void @runtime.scheduler(i8*) | ||||||
| ; Will be changed to true if there are 'go' statements in the compiled program. | ; Will be changed to true if there are 'go' statements in the compiled program. | ||||||
| @has_scheduler = private unnamed_addr constant i1 false | @has_scheduler = private unnamed_addr constant i1 false | ||||||
| 
 | 
 | ||||||
| ; Will be changed by the compiler to the first type number with methods. |  | ||||||
| @first_interface_num = private unnamed_addr constant i32 0 |  | ||||||
| 
 |  | ||||||
| ; Will be filled by the compiler with runtime type information. |  | ||||||
| %interface_tuple = type { i32, i32 } ; { index, len } |  | ||||||
| @interface_tuples = external global [0 x %interface_tuple] |  | ||||||
| @interface_signatures = external global [0 x i32] ; array of method IDs |  | ||||||
| @interface_functions = external global [0 x i8*] ; array of function pointers |  | ||||||
| 
 |  | ||||||
| define i32 @main() { | define i32 @main() { | ||||||
|     call void @runtime.initAll() |     call void @runtime.initAll() | ||||||
|     %has_scheduler = load i1, i1* @has_scheduler |     %has_scheduler = load i1, i1* @has_scheduler | ||||||
|  | @ -36,34 +25,3 @@ without_scheduler: | ||||||
|     call void @main.main() |     call void @main.main() | ||||||
|     ret i32 0 |     ret i32 0 | ||||||
| } | } | ||||||
| 
 |  | ||||||
| ; Get the function pointer for the method on the interface. |  | ||||||
| ; This function only reads constant global data and it's own arguments so it can |  | ||||||
| ; be 'readnone' (a pure function). |  | ||||||
| define i8* @itfmethod(%interface %itf, i32 %method) noinline readnone { |  | ||||||
| entry: |  | ||||||
|     ; Calculate the index in @interface_tuples |  | ||||||
|     %concrete_type_num = extractvalue %interface %itf, 0 |  | ||||||
|     %first_interface_num = load i32, i32* @first_interface_num |  | ||||||
|     %index = sub i32 %concrete_type_num, %first_interface_num |  | ||||||
| 
 |  | ||||||
|     ; Calculate the index for @interface_signatures and @interface_functions |  | ||||||
|     %itf_index_ptr = getelementptr inbounds [0 x %interface_tuple], [0 x %interface_tuple]* @interface_tuples, i32 0, i32 %index, i32 0 |  | ||||||
|     %itf_index = load i32, i32* %itf_index_ptr |  | ||||||
|     br label %find_method |  | ||||||
| 
 |  | ||||||
|     ; This is a while loop until the method has been found. |  | ||||||
|     ; It must be in here, so avoid checking the length. |  | ||||||
| find_method: |  | ||||||
|     %itf_index.phi = phi i32 [ %itf_index, %entry], [ %itf_index.phi.next, %find_method] |  | ||||||
|     %m_ptr = getelementptr inbounds [0 x i32], [0 x i32]* @interface_signatures, i32 0, i32 %itf_index.phi |  | ||||||
|     %m = load i32, i32* %m_ptr |  | ||||||
|     %found = icmp eq i32 %m, %method |  | ||||||
|     %itf_index.phi.next = add i32 %itf_index.phi, 1 |  | ||||||
|     br i1 %found, label %found_method, label %find_method |  | ||||||
| 
 |  | ||||||
| found_method: |  | ||||||
|     %fp_ptr = getelementptr inbounds [0 x i8*], [0 x i8*]* @interface_functions, i32 0, i32 %itf_index.phi |  | ||||||
|     %fp = load i8*, i8** %fp_ptr |  | ||||||
|     ret i8* %fp |  | ||||||
| } |  | ||||||
|  |  | ||||||
		Загрузка…
	
	Создание таблицы
		
		Сослаться в новой задаче
	
	 Ayke van Laethem
						Ayke van Laethem