From fb56258334347d476d69af75e4e7c5270c347300 Mon Sep 17 00:00:00 2001 From: mappu Date: Tue, 19 Nov 2024 19:27:19 +1300 Subject: [PATCH] genbindings: constructors return every subclass pointer --- cmd/genbindings/emitcabi.go | 34 ++++++++- cmd/genbindings/emitgo.go | 136 ++++++++++++++++++++++++++---------- 2 files changed, 131 insertions(+), 39 deletions(-) diff --git a/cmd/genbindings/emitcabi.go b/cmd/genbindings/emitcabi.go index a3b8cde0..3f996f7a 100644 --- a/cmd/genbindings/emitcabi.go +++ b/cmd/genbindings/emitcabi.go @@ -595,6 +595,11 @@ func getReferencedTypes(src *CppParsedHeader) []string { } maybeAddType(vm.ReturnType) } + for _, cn := range c.AllInherits() { + maybeAddType(CppParameter{ + ParameterType: cn, + }) + } } // Some types (e.g. QRgb) are found but are typedefs, not classes @@ -709,7 +714,7 @@ extern "C" { methodPrefixName := cabiClassName(c.ClassName) for i, ctor := range c.Ctors { - ret.WriteString(fmt.Sprintf("%s %s_new%s(%s);\n", methodPrefixName+"*", methodPrefixName, maybeSuffix(i), emitParametersCabi(ctor, ""))) + ret.WriteString(fmt.Sprintf("void %s_new%s(%s);\n", methodPrefixName, maybeSuffix(i), emitParametersCabiConstructor(&c, &ctor))) } for _, m := range c.Methods { @@ -749,6 +754,33 @@ func fullyQualifiedConstructor(className string) string { return className + "::" + parts[len(parts)-1] } +func emitParametersCabiConstructor(c *CppClass, ctor *CppMethod) string { + + plist := slice_copy(ctor.Parameters) // semi-shallow copy + + plist = append(plist, CppParameter{ + ParameterName: cabiClassName("outptr_" + c.ClassName), + ParameterType: c.ClassName, + Pointer: true, + PointerCount: 2, + }) + for _, baseClass := range c.AllInherits() { + plist = append(plist, CppParameter{ + ParameterName: cabiClassName("outptr_" + baseClass), + ParameterType: baseClass, + Pointer: true, + PointerCount: 2, + }) + } + + slist := make([]string, 0, len(plist)) + for _, p := range plist { + slist = append(slist, p.RenderTypeCabi()+" "+p.ParameterName) + } + + return strings.Join(slist, `, `) +} + func emitBindingCpp(src *CppParsedHeader, filename string) (string, error) { ret := strings.Builder{} diff --git a/cmd/genbindings/emitgo.go b/cmd/genbindings/emitgo.go index 7d94d3aa..905971c2 100644 --- a/cmd/genbindings/emitgo.go +++ b/cmd/genbindings/emitgo.go @@ -579,14 +579,22 @@ func (gfs *goFileState) emitCabiToGo(assignExpr string, rt CppParameter, rvalue shouldReturn = "" + namePrefix + "_ret := " crossPackage := "" - if pkg, ok := KnownClassnames[rt.ParameterType]; ok && pkg.PackageName != gfs.currentPackageName { + pkg, ok := KnownClassnames[rt.ParameterType] + if !ok { + panic("emitCabiToGo: Encountered an unknown Qt class") + } + + if pkg.PackageName != gfs.currentPackageName { crossPackage = path.Base(pkg.PackageName) + "." gfs.imports[importPathForQtPackage(pkg.PackageName)] = struct{}{} } + // FIXME This needs to somehow figure out the real child pointers + extraConstructArgs := strings.Repeat(", nil", len(pkg.Class.AllInherits())) + if rt.Pointer || rt.ByRef { gfs.imports["unsafe"] = struct{}{} - return assignExpr + " " + crossPackage + "UnsafeNew" + cabiClassName(rt.ParameterType) + "(unsafe.Pointer(" + rvalue + "))" + return assignExpr + " " + crossPackage + "UnsafeNew" + cabiClassName(rt.ParameterType) + "(unsafe.Pointer(" + rvalue + ")" + extraConstructArgs + ")" } else { // This is return by value, but CABI has new'd it into a @@ -596,10 +604,10 @@ func (gfs *goFileState) emitCabiToGo(assignExpr string, rt CppParameter, rvalue // of Go scope if crossPackage == "" { - afterword += namePrefix + "_goptr := new" + cabiClassName(rt.ParameterType) + "(" + namePrefix + "_ret)\n" + afterword += namePrefix + "_goptr := new" + cabiClassName(rt.ParameterType) + "(" + namePrefix + "_ret" + extraConstructArgs + ")\n" } else { gfs.imports["unsafe"] = struct{}{} - afterword += namePrefix + "_goptr := " + crossPackage + "UnsafeNew" + cabiClassName(rt.ParameterType) + "(unsafe.Pointer(" + namePrefix + "_ret))\n" + afterword += namePrefix + "_goptr := " + crossPackage + "UnsafeNew" + cabiClassName(rt.ParameterType) + "(unsafe.Pointer(" + namePrefix + "_ret)" + extraConstructArgs + ")\n" } afterword += namePrefix + "_goptr.GoGC() // Qt uses pass-by-value semantics for this type. Mimic with finalizer\n" @@ -758,69 +766,121 @@ import "C" } `) + + // CGO types only exist within the same Go file, so other Go files can't + // call this same private ctor function, unless it goes through unsafe.Pointer{}. + // This is probably because C types can possibly violate the ODR whereas + // that never happens in Go's type system. + gfs.imports["unsafe"] = struct{}{} localInit := "h: h" - for _, base := range c.Inherits { - gfs.imports["unsafe"] = struct{}{} + unsafeInit := "h: (*C." + goClassName + ")(h)" + extraCArgs := "" + extraUnsafeArgs := "" + // We require arguments for all inherits, but we only embed the direct inherits + // Any recursive inherits will be owned by the base + for _, base := range c.AllInherits() { + + extraCArgs += ", h_" + cabiClassName(base) + " *C." + cabiClassName(base) + extraUnsafeArgs += ", h_" + cabiClassName(base) + " unsafe.Pointer" + } + + for _, base := range c.DirectInherits { ctorPrefix := "" - if pkg, ok := KnownClassnames[base]; ok && pkg.PackageName != gfs.currentPackageName { - ctorPrefix = path.Base(pkg.PackageName) + "." + pkg, ok := KnownClassnames[base] + if !ok { + panic("Class " + c.ClassName + " has unknown parent " + base) } - localInit += ", " + cabiClassName(base) + ": " + ctorPrefix + "UnsafeNew" + cabiClassName(base) + "(unsafe.Pointer(h))" + constructRequiresParams := pkg.Class.AllInherits() + var ixxParams []string = make([]string, 0, len(constructRequiresParams)+1) + ixxParams = append(ixxParams, "h_"+cabiClassName(base)) + for _, grandchildInheritedClass := range constructRequiresParams { + ixxParams = append(ixxParams, "h_"+cabiClassName(grandchildInheritedClass)) + } + + if pkg.PackageName != gfs.currentPackageName { + ctorPrefix = path.Base(pkg.PackageName) + "." + + localInit += ",\n" + cabiClassName(base) + ": " + ctorPrefix + "UnsafeNew" + cabiClassName(base) + "(unsafe.Pointer(" + strings.Join(ixxParams, "), unsafe.Pointer(") + "))" + } else { + localInit += ",\n" + cabiClassName(base) + ": new" + cabiClassName(base) + "(" + strings.Join(ixxParams, ", ") + ")" + + } + + unsafeInit += ",\n" + cabiClassName(base) + ": " + ctorPrefix + "UnsafeNew" + cabiClassName(base) + "(" + strings.Join(ixxParams, ", ") + ")" } ret.WriteString(` - func new` + goClassName + `(h *C.` + goClassName + `) *` + goClassName + ` { + // new` + goClassName + ` constructs the type using only CGO pointers. + func new` + goClassName + `(h *C.` + goClassName + extraCArgs + `) *` + goClassName + ` { if h == nil { return nil } return &` + goClassName + `{` + localInit + `} } - `) - - // CGO types only exist within the same Go file, so other Go files can't - // call this same private ctor function, unless it goes through unsafe.Pointer{}. - // This is probably because C types can possibly violate the ODR whereas - // that never happens in Go's type system. - gfs.imports["unsafe"] = struct{}{} - ret.WriteString(` - func UnsafeNew` + goClassName + `(h unsafe.Pointer) *` + goClassName + ` { - return new` + goClassName + `( (*C.` + goClassName + `)(h) ) + // UnsafeNew` + goClassName + ` constructs the type using only unsafe pointers. + func UnsafeNew` + goClassName + `(h unsafe.Pointer` + extraUnsafeArgs + `) *` + goClassName + ` { + if h == nil { + return nil + } + + return &` + goClassName + `{` + unsafeInit + `} } `) + // + for i, ctor := range c.Ctors { preamble, forwarding := gfs.emitParametersGo2CABIForwarding(ctor) + ret.WriteString(` + // New` + goClassName + maybeSuffix(i) + ` constructs a new ` + c.ClassName + ` object. + func New` + goClassName + maybeSuffix(i) + `(` + gfs.emitParametersGo(ctor.Parameters) + `) *` + goClassName + ` { + `, + ) + if ctor.LinuxOnly { gfs.imports["runtime"] = struct{}{} ret.WriteString(` - // New` + goClassName + maybeSuffix(i) + ` constructs a new ` + c.ClassName + ` object. - func New` + goClassName + maybeSuffix(i) + `(` + gfs.emitParametersGo(ctor.Parameters) + `) *` + goClassName + ` { - if runtime.GOOS == "linux" { - ` + preamble + ` ret := C.` + goClassName + `_new` + maybeSuffix(i) + `(` + forwarding + `) - return new` + goClassName + `(ret) - } else { - panic("Unsupported OS") - } + if runtime.GOOS != "linux" { + panic("Unsupported OS") + } + `) + } + + ret.WriteString(preamble) + + // Outptr management + + outptrs := make([]string, 0, len(c.AllInherits())+1) + ret.WriteString(`var outptr_` + cabiClassName(c.ClassName) + ` *C.` + goClassName + " = nil\n") + outptrs = append(outptrs, "outptr_"+cabiClassName(c.ClassName)) + for _, baseClass := range c.AllInherits() { + ret.WriteString(`var outptr_` + cabiClassName(baseClass) + ` *C.` + baseClass + " = nil\n") + outptrs = append(outptrs, "outptr_"+cabiClassName(baseClass)) + } + + if len(forwarding) > 0 { + forwarding += ", " + } + forwarding += "&" + strings.Join(outptrs, ", &") + + // Call Cgo constructor + + ret.WriteString(` + C.` + goClassName + `_new` + maybeSuffix(i) + `(` + forwarding + `) + ret := new` + goClassName + `(` + strings.Join(outptrs, `, `) + `) + ret.isSubclass = true + return ret } `) - } else { - ret.WriteString(` - // New` + goClassName + maybeSuffix(i) + ` constructs a new ` + c.ClassName + ` object. - func New` + goClassName + maybeSuffix(i) + `(` + gfs.emitParametersGo(ctor.Parameters) + `) *` + goClassName + ` { - ` + preamble + ` ret := C.` + goClassName + `_new` + maybeSuffix(i) + `(` + forwarding + `) - return new` + goClassName + `(ret) - } - - `) - } + } for _, m := range c.Methods {