Skip to content

Commit

Permalink
Merge pull request #47 from mappu/miqt-core-next
Browse files Browse the repository at this point in the history
Latest improvements for ABI and marshaling
  • Loading branch information
mappu authored Oct 19, 2024
2 parents c682772 + bbcb3c3 commit b034456
Show file tree
Hide file tree
Showing 883 changed files with 35,370 additions and 25,872 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ cmd/genbindings/cachedir/
# docker files
container-build-cache/

# local genbindings configuration
cmd/genbindings/genbindings.local*

# binaries
cmd/handbindings/handbindings
cmd/handbindings/bindings_test/direct
Expand All @@ -19,6 +22,7 @@ examples/windowsmanifest/windowsmanifest
examples/windowsmanifest/windowsmanifest.exe
examples/uidesigner/uidesigner
examples/uidesigner/uidesigner.exe
examples/libraries/qt-qprintsupport/qt-qprintsupport

# android temporary build files
android-build
Expand Down
9 changes: 8 additions & 1 deletion cmd/genbindings/clang2il.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ nextTopLevel:
"ClassTemplateSpecializationDecl",
"ClassTemplatePartialSpecializationDecl",
"FunctionTemplateDecl",
"BuiltinTemplateDecl", // Scintilla
"VarTemplatePartialSpecializationDecl", // e.g. Qt6 qcontainerinfo.h
"VarTemplateSpecializationDecl", // e.g. qhashfunctions.h
"TypeAliasTemplateDecl", // e.g. qendian.h
Expand Down Expand Up @@ -89,7 +90,7 @@ nextTopLevel:

} else {

contents, err := parseHeader(namespaceInner, namespace+"::")
contents, err := parseHeader(namespaceInner, addNamePrefix+namespace+"::")
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -130,6 +131,12 @@ nextTopLevel:
// TODO e.g. qfuturewatcher.h
// Probably can't be supported in the Go binding

case "AbiTagAttr":
// e.g. scintilla.org ScintillaEditBase
case "VisibilityAttr":
// e.g. scintilla.org ScintillaEditBase
// Don't understand why this appears at top level??

case "UsingDirectiveDecl", // qtextstream.h
"UsingDecl", // qglobal.h
"UsingShadowDecl": // global.h
Expand Down
48 changes: 39 additions & 9 deletions cmd/genbindings/emitcabi.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import (
func (p CppParameter) RenderTypeCabi() string {

if p.ParameterType == "QString" {
return "struct miqt_string*"
return "struct miqt_string"

} else if p.ParameterType == "QByteArray" {
return "struct miqt_string"

} else if _, ok := p.QListOf(); ok {
return "struct miqt_array*"
Expand Down Expand Up @@ -146,7 +149,10 @@ func emitParametersCabi(m CppMethod, selfType string) string {

for _, p := range m.Parameters {
if p.ParameterType == "QString" {
tmp = append(tmp, "struct miqt_string* "+p.ParameterName)
tmp = append(tmp, "struct miqt_string "+p.ParameterName)

} else if p.ParameterType == "QByteArray" {
tmp = append(tmp, "struct miqt_string "+p.ParameterName)

} else if t, ok := p.QListOf(); ok {
tmp = append(tmp, "struct miqt_array* /* of "+t.RenderTypeCabi()+" */ "+p.ParameterName)
Expand Down Expand Up @@ -197,11 +203,18 @@ func emitCABI2CppForwarding(p CppParameter, indent string) (preamble string, for
nameprefix := makeNamePrefix(p.ParameterName)

if p.ParameterType == "QString" {
// The CABI has accepted two parameters - need to convert to one real QString
// Create it on the stack
preamble += indent + "QString " + nameprefix + "_QString = QString::fromUtf8(&" + p.ParameterName + "->data, " + p.ParameterName + "->len);\n"
// The CABI received parameter is a struct miqt_string, passed by value
// C++ needs it as a QString. Create one on the stack for automatic cleanup
// The caller will free the miqt_string
preamble += indent + "QString " + nameprefix + "_QString = QString::fromUtf8(" + p.ParameterName + ".data, " + p.ParameterName + ".len);\n"
return preamble, nameprefix + "_QString"

} else if p.ParameterType == "QByteArray" {
// The caller will free the miqt_string data
// This ctor makes a deep copy, on the stack which will be dtor'd by RAII
preamble += indent + "QByteArray " + nameprefix + "_QByteArray(" + p.ParameterName + ".data, " + p.ParameterName + ".len);\n"
return preamble, nameprefix + "_QByteArray"

} else if listType, ok := p.QListOf(); ok {

preamble += indent + p.GetQtCppType().ParameterType + " " + nameprefix + "_QList;\n"
Expand Down Expand Up @@ -315,7 +328,23 @@ func emitAssignCppToCabi(assignExpression string, p CppParameter, rvalue string)
afterCall += indent + "QByteArray " + namePrefix + "_b = " + namePrefix + "_ret.toUtf8();\n"
}

afterCall += indent + assignExpression + "miqt_strdup(" + namePrefix + "_b.data(), " + namePrefix + "_b.length());\n"
afterCall += indent + "struct miqt_string " + namePrefix + "_ms;\n"
afterCall += indent + namePrefix + "_ms.len = " + namePrefix + "_b.length();\n"
afterCall += indent + namePrefix + "_ms.data = static_cast<char*>(malloc(" + namePrefix + "_ms.len));\n"
afterCall += indent + "memcpy(" + namePrefix + "_ms.data, " + namePrefix + "_b.data(), " + namePrefix + "_ms.len);\n"
afterCall += indent + assignExpression + namePrefix + "_ms;\n"

} else if p.ParameterType == "QByteArray" {
// C++ has given us a QByteArray. CABI needs this as a struct miqt_string
// Do not free the data, the caller will free it

shouldReturn = ifv(p.Const, "const ", "") + "QByteArray " + p.ParameterName + "_qb = "

afterCall += indent + "struct miqt_string " + namePrefix + "_ms;\n"
afterCall += indent + namePrefix + "_ms.len = " + namePrefix + "_qb.length();\n"
afterCall += indent + namePrefix + "_ms.data = static_cast<char*>(malloc(" + namePrefix + "_ms.len));\n"
afterCall += indent + "memcpy(" + namePrefix + "_ms.data, " + namePrefix + "_qb.data(), " + namePrefix + "_ms.len);\n"
afterCall += indent + assignExpression + namePrefix + "_ms;\n"

} else if t, ok := p.QListOf(); ok {

Expand Down Expand Up @@ -577,9 +606,6 @@ func emitBindingCpp(src *CppParsedHeader, filename string) (string, error) {
ret := strings.Builder{}

for _, ref := range getReferencedTypes(src) {
if !ImportHeaderForClass(ref) {
continue
}

if ref == "QString" {
ret.WriteString("#include <QString>\n")
Expand All @@ -593,6 +619,10 @@ func emitBindingCpp(src *CppParsedHeader, filename string) (string, error) {
continue
}

if !ImportHeaderForClass(ref) {
continue
}

ret.WriteString(`#include <` + ref + ">\n")
}

Expand Down
107 changes: 76 additions & 31 deletions cmd/genbindings/emitgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ func (p CppParameter) RenderTypeGo(gfs *goFileState) string {
if p.ParameterType == "QString" {
return "string"
}
if p.ParameterType == "QByteArray" {
return "[]byte"
}

if t, ok := p.QListOf(); ok {
return "[]" + t.RenderTypeGo(gfs)
Expand Down Expand Up @@ -142,7 +145,11 @@ func (p CppParameter) RenderTypeGo(gfs *goFileState) string {

func (p CppParameter) parameterTypeCgo() string {
if p.ParameterType == "QString" {
return "*C.struct_miqt_string"
return "C.struct_miqt_string"
}

if p.ParameterType == "QByteArray" {
return "C.struct_miqt_string"
}

if _, ok := p.QListOf(); ok {
Expand Down Expand Up @@ -256,11 +263,25 @@ func (gfs *goFileState) emitParameterGo2CABIForwarding(p CppParameter) (preamble
// Go: convert string -> miqt_string*
// CABI: convert miqt_string* -> real QString

gfs.imports["libmiqt"] = struct{}{}
preamble += nameprefix + "_ms := libmiqt.Strdupg(" + p.ParameterName + ")\n"
preamble += "defer C.free(" + nameprefix + "_ms)\n"
gfs.imports["unsafe"] = struct{}{}
preamble += nameprefix + "_ms := C.struct_miqt_string{}\n"
preamble += nameprefix + "_ms.data = C.CString(" + p.ParameterName + ")\n"
preamble += nameprefix + "_ms.len = C.size_t(len(" + p.ParameterName + "))\n"
preamble += "defer C.free(unsafe.Pointer(" + nameprefix + "_ms.data))\n"

rvalue = nameprefix + "_ms"

} else if p.ParameterType == "QByteArray" {
// Go: convert []byte -> miqt_string
// CABI: convert miqt_string -> QByteArray
// n.b. This can ALIAS the existing []byte data

rvalue = "(*C.struct_miqt_string)(" + nameprefix + "_ms)"
gfs.imports["unsafe"] = struct{}{}
preamble += nameprefix + "_alias := C.struct_miqt_string{}\n"
preamble += nameprefix + "_alias.data = (*C.char)(unsafe.Pointer(&" + p.ParameterName + "[0]))\n"
preamble += nameprefix + "_alias.len = C.size_t(len(" + p.ParameterName + "))\n"

rvalue = nameprefix + "_alias"

} else if listType, ok := p.QListOf(); ok {
// QList<T>
Expand Down Expand Up @@ -329,32 +350,54 @@ func (gfs *goFileState) emitParameterGo2CABIForwarding(p CppParameter) (preamble

func (gfs *goFileState) emitCabiToGo(assignExpr string, rt CppParameter, rvalue string) string {

shouldReturn := "return "
shouldReturn := assignExpr // "return "
afterword := ""
namePrefix := makeNamePrefix(rt.ParameterName)

if rt.ParameterType == "void" && !rt.Pointer {
shouldReturn = ""
return shouldReturn + " " + rvalue + "\n" + afterword

} else if rt.ParameterType == "void" && rt.Pointer {
// ...
gfs.imports["unsafe"] = struct{}{}
return assignExpr + " (unsafe.Pointer)(" + rvalue + ")\n"

} else if rt.ParameterType == "char" && rt.Pointer {
// Qt functions normally return QString - anything returning char*
// is something like QByteArray.Data() where it returns an unsafe
// internal pointer
// However in case this is a signal, we need to be able to marshal both
// forwards and backwards with the same types, this has to be a string
// in both cases
// This is not a miqt_string and therefore MIQT did not allocate it,
// and therefore we don't have to free it either
gfs.imports["unsafe"] = struct{}{}

shouldReturn = namePrefix + "_ret := "
afterword += assignExpr + " (unsafe.Pointer)(" + namePrefix + "_ret)\n"
afterword += assignExpr + " C.GoString(" + namePrefix + "_ret)\n"
return shouldReturn + " " + rvalue + "\n" + afterword

} else if rt.ParameterType == "QString" {
gfs.imports["unsafe"] = struct{}{}

shouldReturn = "var " + namePrefix + "_ms *C.struct_miqt_string = "
afterword += namePrefix + "_ret := C.GoStringN(&" + namePrefix + "_ms.data, C.int(int64(" + namePrefix + "_ms.len)))\n"
afterword += "C.free(unsafe.Pointer(" + namePrefix + "_ms))\n"
shouldReturn = "var " + namePrefix + "_ms C.struct_miqt_string = "
afterword += namePrefix + "_ret := C.GoStringN(" + namePrefix + "_ms.data, C.int(int64(" + namePrefix + "_ms.len)))\n"
afterword += "C.free(unsafe.Pointer(" + namePrefix + "_ms.data))\n"
afterword += assignExpr + namePrefix + "_ret"
return shouldReturn + " " + rvalue + "\n" + afterword

} else if rt.ParameterType == "QByteArray" {
// We receive the CABI type of a miqt_string. Convert it into []byte
// We must free the miqt_string data pointer - this is a data copy,
// not an alias

gfs.imports["unsafe"] = struct{}{}

shouldReturn = "var " + namePrefix + "_bytearray C.struct_miqt_string = "
afterword += namePrefix + "_ret := C.GoBytes(unsafe.Pointer(" + namePrefix + "_bytearray.data), C.int(int64(" + namePrefix + "_bytearray.len)))\n"
afterword += "C.free(unsafe.Pointer(" + namePrefix + "_bytearray.data))\n"
afterword += assignExpr + namePrefix + "_ret"
return shouldReturn + " " + rvalue + "\n" + afterword

} else if t, ok := rt.QListOf(); ok {
gfs.imports["unsafe"] = struct{}{}
Expand All @@ -370,6 +413,7 @@ func (gfs *goFileState) emitCabiToGo(assignExpr string, rt CppParameter, rvalue
afterword += "}\n"
afterword += "C.free(unsafe.Pointer(" + namePrefix + "_ma))\n"
afterword += assignExpr + " " + namePrefix + "_ret\n"
return shouldReturn + " " + rvalue + "\n" + afterword

} else if t, ok := rt.QSetOf(); ok {

Expand All @@ -387,6 +431,7 @@ func (gfs *goFileState) emitCabiToGo(assignExpr string, rt CppParameter, rvalue
afterword += "}\n"
afterword += "C.free(unsafe.Pointer(" + namePrefix + "_ma))\n"
afterword += assignExpr + " " + namePrefix + "_ret\n"
return shouldReturn + " " + rvalue + "\n" + afterword

} else if rt.QtClassType() {
// Construct our Go type based on this inner CABI type
Expand Down Expand Up @@ -427,15 +472,17 @@ func (gfs *goFileState) emitCabiToGo(assignExpr string, rt CppParameter, rvalue
afterword += assignExpr + " *" + namePrefix + "_goptr\n"
}
}
return shouldReturn + " " + rvalue + "\n" + afterword

} else if rt.IntType() || rt.IsKnownEnum() || rt.IsFlagType() || rt.ParameterType == "bool" || rt.QtCppOriginalType != nil {
// Need to cast Cgo type to Go int type
// Optimize assignment to avoid temporary
return assignExpr + "(" + rt.RenderTypeGo(gfs) + ")(" + rvalue + ")\n"

} else {
panic(fmt.Sprintf("emitgo::emitCabiToGo missing type handler for parameter %+v", rt))
}

return shouldReturn + " " + rvalue + "\n" + afterword
}

func emitGo(src *CppParsedHeader, headerName string, packageName string) (string, error) {
Expand Down Expand Up @@ -468,13 +515,23 @@ import "C"
nameTest := map[string]string{}
nextEnum:
for _, e := range src.Enums {

shortEnumName := e.ShortEnumName()

// Disallow entry<-->entry collisions
for _, ee := range e.Entries {
if other, ok := nameTest[ee.EntryName]; ok {
if other, ok := nameTest[shortEnumName+"::"+ee.EntryName]; ok {
preventShortNames[e.EnumName] = struct{}{} // Our full enum name
preventShortNames[other] = struct{}{} // Their full enum name
continue nextEnum
}
nameTest[shortEnumName+"::"+ee.EntryName] = e.EnumName

if _, ok := KnownClassnames[shortEnumName+"::"+ee.EntryName]; ok {
preventShortNames[e.EnumName] = struct{}{}
preventShortNames[other] = struct{}{}
continue nextEnum
}
nameTest[ee.EntryName] = e.EnumName

}
}
}
Expand All @@ -484,19 +541,11 @@ import "C"
continue // Removed by transformRedundant AST pass
}

// Fully qualified name of the enum itself
goEnumName := cabiClassName(e.EnumName)
goEnumName := cabiClassName(e.EnumName) // Fully qualified name of the enum itself

// Shorter name, so that enum elements are reachable from the surrounding
// namespace
goEnumShortName := goEnumName
goEnumShortName := goEnumName // Shorter name, so that enum elements are reachable from the surrounding namespace
if _, ok := preventShortNames[e.EnumName]; !ok {
// Strip back one single :: pair from the generated variable name
nameParts := strings.Split(e.EnumName, `::`)
if len(nameParts) > 1 {
nameParts = nameParts[0 : len(nameParts)-1]
}
goEnumShortName = cabiClassName(strings.Join(nameParts, `::`))
goEnumShortName = cabiClassName(e.ShortEnumName())
}

ret.WriteString(`
Expand Down Expand Up @@ -628,13 +677,9 @@ import "C"
if returnTypeDecl == "void" {
returnTypeDecl = ""
}
if m.ReturnType.QtClassType() && m.ReturnType.ParameterType != "QString" && !(m.ReturnType.Pointer || m.ReturnType.ByRef) {
if m.ReturnType.QtClassType() && m.ReturnType.ParameterType != "QString" && m.ReturnType.ParameterType != "QByteArray" && !(m.ReturnType.Pointer || m.ReturnType.ByRef) {
returnTypeDecl = "*" + returnTypeDecl
}
if (m.ReturnType.ParameterType == "char" || m.ReturnType.ParameterType == "void") && m.ReturnType.Pointer {
gfs.imports["unsafe"] = struct{}{}
returnTypeDecl = "unsafe.Pointer"
}

rvalue := `C.` + goClassName + `_` + m.SafeMethodName() + `(` + forwarding + `)`

Expand Down
8 changes: 8 additions & 0 deletions cmd/genbindings/exceptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func AllowHeader(fullpath string) bool {
"qmaccocoaviewcontainer_mac.h", // Needs NSView* headers. TODO allow with darwin build tag
"qmacnativewidget_mac.h", // Needs NSView* headers. TODO allow with darwin build tag
"qstring.h", // QString does not exist in this binding
"qbytearray.h", // QByteArray does not exist in this binding
"qlist.h", // QList does not exist in this binding
"qvector.h": // QVector does not exist in this binding
return false
Expand Down Expand Up @@ -211,6 +212,13 @@ func CheckComplexity(p CppParameter, isReturnType bool) error {
return ErrTooComplex
}

// QBuffer can accept a raw pointer to an internal QByteArray, but that
// doesn't work when QByteArray is deleted
// QDataStream
if p.ParameterType == "QByteArray" && p.Pointer && !isReturnType {
return ErrTooComplex
}

if p.ParameterType == "QFormLayout::ItemRole" && p.Pointer && !isReturnType { // Out-parameters in QFormLayout
return ErrTooComplex
}
Expand Down
Loading

0 comments on commit b034456

Please sign in to comment.