diff --git a/cl/compile.go b/cl/compile.go index a0e690ad..c0cea9ff 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -266,7 +266,7 @@ func NewPackage(pkgPath, pkgName string, files []Source, conf *Config) (ret Pack pkgOf: conf.PackageOf, nameLookup: nameLookup, pubLookup: conf.PubFileLookup, fileBases: make(map[clang.File]int), ovobjs: make(map[string]*overloadObj), macroVals: make(map[string]any), types: make(map[string]typeObj), - logicals: make(map[string]*logicalClass), + logicals: make(map[string]*types.Named), lastSeen: make(map[string]none), impPkgs: make(map[string]none), } diff --git a/cl/ctx.go b/cl/ctx.go index 74db76ba..3a8a157c 100644 --- a/cl/ctx.go +++ b/cl/ctx.go @@ -51,7 +51,7 @@ func goNode(ctx *pkgCtx, v clang.Cursor) ast.Node { var file clang.File var pos, end c.Uint rg := v.Extent() - rg.Start().Spelling(&file, nil, nil, &pos) + rg.Start().Expansion(&file, nil, nil, &pos) if file == clang.InvalidFile { return nil } @@ -155,7 +155,7 @@ type pkgCtx struct { lastSeen map[string]none // last seen include file set (loaded include files) thisSeen map[string]none // include file set seen in this translation unit - logicals map[string]*logicalClass // logical Go class name => logical class info + logicals map[string]*types.Named // logical Go class name => logical class type loads []compileUnit compiles []compileUnit diff --git a/cl/func.go b/cl/func.go index 0137986e..e2d35000 100644 --- a/cl/func.go +++ b/cl/func.go @@ -133,25 +133,32 @@ func compileFuncOrMethod(ctx *pkgCtx, obj *overloadObj, this *classCtx) { if this == nil { if ctx.lang == LanguageC { // try to method for C global functions - if cls, mayClass := ctx.methodCheck(cName); mayClass { - params, recv, typRecv, typName = tryToMethod(ctx, pkgTypes, params) - if typRecv != nil { - recvCType := fn.Argument(0).Type() - if recvCType.Kind == lc.Type_Pointer { - recvCType = recvCType.Pointee() - } - typCName = trimTypeTag(clang.String(recvCType.Unqualified())) + oldParams := params + params, recv, typRecv, typName = tryToMethod(ctx, pkgTypes, params) + if typRecv != nil { + recvCType := fn.Argument(0).Type() + isPtr := recvCType.Kind == lc.Type_Pointer + if isPtr { + recvCType = recvCType.Pointee() + } + typCName = trimTypeTag(clang.String(recvCType.Unqualified())) + if isPtr { // When the function resolves to a logical class distinct from // the physical receiver type (for example PyList_GetItem // resolves to List while its receiver is the base class // Object), emit the method on the logical class - which embeds // the base - and generate the As conversion method on // the base class. See logical.go. - if cls != "" && cls != typName { - logical := ctx.logicalClassOf(fn, cls, typRecv) - recv = types.NewParam(recv.Pos(), pkgTypes, recv.Name(), types.NewPointer(logical)) - typRecv = logical - typName = cls + if cls, mayClass := ctx.methodCheck(cName); mayClass { + if cls != "" && cls != typName { + if contains(cls, ctx.nonClasses) { + params, recv, typName, typCName = oldParams, nil, "", "" + } else if logical, ok := ctx.logicalClassOf(fn, cls, typRecv, recv); ok { + recv = types.NewParam(recv.Pos(), pkgTypes, recv.Name(), types.NewPointer(logical)) + typRecv = logical + typName = cls + } + } } } } diff --git a/cl/logical.go b/cl/logical.go index b5e36712..14953852 100644 --- a/cl/logical.go +++ b/cl/logical.go @@ -52,11 +52,6 @@ import ( const asMethodPrefix = "As" -type logicalClass struct { - named *types.Named // the logical class type (List) - base *types.Named // the physical base class it embeds (Object) -} - // logicalClassOf returns the logical class named goName whose base is the // physical type base, creating it (and its As conversion method on base) // on first use. It is lazy and deterministic: the type and the conversion method @@ -65,42 +60,38 @@ type logicalClass struct { // If a package-level type with the same name already exists (generated from the // headers or provided through TypeAlias), it is reused instead of generating a // second one, and the As conversion is still added when it is missing. -func (p *pkgCtx) logicalClassOf(decl clang.Cursor, goName string, base *types.Named) *types.Named { +func (p *pkgCtx) logicalClassOf(decl clang.Cursor, goName string, baseTy *types.Named, baseRecv *types.Var) (*types.Named, bool) { if lc, ok := p.logicals[goName]; ok { - return lc.named + return lc, lc != nil } - named := p.newLogicalType(decl, goName, base) - lc := &logicalClass{named: named, base: base} - p.logicals[goName] = lc + named, ok := p.newLogicalType(decl, goName, baseTy) + p.logicals[goName] = named + if !ok { + return nil, false + } // genAsMethod runs once per logical class (subsequent resolutions hit the // cache above). It is attempted whether the type was freshly emitted or an // existing package-level type was reused, so a reused type still gets its // As conversion; genAsMethod itself keeps any pre-existing method of // that name and reports a diagnostic. - p.genAsMethod(decl, lc) - return named + p.genAsMethod(decl, named, baseTy, baseRecv) + return named, true } -// newLogicalType returns the named type for the logical class, reusing an -// existing package-level type when one is present and otherwise emitting a fresh -// "type struct { }". -func (p *pkgCtx) newLogicalType(decl clang.Cursor, goName string, base *types.Named) *types.Named { - if o := p.pkg.Types.Scope().Lookup(goName); o != nil { - if tn, ok := o.(*types.TypeName); ok { - if n, ok := tn.Type().(*types.Named); ok { - return n - } - } - } - +func (p *pkgCtx) newLogicalType(decl clang.Cursor, goName string, base *types.Named) (*types.Named, bool) { pkg := p.pkg pkgTypes := pkg.Types + if pkgTypes.Scope().Lookup(goName) != nil { + // can't create a new logical type with the same name as an existing type + // p.ignoref(featExplicitIgnore, decl, "logical class %s: name exists, ignored", goName) + return nil, false + } typDecl := newType(p, decl, "", goName) embed := types.NewField(goNodePos(p, decl), pkgTypes, base.Obj().Name(), base, true) typDecl.InitType(pkg, types.NewStruct([]*types.Var{embed}, nil)) - return typDecl.Type() + return typDecl.Type(), true } // genAsMethod emits the conversion method on the base class, for example @@ -110,29 +101,27 @@ func (p *pkgCtx) newLogicalType(decl clang.Cursor, goName string, base *types.Na // The conversion only reinterprets the pointer; it does not call into C and does // not check the object's real type. If a method of the same name already exists // on the base class, the existing method is kept and a diagnostic is reported. -func (p *pkgCtx) genAsMethod(decl clang.Cursor, lc *logicalClass) { - name := asMethodPrefix + lc.named.Obj().Name() - if pos, _, exists := findMember(lc.base, name); exists { +func (p *pkgCtx) genAsMethod(decl clang.Cursor, named, baseTy *types.Named, baseRecv *types.Var) { + name := asMethodPrefix + named.Obj().Name() + if pos, _, exists := findMember(baseTy, name); exists { p.errorf(decl, "%s redeclared in this block\n\t%v: other declaration of %s", name, p.position(pos), name) return } pkg := p.pkg pkgTypes := pkg.Types - recvType := types.NewPointer(lc.base) - retType := types.NewPointer(lc.named) - recv := types.NewParam(goNodePos(p, decl), pkgTypes, c2goMethodRecvName, recvType) + retType := types.NewPointer(named) results := types.NewTuple(types.NewParam(token.NoPos, pkgTypes, "", retType)) - sig := types.NewSignatureType(recv, nil, nil, nil, results, false) + sig := types.NewSignatureType(baseRecv, nil, nil, nil, results, false) f, err := pkg.NewFuncWith(goNodePos(p, decl), name, sig, nil) if err != nil { - p.panicf(decl, "logical class %s: genAsMethod failed - %v", lc.named.Obj().Name(), err) + p.panicf(decl, "logical class %s: genAsMethod failed - %v", named.Obj().Name(), err) } cb := f.BodyStart(pkg) // return (*List)(unsafe.Pointer(self)) cb.Typ(retType). - Typ(p.unsafePointer()).Val(recv). + Typ(p.unsafePointer()).Val(baseRecv). Call(1). Call(1). Return(1).End() diff --git a/cl/name.go b/cl/name.go index c00fd859..b6a2a017 100644 --- a/cl/name.go +++ b/cl/name.go @@ -243,7 +243,7 @@ func (p *pkgCtx) typeName(cName string, _ bool) string { } name := rmPrefix(cName, p.nsPrefix) name, underscoreStart := rmPrefixAndUnderscoreStart(name, p.typePrefix) - name = rmSuffix(name, p.typeSuffix) + name = cutSuffixes(name, p.typeSuffix) return p.cstyleToGo(name, underscoreStart, true) } @@ -303,10 +303,10 @@ func (p *pkgCtx) funcName(cName string, order int, typName, typCName string, glo panic(fmt.Errorf("invalid TypeAbbr for %q: %v", typName, v)) } } else { - typName = rmSuffix(typName, p.typeAbbrSuffix) + typName = cutSuffixes(typName, p.typeAbbrSuffix) typSuffix = []string{typName} } - name = rmSuffix(name, typSuffix) + name = cutSuffixes(name, typSuffix) name = cutMethodPrefix(name, typName) } } @@ -408,6 +408,18 @@ func cutPrefix(name, prefix string) string { return name } +func cutSuffixes(name string, suffix []string) string { + for _, sfx := range suffix { + if strings.HasSuffix(name, sfx) { + if name == sfx { + return name + } + return name[:len(name)-len(sfx)] + } + } + return name +} + func rmPrefixAndUnderscoreStart(cName string, prefix []string) (name string, underscoreStart bool) { name, underscoreStart = checkUnderscoreStart(cName) name = rmPrefix(name, prefix) @@ -432,15 +444,6 @@ func rmPrefix(name string, prefix []string) string { return name } -func rmSuffix(name string, suffix []string) string { - for _, sfx := range suffix { - if strings.HasSuffix(name, sfx) { - return name[:len(name)-len(sfx)] - } - } - return name -} - func contains(v string, names []string) bool { for _, name := range names { if name == v { diff --git a/go.mod b/go.mod index 49e346ef..9bef9a15 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.27.0 // llgo 1.0 require ( github.com/goplus/gogen v1.26.0 - github.com/goplus/lib v0.5.11 + github.com/goplus/lib v0.5.12 github.com/goplus/mod v0.22.1 - github.com/llarhub/clang-c v0.7.0 + github.com/llarhub/clang-c v0.8.0 github.com/qiniu/x v1.19.1 ) diff --git a/go.sum b/go.sum index 9ec2d685..4db0b81f 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ github.com/goplus/gogen v1.26.0 h1:A0s5CyFrOKuBy1NaN6apnxNmOoxciYbI81eBzyfYdjg= github.com/goplus/gogen v1.26.0/go.mod h1:Y7ulYW3wonQ3d9er00b0uGFEV/IUZa6okWJZh892ACQ= -github.com/goplus/lib v0.5.11 h1:1KZ6pLcc4Ef6EZVk4A/LtKIi1/zQ4g9f+TEdHkdpfhg= -github.com/goplus/lib v0.5.11/go.mod h1:t4Cgs4QYbPzpI1RnYRA0iXWTgUFUuE09TrE2PvuU0VM= +github.com/goplus/lib v0.5.12 h1:LZ31xPTvuchVI1lqsKlCRHJIzqSFhXDjWTA1pLoaWb0= +github.com/goplus/lib v0.5.12/go.mod h1:Ucw7fZlTqrcuHgUvZHP8Oce6IwxULgASlzJxLt8E4p8= github.com/goplus/mod v0.22.1 h1:3GQZqKlcQAiMpRUDAHvFryGYie+mT0JMjguEqOtIjkE= github.com/goplus/mod v0.22.1/go.mod h1:APrczG2FtFcQelU4vTq9xw+GrVs4sPPKnRfGidWBlXY= -github.com/llarhub/clang-c v0.7.0 h1:YkO1FR1R1ScKJijF1PUVIpHnIC4uNgsSg/7CLzEBpFE= -github.com/llarhub/clang-c v0.7.0/go.mod h1:sZhzXACY6REPetdPrGWcSvRmVkl9RgMHbGWHGeOTchI= +github.com/llarhub/clang-c v0.8.0 h1:2JPW4ZiaYPwdI9G7LIiBAaFfI1fLzsJlmJe/3eWtdtQ= +github.com/llarhub/clang-c v0.8.0/go.mod h1:os8efa2pPUOT8LmMprrndHWqlaFffYhxYwtxpH5SBR4= github.com/qiniu/x v1.19.1 h1:Z6ZALlHMrZw/AImYjYnYKMXemasVdUF2o7riwm9TZTs= github.com/qiniu/x v1.19.1/go.mod h1:Sx3Wy+0GI9OsX4a53mYj6A0o7mHJ94PUvraqGYb4EIs= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= diff --git a/tool/_testc/python-3.14.8/include/pythread.h b/tool/_testc/python-3.14.8/include/pythread.h index 79b53e02..b265e707 100644 --- a/tool/_testc/python-3.14.8/include/pythread.h +++ b/tool/_testc/python-3.14.8/include/pythread.h @@ -17,7 +17,9 @@ typedef enum Py_Lock_Status { typedef struct _Py_tss_t Py_tss_t; /* opaque */ -typedef struct PyObject {} PyObject; +struct _object {}; + +typedef struct _object PyObject; // Test if an object is the True singleton, the same as "x is True" in Python. PyAPI_FUNC(int) Py_IsTrue(PyObject *x); @@ -29,6 +31,40 @@ PyAPI_FUNC(void) PyList_SetItem(PyObject *x, PyObject *index, PyObject *value); PyAPI_FUNC(PyObject *) PyDict_GetItem(PyObject *x, PyObject *index); +PyAPI_FUNC(PyObject *) PyImport_Import(PyObject *name); + +typedef enum { + /* PyMem_RawMalloc(), PyMem_RawRealloc() and PyMem_RawFree() */ + PYMEM_DOMAIN_RAW, + + /* PyMem_Malloc(), PyMem_Realloc() and PyMem_Free() */ + PYMEM_DOMAIN_MEM, + + /* PyObject_Malloc(), PyObject_Realloc() and PyObject_Free() */ + PYMEM_DOMAIN_OBJ +} PyMemAllocatorDomain; + +typedef struct { + /* user context passed as the first argument to the 4 functions */ + void *ctx; + + /* allocate a memory block */ + void* (*malloc) (void *ctx, size_t size); + + /* allocate a memory block initialized by zeros */ + void* (*calloc) (void *ctx, size_t nelem, size_t elsize); + + /* allocate or resize a memory block */ + void* (*realloc) (void *ctx, void *ptr, size_t new_size); + + /* release a memory block */ + void (*free) (void *ctx, void *ptr); +} PyMemAllocatorEx; + +/* Get the memory block allocator of the specified domain. */ +PyAPI_FUNC(void) PyMem_GetAllocator(PyMemAllocatorDomain domain, + PyMemAllocatorEx *allocator); + #ifndef Py_LIMITED_API # define Py_CPYTHON_PYTHREAD_H # include "cpython/pythread.h" diff --git a/tool/_testc/python-3.14.8/llcppg.cfg b/tool/_testc/python-3.14.8/llcppg.cfg index 38d9026f..31d8fd5f 100644 --- a/tool/_testc/python-3.14.8/llcppg.cfg +++ b/tool/_testc/python-3.14.8/llcppg.cfg @@ -6,7 +6,7 @@ "Files": ["Python.h"], "Deps": [], "Class": [], - "NonClass": [], + "NonClass": ["Import"], "TypePrefix": ["Py_", "Py"], "MethodCheck": ["Py*_: $1"], "FuncPrefix": ["Py_", "Py"], @@ -16,6 +16,7 @@ "TypeAbbr": { }, "Rename": { + "PyImport_Import": "Import" }, "MacroIgnore": [], "TypeIgnore": [], diff --git a/tool/_testc/python-3.14.8/pythread.go b/tool/_testc/python-3.14.8/pythread.go index 4ce2977e..fb2b53ee 100644 --- a/tool/_testc/python-3.14.8/pythread.go +++ b/tool/_testc/python-3.14.8/pythread.go @@ -18,6 +18,17 @@ const ( LOCK_INTR LockStatus = 2 ) +type MemAllocatorDomain c.Uint + +const ( + // PyMem_RawMalloc(), PyMem_RawRealloc() and PyMem_RawFree() + PYMEM_DOMAIN_RAW MemAllocatorDomain = 0 + // PyMem_Malloc(), PyMem_Realloc() and PyMem_Free() + PYMEM_DOMAIN_MEM MemAllocatorDomain = 1 + // PyObject_Malloc(), PyObject_Realloc() and PyObject_Free() + PYMEM_DOMAIN_OBJ MemAllocatorDomain = 2 +) + type ThreadTypeLock uintptr // When Py_LIMITED_API is not defined, the type layout of Py_tss_t is @@ -28,21 +39,31 @@ type X_tssT struct { X_key pthread.Key } type TssT = X_tssT -type Object struct { +type X_object struct { +} +type Object = X_object +type MemAllocatorEx struct { + Ctx unsafe.Pointer + Malloc func(_llcppg_param1 unsafe.Pointer, _llcppg_param2 c.SizeT) unsafe.Pointer + Calloc func(_llcppg_param1 unsafe.Pointer, _llcppg_param2 c.SizeT, _llcppg_param3 c.SizeT) unsafe.Pointer + Realloc func(_llcppg_param1 unsafe.Pointer, _llcppg_param2 unsafe.Pointer, _llcppg_param3 c.SizeT) unsafe.Pointer + Free func(_llcppg_param1 unsafe.Pointer, _llcppg_param2 unsafe.Pointer) } // Test if an object is the True singleton, the same as "x is True" in Python. // -//go:linkname IsTrue C.Py_IsTrue -func IsTrue(x *Object) c.Int - -// llgo:link (*Object).IsTrue C.PyObject_IsTrue +// llgo:link (*Object).IsTrue C.Py_IsTrue func (self *Object) IsTrue() c.Int { return 0 } +// llgo:link (*Object).ObjectIsTrue C.PyObject_IsTrue +func (self *Object) ObjectIsTrue() c.Int { + return 0 +} + type List struct { - Object + X_object } func (self *Object) AsList() *List { @@ -59,7 +80,7 @@ func (self *List) SetItem(index *Object, value *Object) { } type Dict struct { - Object + X_object } func (self *Object) AsDict() *Dict { @@ -71,6 +92,15 @@ func (self *Dict) Item(index *Object) *Object { return nil } +//go:linkname Import C.PyImport_Import +func Import(name *Object) *Object + +// Get the memory block allocator of the specified domain. +// +// llgo:link MemAllocatorDomain.MemGetAllocator C.PyMem_GetAllocator +func (self MemAllocatorDomain) MemGetAllocator(allocator *MemAllocatorEx) { +} + // PY_TIMEOUT_MAX is the highest usable value (in microseconds) of PY_TIMEOUT_T // type, and depends on the system threading API. //