diff --git a/compiler/compiler.go b/compiler/compiler.go index e9d06349..6cae54a5 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -23,7 +23,7 @@ import ( // Version of the compiler pacakge. Must be incremented each time the compiler // package changes in a way that affects the generated LLVM module. // This version is independent of the TinyGo version number. -const Version = 2 // last change: adding wasm-export-name attribute +const Version = 3 // last change: remove runtime.typeInInterface func init() { llvm.InitializeAllTargets() diff --git a/compiler/interface.go b/compiler/interface.go index b21476b6..b6ca8492 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -24,16 +24,7 @@ import ( func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value { itfValue := b.emitPointerPack([]llvm.Value{val}) itfTypeCodeGlobal := b.getTypeCode(typ) - itfMethodSetGlobal := b.getTypeMethodSet(typ) - itfConcreteTypeGlobal := b.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name()) - if itfConcreteTypeGlobal.IsNil() { - typeInInterface := b.getLLVMRuntimeType("typeInInterface") - itfConcreteTypeGlobal = llvm.AddGlobal(b.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name()) - itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal})) - itfConcreteTypeGlobal.SetGlobalConstant(true) - itfConcreteTypeGlobal.SetLinkage(llvm.LinkOnceODRLinkage) - } - itfTypeCode := b.CreatePtrToInt(itfConcreteTypeGlobal, b.uintptrType, "") + itfTypeCode := b.CreatePtrToInt(itfTypeCodeGlobal, b.uintptrType, "") itf := llvm.Undef(b.getLLVMRuntimeType("_interface")) itf = b.CreateInsertValue(itf, itfTypeCode, 0, "") itf = b.CreateInsertValue(itf, itfValue, 1, "") @@ -54,6 +45,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { // reflect lowering simpler. var references llvm.Value var length int64 + var methodSet llvm.Value switch typ := typ.(type) { case *types.Named: references = c.getTypeCode(typ.Underlying()) @@ -71,14 +63,22 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { structGlobal := c.makeStructTypeFields(typ) references = llvm.ConstBitCast(structGlobal, global.Type()) } - if !references.IsNil() { + if _, ok := typ.Underlying().(*types.Interface); !ok { + methodSet = c.getTypeMethodSet(typ) + } + if !references.IsNil() || length != 0 || !methodSet.IsNil() { // Set the 'references' field of the runtime.typecodeID struct. globalValue := llvm.ConstNull(global.Type().ElementType()) - globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0}) + if !references.IsNil() { + globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0}) + } if length != 0 { lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false) globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1}) } + if !methodSet.IsNil() { + globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2}) + } global.SetInitializer(globalValue) global.SetLinkage(llvm.LinkOnceODRLinkage) } diff --git a/interp/interpreter.go b/interp/interpreter.go index d5e36215..44e5a900 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -286,11 +286,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent if r.debug { fmt.Fprintln(os.Stderr, indent+"typeassert:", operands[1:]) } - typeInInterfacePtr, err := operands[1].asPointer(r) - if err != nil { - return nil, mem, r.errorAt(inst, err) - } - actualType, err := mem.load(typeInInterfacePtr, r.pointerSize).asPointer(r) + actualType, err := operands[1].asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } @@ -310,11 +306,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } // Load various values for the interface implements check below. - typeInInterfacePtr, err := operands[1].asPointer(r) + typecodePtr, err := operands[1].asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } - methodSetPtr, err := mem.load(typeInInterfacePtr.addOffset(r.pointerSize), r.pointerSize).asPointer(r) + methodSetPtr, err := mem.load(typecodePtr.addOffset(r.pointerSize*2), r.pointerSize).asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } diff --git a/interp/memory.go b/interp/memory.go index 4c66b537..4504ba2c 100644 --- a/interp/memory.go +++ b/interp/memory.go @@ -1048,7 +1048,7 @@ func (v rawValue) rawLLVMValue(mem *memoryView) llvm.Value { // There are some special pointer types that should be used as a // ptrtoint, so that they can be used in certain optimizations. name := elementType.StructName() - if name == "runtime.typeInInterface" || name == "runtime.funcValueWithSignature" { + if name == "runtime.typecodeID" || name == "runtime.funcValueWithSignature" { uintptrType := ctx.IntType(int(mem.r.pointerSize) * 8) field = llvm.ConstPtrToInt(field, uintptrType) } diff --git a/interp/testdata/interface.ll b/interp/testdata/interface.ll index 22378b69..5b7798ba 100644 --- a/interp/testdata/interface.ll +++ b/interp/testdata/interface.ll @@ -1,14 +1,12 @@ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64--linux" -%runtime.typecodeID = type { %runtime.typecodeID*, i64 } +%runtime.typecodeID = type { %runtime.typecodeID*, i64, %runtime.interfaceMethodInfo* } %runtime.interfaceMethodInfo = type { i8*, i64 } -%runtime.typeInInterface = type { %runtime.typecodeID*, %runtime.interfaceMethodInfo* } @main.v1 = global i1 0 -@"reflect/types.type:named:main.foo" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i64 0 } +@"reflect/types.type:named:main.foo" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i64 0, %runtime.interfaceMethodInfo* null } @"reflect/types.type:basic:int" = external constant %runtime.typecodeID -@"typeInInterface:reflect/types.type:named:main.foo" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:named:main.foo", %runtime.interfaceMethodInfo* null } declare i1 @runtime.typeAssert(i64, %runtime.typecodeID*, i8*, i8*) @@ -22,7 +20,7 @@ entry: define internal void @main.init() unnamed_addr { entry: ; Test type asserts. - %typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:named:main.foo" to i64), %runtime.typecodeID* @"reflect/types.type:named:main.foo", i8* undef, i8* null) + %typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:main.foo" to i64), %runtime.typecodeID* @"reflect/types.type:named:main.foo", i8* undef, i8* null) store i1 %typecode, i1* @main.v1 ret void } diff --git a/src/internal/task/task_coroutine.go b/src/internal/task/task_coroutine.go index 33d910ed..3a267cb9 100644 --- a/src/internal/task/task_coroutine.go +++ b/src/internal/task/task_coroutine.go @@ -11,7 +11,7 @@ import ( type rawState uint8 //export llvm.coro.resume -func (s *rawState) resume() +func coroResume(*rawState) type state struct{ *rawState } @@ -20,7 +20,7 @@ func noopState() *rawState // Resume the task until it pauses or completes. func (t *Task) Resume() { - t.state.resume() + coroResume(t.state.rawState) } // setState is used by the compiler to set the state of the function at the beginning of a function call. diff --git a/src/runtime/interface.go b/src/runtime/interface.go index 870ba6ab..0d38389b 100644 --- a/src/runtime/interface.go +++ b/src/runtime/interface.go @@ -107,6 +107,8 @@ type typecodeID struct { // The array length, for array types. length uintptr + + methodSet *interfaceMethodInfo // nil or a GEP of an array } // structField is used by the compiler to pass information to the interface @@ -118,15 +120,6 @@ type structField struct { embedded bool } -// Pseudo type used before interface lowering. By using a struct instead of a -// function call, this is simpler to reason about during init interpretation -// than a function call. Also, by keeping the method set around it is easier to -// implement interfaceImplements in the interp package. -type typeInInterface struct { - typecode *typecodeID // element type, underlying type, or reference to struct fields - methodSet *interfaceMethodInfo // nil or a GEP of an array -} - // Pseudo function call used during a type assert. It is used during interface // lowering, to assign the lowest type numbers to the types with the most type // asserts. Also, it is replaced with const false if this type assert can never diff --git a/testdata/reflect.go b/testdata/reflect.go index dcd64a5d..ba81fe96 100644 --- a/testdata/reflect.go +++ b/testdata/reflect.go @@ -265,6 +265,19 @@ func main() { println("type assertion failed (but should succeed)") } + // Test type that is not referenced at all: not when creating the + // reflect.Value (except through the field) and not with a type assert. + // Previously this would result in a type assert failure because the Int() + // method wasn't picked up. + v = reflect.ValueOf(struct { + X totallyUnreferencedType + }{}) + if v.Field(0).Interface().(interface { + Int() int + }).Int() != 42 { + println("could not call method on totally unreferenced type") + } + if reflect.TypeOf(new(myint)) != reflect.PtrTo(reflect.TypeOf(myint(0))) { println("PtrTo failed for type myint") } @@ -363,6 +376,12 @@ func assertSize(ok bool, typ string) { type unreferencedType int +type totallyUnreferencedType int + +func (totallyUnreferencedType) Int() int { + return 42 +} + func TestStructTag() { type S struct { F string `species:"gopher" color:"blue"` diff --git a/transform/interface-lowering.go b/transform/interface-lowering.go index 19546c58..34aa6915 100644 --- a/transform/interface-lowering.go +++ b/transform/interface-lowering.go @@ -46,6 +46,7 @@ import ( // any method in particular. type signatureInfo struct { name string + global llvm.Value methods []*methodInfo interfaces []*interfaceInfo } @@ -73,13 +74,12 @@ type methodInfo struct { // typeInfo describes a single concrete Go type, which can be a basic or a named // type. If it is a named type, it may have methods. type typeInfo struct { - name string - typecode llvm.Value - methodSet llvm.Value - num uint64 // the type number after lowering - countMakeInterfaces int // how often this type is used in an interface - countTypeAsserts int // how often a type assert happens on this method - methods []*methodInfo + name string + typecode llvm.Value + methodSet llvm.Value + num uint64 // the type number after lowering + countTypeAsserts int // how often a type assert happens on this method + methods []*methodInfo } // getMethod looks up the method on this type with the given signature and @@ -104,9 +104,6 @@ func (t typeInfoSlice) Less(i, j int) bool { if t[i].countTypeAsserts != t[j].countTypeAsserts { return t[i].countTypeAsserts < t[j].countTypeAsserts } - if t[i].countMakeInterfaces != t[j].countMakeInterfaces { - return t[i].countMakeInterfaces < t[j].countMakeInterfaces - } return t[i].name < t[j].name } func (t typeInfoSlice) Swap(i, j int) { t[i], t[j] = t[j], t[i] } @@ -115,6 +112,7 @@ func (t typeInfoSlice) Swap(i, j int) { t[i], t[j] = t[j], t[i] } // methods it has. type interfaceInfo struct { name string // name with $interface suffix + methodSet llvm.Value // global which this interfaceInfo describes signatures []*signatureInfo // method set types typeInfoSlice // types this interface implements assertFunc llvm.Value // runtime.interfaceImplements replacement @@ -163,9 +161,9 @@ func LowerInterfaces(mod llvm.Module) error { // run runs the pass itself. func (p *lowerInterfacesPass) run() error { // Collect all type codes. - typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0) - typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0) - var typesInInterfaces []llvm.Value + typecodeID := p.mod.GetTypeByName("runtime.typecodeID") + typecodeIDPtr := llvm.PointerType(typecodeID, 0) + var typecodeIDs []llvm.Value for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) { switch global.Type() { case typecodeIDPtr: @@ -174,32 +172,19 @@ func (p *lowerInterfacesPass) run() error { // discarded afterwards. name := global.Name() if _, ok := p.types[name]; !ok { - p.types[name] = &typeInfo{ + typecodeIDs = append(typecodeIDs, global) + t := &typeInfo{ name: name, typecode: global, } - } - case typeInInterfacePtr: - // Count per type how often it is put in an interface. Also, collect - // all methods this type has (if it is named). - typesInInterfaces = append(typesInInterfaces, global) - initializer := global.Initializer() - typecode := llvm.ConstExtractValue(initializer, []uint32{0}) - methodSet := llvm.ConstExtractValue(initializer, []uint32{1}) - typecodeName := typecode.Name() - t := p.types[typecodeName] - if t == nil { - t = &typeInfo{ - name: typecodeName, - typecode: typecode, + p.types[name] = t + initializer := global.Initializer() + if initializer.IsNil() { + continue } - p.types[typecodeName] = t + methodSet := llvm.ConstExtractValue(initializer, []uint32{2}) + p.addTypeMethods(t, methodSet) } - p.addTypeMethods(t, methodSet) - - // Count the number of MakeInterface instructions, for sorting the - // typecodes later. - t.countMakeInterfaces += len(getUses(global)) } } @@ -363,18 +348,30 @@ func (p *lowerInterfacesPass) run() error { // Assign a type code for each type. assignTypeCodes(p.mod, typeSlice) - // Replace each use of a runtime.typeInInterface with the constant type + // Replace each use of a ptrtoint runtime.typecodeID with the constant type // code. - for _, global := range typesInInterfaces { + for _, global := range typecodeIDs { for _, use := range getUses(global) { - t := p.types[llvm.ConstExtractValue(global.Initializer(), []uint32{0}).Name()] + if use.IsAConstantExpr().IsNil() { + continue + } + t := p.types[global.Name()] typecode := llvm.ConstInt(p.uintptrType, t.num, false) + switch use.Opcode() { + case llvm.PtrToInt: + // Already of the correct type. + case llvm.BitCast: + // Could happen when stored in an interface (which is of type + // i8*). + typecode = llvm.ConstIntToPtr(typecode, use.Type()) + default: + panic("unexpected constant expression") + } use.ReplaceAllUsesWith(typecode) } } - // Replace each type assert with an actual type comparison or (if the type - // assert is impossible) the constant false. + // Replace each type assert with an actual type comparison. for _, use := range typeAssertUses { actualType := use.Operand(0) assertedTypeGlobal := use.Operand(1) @@ -405,20 +402,36 @@ func (p *lowerInterfacesPass) run() error { } } - // Remove stray runtime.typeInInterface globals. Required for the following - // cleanup. - for _, global := range typesInInterfaces { - global.EraseFromParentAsGlobal() - } - - // Remove method sets of types. Unnecessary, but cleans up the IR for - // inspection. + // Remove most objects created for interface and reflect lowering. + // Unnecessary, but cleans up the IR for inspection and testing. + zeroTypeCode := llvm.ConstNull(typecodeID) for _, typ := range p.types { + // Only some typecodes have an initializer. + initializer := typ.typecode.Initializer() + if !initializer.IsNil() { + references := llvm.ConstExtractValue(initializer, []uint32{0}) + typ.typecode.SetInitializer(zeroTypeCode) + if !references.IsAConstantExpr().IsNil() && references.Opcode() == llvm.BitCast { + // Structs have a 'references' field that is not a typecode but + // a pointer to a runtime.structField array and therefore a + // bitcast. This global should be erased separately, otherwise + // typecode objects cannot be erased. + structFields := references.Operand(0) + structFields.EraseFromParentAsGlobal() + } + } + if !typ.methodSet.IsNil() { typ.methodSet.EraseFromParentAsGlobal() typ.methodSet = llvm.Value{} } } + for _, itf := range p.interfaces { + // Remove method sets of interfaces. + itf.methodSet.EraseFromParentAsGlobal() + itf.methodSet = llvm.Value{} + } + return nil } @@ -437,9 +450,10 @@ func (p *lowerInterfacesPass) addTypeMethods(t *typeInfo, methodSet llvm.Value) set := methodSet.Initializer() // get value from global for i := 0; i < set.Type().ArrayLength(); i++ { methodData := llvm.ConstExtractValue(set, []uint32{uint32(i)}) - signatureName := llvm.ConstExtractValue(methodData, []uint32{0}).Name() + signatureGlobal := llvm.ConstExtractValue(methodData, []uint32{0}) + signatureName := signatureGlobal.Name() function := llvm.ConstExtractValue(methodData, []uint32{1}).Operand(0) - signature := p.getSignature(signatureName) + signature := p.getSignature(signatureName, signatureGlobal) method := &methodInfo{ function: function, signatureInfo: signature, @@ -454,13 +468,15 @@ func (p *lowerInterfacesPass) addTypeMethods(t *typeInfo, methodSet llvm.Value) func (p *lowerInterfacesPass) addInterface(methodSet llvm.Value) { name := methodSet.Name() t := &interfaceInfo{ - name: name, + name: name, + methodSet: methodSet, } p.interfaces[name] = t methodSet = methodSet.Initializer() // get global value from getelementptr for i := 0; i < methodSet.Type().ArrayLength(); i++ { - signatureName := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)}).Name() - signature := p.getSignature(signatureName) + signatureGlobal := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)}) + signatureName := signatureGlobal.Name() + signature := p.getSignature(signatureName, signatureGlobal) signature.interfaces = append(signature.interfaces, t) t.signatures = append(t.signatures, signature) } @@ -468,10 +484,11 @@ func (p *lowerInterfacesPass) addInterface(methodSet llvm.Value) { // getSignature returns a new *signatureInfo, creating it if it doesn't already // exist. -func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo { +func (p *lowerInterfacesPass) getSignature(name string, global llvm.Value) *signatureInfo { if _, ok := p.signatures[name]; !ok { p.signatures[name] = &signatureInfo{ - name: name, + name: name, + global: global, } } return p.signatures[name] diff --git a/transform/testdata/interface.ll b/transform/testdata/interface.ll index 1e2ce61b..c58e8a23 100644 --- a/transform/testdata/interface.ll +++ b/transform/testdata/interface.ll @@ -1,21 +1,17 @@ target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" target triple = "armv7m-none-eabi" -%runtime.typecodeID = type { %runtime.typecodeID*, i32 } -%runtime.typeInInterface = type { %runtime.typecodeID*, %runtime.interfaceMethodInfo* } +%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* } %runtime.interfaceMethodInfo = type { i8*, i32 } @"reflect/types.type:basic:uint8" = external constant %runtime.typecodeID @"reflect/types.type:basic:int" = external constant %runtime.typecodeID -@"typeInInterface:reflect/types.type:basic:uint8" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:basic:uint8", %runtime.interfaceMethodInfo* null } -@"typeInInterface:reflect/types.type:basic:int" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:basic:int", %runtime.interfaceMethodInfo* null } @"func NeverImplementedMethod()" = external constant i8 @"Unmatched$interface" = private constant [1 x i8*] [i8* @"func NeverImplementedMethod()"] @"func Double() int" = external constant i8 @"Doubler$interface" = private constant [1 x i8*] [i8* @"func Double() int"] @"Number$methodset" = private constant [1 x %runtime.interfaceMethodInfo] [%runtime.interfaceMethodInfo { i8* @"func Double() int", i32 ptrtoint (i32 (i8*, i8*)* @"(Number).Double$invoke" to i32) }] -@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0 } -@"typeInInterface:reflect/types.type:named:Number" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:named:Number", %runtime.interfaceMethodInfo* getelementptr inbounds ([1 x %runtime.interfaceMethodInfo], [1 x %runtime.interfaceMethodInfo]* @"Number$methodset", i32 0, i32 0) } +@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([1 x %runtime.interfaceMethodInfo], [1 x %runtime.interfaceMethodInfo]* @"Number$methodset", i32 0, i32 0) } declare i1 @runtime.interfaceImplements(i32, i8**) declare i1 @runtime.typeAssert(i32, %runtime.typecodeID*) @@ -27,9 +23,9 @@ declare void @runtime.printnl() declare void @runtime.nilPanic(i8*, i8*) define void @printInterfaces() { - call void @printInterface(i32 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:basic:int" to i32), i8* inttoptr (i32 5 to i8*)) - call void @printInterface(i32 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:basic:uint8" to i32), i8* inttoptr (i8 120 to i8*)) - call void @printInterface(i32 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:named:Number" to i32), i8* inttoptr (i32 3 to i8*)) + call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* inttoptr (i32 5 to i8*)) + call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), i8* inttoptr (i8 120 to i8*)) + call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32), i8* inttoptr (i32 3 to i8*)) ret void } diff --git a/transform/testdata/interface.out.ll b/transform/testdata/interface.out.ll index 4bb9ec1b..12a4c6a4 100644 --- a/transform/testdata/interface.out.ll +++ b/transform/testdata/interface.out.ll @@ -1,15 +1,14 @@ target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" target triple = "armv7m-none-eabi" -%runtime.typecodeID = type { %runtime.typecodeID*, i32 } +%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* } +%runtime.interfaceMethodInfo = type { i8*, i32 } @"reflect/types.type:basic:uint8" = external constant %runtime.typecodeID @"reflect/types.type:basic:int" = external constant %runtime.typecodeID @"func NeverImplementedMethod()" = external constant i8 -@"Unmatched$interface" = private constant [1 x i8*] [i8* @"func NeverImplementedMethod()"] @"func Double() int" = external constant i8 -@"Doubler$interface" = private constant [1 x i8*] [i8* @"func Double() int"] -@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0 } +@"reflect/types.type:named:Number" = private constant %runtime.typecodeID zeroinitializer declare i1 @runtime.interfaceImplements(i32, i8**)