Maratyszcza/PeachPy

Go: `uintptr` support?

dgryski opened this issue · 12 comments

https://golang.org/pkg/builtin/#uintptr

It seems to be this can map directly to uintptr_t ?

diff --git a/peachpy/x86_64/function.py b/peachpy/x86_64/function.py
index f4614d6..00c81b9 100644
--- a/peachpy/x86_64/function.py
+++ b/peachpy/x86_64/function.py
@@ -130,6 +130,8 @@ class Function:
                 return "boolean"
             elif c_type.is_size_integer:
                 return "int" if c_type.is_signed_integer else "uint"
+            elif c_type.is_pointer_integer:
+                return "uintptr"
             elif c_type.is_signed_integer:
                 return {
                     1: "int8",

My rationale for wanting this is to make it easier to have slices and structs as arguments.

A slice header ( https://golang.org/pkg/reflect/#SliceHeader ) can be simulated by having three arguments,

s = Argument(ptr(uintptr_t))
s_len = Argument(int64_t)
s_cap = Argument(int64_t)

(I know the documentation says to use ptrdiff_t to get Go's int, but that really makes the code confusing... In my code I'm using int64 explicitly because I know I'm on 64-bit platform.)

However, due to the lack of mapping for uintptr, this fails when trying to generate the commented function header -- I have to put an explicit integer type for s.

Similarly if I want to pass a pointer to a struct, using uintptr_t seems the only sane option.

Would size_t work for you? Its an equivalent of Go's uint

I think my goal is to find an integer type that would make semantic sense rather than just one which generates the correct assembly instructions. Reading the peachpy code and seeing size_t for a pointer and a ptrdiff_t for the length and capacity is confusing. There is already a uintptr_t type both in Go and C -- it just needs to be mapped.

I absolutely agree that one should use the type that makes semantic sense; I just don't think that uintptr_t is such type.

  • Semantically, size_t is the right type for index, length and capacity. E.g. in C++ both std::vector<T>::size() and std::vector<T>::capacity() return size_t. sizeof operator also returns a size_t.
  • uintptr_t is the right type to do integer arithmetic on pointers. E.g. if one wants to round a pointer to the next 16-byte boundary - using operators which are not supported for pointer types - one would cast the pointer to uintptr_t, do the arithmetics, and then cast back to pointer types.
  • There are platforms (albeit AFAIK none of them is supported by Go toolchain) where size_t and uintptr_t are different in C ABI. E.g. on some 16-bit platforms with segments arrays must fit into a single segment, limited in size by 64K; however, pointers are 32-bit wide. On such platforms size_t is be 16-bit wide (enough to hold length, capacity or index into any array), but uintptr_t is 32-bit wide (enough to cast a pointer).

Ah, so this is where my Go-centricity is confusing: It seems like the uintrptr type in Go (which is used in the runtime to represent an arbitrary pointer) doesn't quite match with uintptr_t in C.

I agree about size_t being semantically correct for length and capacity, but unfortunately size_t is unsigned and len/cap are both signed :( ( While negative lengths and capacity don't make sense, having them as regular Go ints make the code that uses them much simpler.)

Not sure there's any straight-forward fix here then :(

There is ssize_t as well (signed size_t), which should map to Go's int. The only cons is that ssize_t is a POSIX type, rather than C type, but if you plan to only use it with Go, this shouldn't be a problem.

This came out of me starting to write down a blog post with examples on integrating Go and PeachPy. It will probably have a large audience. I'd like to make suggestions that make sense both from the Go side and the PeachPy side. ssize_t seems like it solves the problem for length and capacity (although PeachPy seems to only use it in the types_map in Types.as_ctypes_type as the mapping for the ptrdiff_t type. It doesn't look like I can use s_len = Argument(ssize_t).

Right, I thought I implemented ssize_t in PeachPy, but I didn't. I suggest to use size_t for cap and length of the slice arguments. As they are always positive, the difference between signed/unsigned is not important.

So, to summarize:

  • To pass capacity and length components of slice, use PeachPy's size_t type
  • To pass other arguments of Go's int type, use PeachPy's ptrdiff_t type
  • To pass arguments of Go's uint type, use PeachPy's size_t type
  • To pass generic pointers (void* in C), use PeachPy's ptr() type

To pass generic pointers (void* in C), use PeachPy's ptr() type

Aha! This was the piece I was missing. When I was poking around previously I think I missed that you could just have a raw ptr() without having a pointer to something. This absolutely solves my need for the uintrptr type.

I'll use size_t for the length and capacity and ignore the signed/unsigned differences. Although I do have a slight (unfounded?) concern that PeachPy might generate the wrong instruction somewhere?

I do notice that if I use a plain ptr(), I don't get a function prototype comment in front of the generated assembly function. (Not vital to fix..) In Go, this is where the uintptr type would be used.

PeachPy uses argument types for two purposes:

  • To figure out how arguments are passed. In case of Golang, arguments are always passed on stack, and the stack offset is determined by type size. As uint and int have the same size, signedness doesn't affect their offset on stack.
  • To generate C header, if you invoke PeachPy with --emit-c-header option. Apparently, this is not your use-case.

Maybe

diff --git a/peachpy/x86_64/function.py b/peachpy/x86_64/function.py
index f4614d6..3e21117 100644
--- a/peachpy/x86_64/function.py
+++ b/peachpy/x86_64/function.py
@@ -126,6 +126,8 @@ class Function:
             assert isinstance(c_type, peachpy.Type)
             if c_type.is_pointer and c_type.base is not None:
                 return "*" + c_to_go_type(c_type.base)
+            elif c_type.is_pointer:
+                    return "uintptr"
             elif c_type.is_bool:
                 return "boolean"
             elif c_type.is_size_integer:

to add uintptr as the Go-type for raw pointers with no base type?

Thanks!