-
Notifications
You must be signed in to change notification settings - Fork 18k
reflect: documentation about "Conversion of a reflect.SliceHeader or reflect.StringHeader Data field to or from Pointer" is misleading #41705
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I don't think that actually is a bug: assigning And indeed it does exactly that (https://play.golang.org/p/nYH-4TVrXWU): example.com$ go build -gcflags=-m -o=/dev/null . # example.com ./main.go:9:6: can inline demo ./main.go:19:13: inlining call to demo ./main.go:21:12: inlining call to fmt.Printf ./main.go:9:11: moved to heap: val_copy ./main.go:10:6: moved to heap: s ./main.go:19:13: moved to heap: val_copy ./main.go:21:13: s escapes to heap ./main.go:21:12: []interface {}{...} does not escape <autogenerated>:1: .this does not escape CC @mdempsky |
That said, it would be much clearer for the func demo(val_copy int64) string {
var s string
…
return s
} But that's a matter of readability, not correctness. |
@GingerMoon, given the above, is there some other way you think we should clarify the documentation? |
@GingerMoon As I understand your concern, it's that: (1) the documentation suggests your sample program will always print "ab00000000000000" (at least on little-endian CPUs), but (2) the Go compilers do not guarantee this. Do I understand that concern correctly? If so, then concern 2 is mistaken. The Go compilers do guarantee that output, and the documentation is correct to suggest it. However, I'm happy to understand the source of misunderstanding here so that we can improve the documentation and prevent future misunderstandings. |
Thanks you guys. Before talking about the document, I would like to share a strange case to show you why I have a higher expectation on the document.
The result returned by demo is not ALWAYS the same as our expectation.
|
The issue with that code is you've declared a variable The only supported unsafe use of |
The demo func has two problems:
This bug is difficult to debug becasue most of the time, the demo returns the expected result. The sample in the doc below makes people think that hdr.Data = uintptr(unsafe.Pointer(p)) is a correct usage without thinking of the restrictation of using uintptr.
So I'd like to change the document to remind people the risk of using uintptr.
Please correct me if my understanding is wrong. |
This is only an issue because of the first issue. The rules allow converting pointers to For example, this code that you originally shared is allowed:
|
Thanks to @mdempsky for sharing the insight!
// However, in the INVALID case below, hdr.Datar will not be the "strong" reference to p,
|
It seems to me that your new text basically says the same thing as the INVALID comment in the example. |
thank you guys for sharing. |
@ianlancetaylor @mdempsky
According to this description about uintptr, the demo below
&val_copy is lost because it's only referenced by hdr.Data, which is uintptr. In order to show the information below,
How about refine the doc as below?
|
I don't think we should make the promise you are describing, even if that is how the compiler works today. |
It is not the The |
The document says:
What if the ojbect pointed by the uintptr is moved in the case below?
|
Moved when, exactly? The doc is promising that if you write
then the value of |
The doc mentions the garbage collector can move objects, so "Moved when, exactly?" maybe during evacuation gc phace? Or just one of the gc phases. By the way, does golang gc move objects like Java G1GC? I would appreciate if you could share some links specifying how golang gc works. Considering the following case:
Let's assume p's value 0xaddress1. What does golang do in such cases?
Could you please tell me the difference between the valid and invalid conversions? |
In all of the current implementation of Go that I am aware of, the garbage collector never moves objects. However, the stack copier does move objects. There are various documents that describe different aspects of the garbage collector used by the gc runtime. The basic framework is described at https://golang.org/s/go14gc.
That turns out not to be the case. Go supports pointers to the interior of objects, and when those objects move the interior pointers are updated. If the address of Note that Java acts differently, as Java does not support pointers to the interior of objects. But in Go you can get such a pointer by writing something like
The valid ones are guaranteed to work. The invalid ones are not guaranteed to work. I don't know how else to explain it. |
Thanks a lot @ianlancetaylor for sharing!
According to to document
, so this is an invalid case. Actually SliceHeader should not be used for this purpose, no matter how we use it.
Am I right? |
@GingerMoon, your Note that proposal #19367 would make that sort of conversion much simpler in most cases. |
Can you explain why?
Since you are making the size and capacity a constant, you could do this instead:
No |
Goroutines have small stacks. In the gc implementation of Go, when a goroutine needs more stack space due to additional function calls, a new stack is allocated elsewhere and the existing stack is copied into the new space. Similarly, if a quiescent goroutine has a large stack, the garbage collector will allocate a smaller memory area, copy the stack into that smaller area, and release the now-unused large stack. Copying a stack requires adjusting all pointers to objects that live on the stack. Such pointers can only live on the stack; pointers from the heap to the stack are forbidden. (With one exception involving channel operations; that exception is handled specially.) |
The bench mark result shows that binary.xxxEndian takes 15 ns/op, while others take only 0.3 ns/op.
And one strange thing is, if I make UnsafeCastInt64ToBytes4.result a global, the whole bench mark is totally different:
And as you can see, in both cases, the implementation below is always stable as 0.27 ns/op
Any insight? |
or possibly
That's literally 1 cycle. I think your benchmark is not measuring the correct thing. Are you using the result? The whole body of the thing you are benchmarking might be optimized away. 1 cycle is the time I get for this benchmark:
|
You are right that I didn't use the result in this benchmark. Now I did another test, I use the result in the bench mark test as below:
This time, BenchmarkUnsafeCastInt64ToBytes3-8 instead of BenchmarkUnsafeCastInt64ToBytes2-8 is always 0.27 ns/op. As a conclusion, below are valid cases, am I right?
|
Yes, UnsafeCastInt64ToBytes2 and UnsafeCastInt64ToBytes3 are both using unsafe.Pointer correctly.
I would recommend UnsafeCastInt64ToBytes3 over UnsafeCastInt64ToBytes2. UnsafeCastInt64ToBytes3 is simpler, so the compiler will have an easier time optimizing it. (That's why the compiler is still able to optimize it away in your revised benchmarks.) I think we're getting away from the issue of whether/how to improve the package unsafe documentation though. |
thanks you all for the clarification! Closing the ticket. |
The document about "Conversion of a reflect.SliceHeader or reflect.StringHeader Data field to or from Pointer" is misleading.
It would be great if the doc specifies one more time the risk of the code below:
When p point to a temporary memory, we will have problem. For example,
The tricky thing is, it's often that the output is the same as expectation.
But this is a bug because &val_copy is not referenced by others except uintptr, hence the content of the memory pointed by &val_copy is undefined after the demo function returned.
The text was updated successfully, but these errors were encountered: