Skip to content
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

gccgo: MemmoveAtomicity fails on ppc64x since it uses C's memmove #41428

Closed
laboger opened this issue Sep 16, 2020 · 33 comments
Closed

gccgo: MemmoveAtomicity fails on ppc64x since it uses C's memmove #41428

laboger opened this issue Sep 16, 2020 · 33 comments
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@laboger
Copy link
Contributor

laboger commented Sep 16, 2020

In the testing of gccgo runtime package on ppc64le and ppc64 we are seeing this error:

[New Thread 0x7fffbeb8eff0 (LWP 166699)]
--- FAIL: TestMemmoveAtomicity (0.01s)
    --- FAIL: TestMemmoveAtomicity/24-backward (0.00s)
        memmove_test.go:267: got partially updated pointer 0xc000000000 at dst[0], want either nil or 0xc00028a088
    --- FAIL: TestMemmoveAtomicity/32-backward (0.00s)
        memmove_test.go:267: got partially updated pointer 0xc000000000 at dst[0], want either nil or 0xc00028a088
    --- FAIL: TestMemmoveAtomicity/48-backward (0.00s)
        memmove_test.go:267: got partially updated pointer 0xc000000000 at dst[0], want either nil or 0xc00028a088
    --- FAIL: TestMemmoveAtomicity/24-forward (0.00s)
        memmove_test.go:267: got partially updated pointer 0x28a088 at dst[2], want either nil or 0xc00028a088
FAIL

This has been happening since the addition of the MemmoveAtomicity test, but has been hidden because of a segv in other runtime tests which prevented all the runtime tests from running. For some reason this now shows up intermittently in the libgo.log.

According to this CL https://go-review.googlesource.com/c/go/+/213418/ it is understood that while Go's memmove copies pointers atomically, but C's memmove does not have that guarantee, and gccgo is using libc's memmove at least on ppc64le/ppc64.

@gopherbot gopherbot added this to the Gccgo milestone Sep 16, 2020
@cherrymui
Copy link
Member

That's unfortunate. It is somewhat surprising that C memmove breaks word boundary, even the thing we move is a multiple of word size and well aligned...

@toothrot toothrot added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Sep 16, 2020
@laboger
Copy link
Contributor Author

laboger commented Sep 18, 2020

I'm not really involved with what goes into glibc for each ISA and distro. Sometimes for a distro they want to chose a version that will work on multiple ISAs, and different ISAs might have different alignment requirements so that all affects its implementation. However the main point is that libc's memmove has no guarantee of atomicity of pointer copies so when it is implemented that is not taken into consideration. Having said that, I looked at the Go language definition and I don't see where it mentions that pointers are always aligned either. So couldn't there be a pointer within a struct that is unaligned because of the size of the previous structure members? I'm looking at the Go Programming Language document, at the very end where it says "size and alignment guarantees".

@randall77
Copy link
Contributor

We don't provide any alignment guarantees on pointers to the user.
We do require it internally, though. Pointers must be naturally aligned. Struct layout will insert padding if the field before a pointer does not end at pointer alignment. (And a struct containing a pointer must be pointer-aligned as well.)

@cherrymui
Copy link
Member

It is deeply baked in the runtime that pointers are aligned. If it is not, everything will go wrong. The compiler ensures pointers are aligned.

@laboger
Copy link
Contributor Author

laboger commented Oct 13, 2020

@ianlancetaylor Would it be possible to add (power) asm implementations of memove and memclr to gccgo that are similar to those implementations in golang?

@ianlancetaylor
Copy link
Contributor

Sure, it's possible. We have one piece of assembler code already in runtime/go-context.S. Of course it will be necessary to change the frontend to call those functions where appropriate, which may mean losing some compiler optimizations.

That said, I haven't looked into this issue, and I'm somewhat flabbergasted that the C library memmove function doesn't do the right thing. As far as I can see the natural way to write memmove on PPC64 would copy a pointer at a time. Any idea why that doesn't happen?

@cherrymui
Copy link
Member

I'm somewhat flabbergasted that the C library memmove function doesn't do the right thing. As far as I can see the natural way to write memmove on PPC64 would copy a pointer at a time.

This is my impression as well. Kind of surprising.

Of course it will be necessary to change the frontend to call those functions where appropriate, which may mean losing some compiler optimizations.

Maybe it is possible to play some symbol shadowing trick? In the frontend we still lower to __builtin_memmove and when the backend decides to generate an actual memmove call, our symbol shadows the libc symbol? Not sure if it would work. Also not sure about all build modes... (Kind of ugly anyway.)

@laboger
Copy link
Contributor Author

laboger commented Oct 19, 2020

That said, I haven't looked into this issue, and I'm somewhat flabbergasted that the C library memmove function doesn't do the right thing. As far as I can see the natural way to write memmove on PPC64 would copy a pointer at a time. Any idea why that doesn't happen?

There is no requirement that the memmove in libc ensure pointer atomicity, so they wouldn't have tested for it. On previous ISAs the alignment requirements varied, so that moving 8-bytes required certain alignment either for performance or correctness. In the memmove for the distro they wanted an implementation that would work for several instruction sets. In some cases doing 2 4-byte moves was as good as an 8-byte move if they didn't have to check for alignment.

@ianlancetaylor
Copy link
Contributor

OK, thanks. It's fine with me to add assembler code for the PPC memmove, as long as on other platforms we continue to call __builtin_memmove. This might involve a PPC-specific declaration of runtime.memmove. It looks like the compiler only calls __builtin_memmove for cases that do not involve pointers, so this might not be too hard.

@laboger
Copy link
Contributor Author

laboger commented Jan 7, 2021

We are also seeing this failure in issue13160.go intermittently, which I believe is the same issue:

panic: bad pointer read 0x27c038!

goroutine 176 [running]:
main.main..func2
        /home/boger/gccgo.git/gcc/gcc/testsuite/go.test/test/fixedbugs/issue13160.go:61
created by main.main
        /home/boger/gccgo.git/gcc/gcc/testsuite/go.test/test/fixedbugs/issue13160.go:56 +0x394
panic: bad pointer read 0xc000000000!

@laboger
Copy link
Contributor Author

laboger commented Jan 11, 2021

@ianlancetaylor What is your recommendation where the ppc64 asm implementation for memmove belongs?

@ianlancetaylor
Copy link
Contributor

I think libgo/runtime is probably the right place.

@tkdlqm2
Copy link

tkdlqm2 commented Jan 14, 2021

Sir, I have a issue like this... I wonder how long does it take to be implemented?

@laboger
Copy link
Contributor Author

laboger commented Jan 14, 2021

I'm not going to commit to a date on this at this point. What gcc release are you using and do you need it for big or little endian?

@tkdlqm2
Copy link

tkdlqm2 commented Jan 15, 2021

I am using gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 and little endian.

@laboger
Copy link
Contributor Author

laboger commented Jan 15, 2021

@ianlancetaylor I don't want to break older BE ISAs (< power7) but if I port over the Golang memmove to ppc64asm I don't know if it will work there and I have no way to test those. I assume it would be OK to use libc memmove for < power7 and the new asm for >= power7 so the old ones continue to work the same? Also I suspect the memmove atomicity isn't broken for < power7 but again can't test that.

@ianlancetaylor
Copy link
Contributor

It's OK with me to only use the new assembler code on newer processors.

We could also just write the code in C. Or switch between C and assembler based on the processor.

@laboger
Copy link
Contributor Author

laboger commented Jan 19, 2021

If this was written in C then that should work on all processors and no need to switch back and forth.

@ianlancetaylor
Copy link
Contributor

I would be fine with a C implementation but it might not be the fastest possible.

@tkdlqm2
Copy link

tkdlqm2 commented Jan 21, 2021

@ianlancetaylor Sir, are you going to fix this bug(memmove in glibc)?

@ianlancetaylor
Copy link
Contributor

@tkdlqm2 I'm leaving this for @laboger .

@laboger
Copy link
Contributor Author

laboger commented Jan 27, 2021

@Helflym Is this an issue on AIX for gccgo?

@laboger
Copy link
Contributor Author

laboger commented Jan 27, 2021

@ianlancetaylor I haven't had to update Makefiles or configure files in gcc for a while, so maybe I'm doing something wrong. If I regenerate the configure file in libgo from configure.ac using autoconf it doesn't match what's in master.

diff --git a/libgo/configure b/libgo/configure
index 70f64c974fd..b23f145480f 100755
--- a/libgo/configure
+++ b/libgo/configure
@@ -818,6 +818,7 @@ infodir
 docdir
 oldincludedir
 includedir
+runstatedir
 localstatedir
 sharedstatedir
 sysconfdir
@@ -900,6 +901,7 @@ datadir='${datarootdir}'
 sysconfdir='${prefix}/etc'
 sharedstatedir='${prefix}/com'
 localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
 includedir='${prefix}/include'
 oldincludedir='/usr/include'
 docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1152,6 +1154,15 @@ do
   | -silent | --silent | --silen | --sile | --sil)
     silent=yes ;;
 
+  -runstatedir | --runstatedir | --runstatedi | --runstated \
+  | --runstate | --runstat | --runsta | --runst | --runs \
+  | --run | --ru | --r)
+    ac_prev=runstatedir ;;
+  -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+  | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+  | --run=* | --ru=* | --r=*)
+    runstatedir=$ac_optarg ;;
+
   -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
     ac_prev=sbindir ;;
   -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1289,7 +1300,7 @@ fi
 for ac_var in  exec_prefix prefix bindir sbindir libexecdir datarootdir \
                datadir sysconfdir sharedstatedir localstatedir includedir \
                oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
-               libdir localedir mandir
+               libdir localedir mandir runstatedir
 do
   eval ac_val=\$$ac_var
   # Remove trailing slashes.
@@ -1442,6 +1453,7 @@ Fine tuning of the installation directories:
   --sysconfdir=DIR        read-only single-machine data [PREFIX/etc]
   --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
   --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --runstatedir=DIR       modifiable per-process data [LOCALSTATEDIR/run]
   --libdir=DIR            object code libraries [EPREFIX/lib]
   --includedir=DIR        C header files [PREFIX/include]
   --oldincludedir=DIR     C header files for non-gcc [/usr/include]
@@ -11544,7 +11556,7 @@ else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<_LT_EOF
-#line 11557 "configure"
+#line 11559 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -11650,7 +11662,7 @@ else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<_LT_EOF
-#line 11663 "configure"
+#line 11665 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H

If I add a new file go-memmove.c and only use it for PPC64 then it looks like I have to update configure.ac, regenerate configure, and update Makefile.in.

@Helflym
Copy link
Contributor

Helflym commented Jan 27, 2021

@laboger Honestly no idea. I don't think so. As long as you stay with C code, it should work. I'll have to check if the test is failing in first instance too. I haven't run gccgo testsuite for a while.

@laboger
Copy link
Contributor Author

laboger commented Jan 27, 2021

@Helflym I was mainly wondering if the fix should be done for AIX too. I think the answer is yes.

@Helflym
Copy link
Contributor

Helflym commented Jan 27, 2021

@laboger I think too. If it doesn't work, I'll revert for AIX. But I see no disadvantage to have a internal memmove instead of calling a libc version. Especially on AIX where libcalls are expensives.

@ianlancetaylor
Copy link
Contributor

@laboger You have to use an unpatched version of autoconf 2.69. Unfortunately distros often patch autoconf, so you can't use a distro autoconf, you have to build it yourself. That said, I can fix this up for you.

@laboger
Copy link
Contributor Author

laboger commented Jan 28, 2021

@ianlancetaylor I'm working on a fix for memmove which I believe works but now the test fails because of memclrNoHeapPointers which uses memset. Based on the function name I had assumed that it didn't require 8-byte atomicity, but the test expects it.

Where are these requirements documented?

@ianlancetaylor
Copy link
Contributor

Unfortunately I doubt that they are documented anywhere.

@cherrymui
Copy link
Member

memclrNoHeapPointers does need pointer atomicity, because it is the underlying implementation of memclrHasPointers (which is write barrier + memclrNoHeapPointers).

We probably want to document it somewhere.

@gopherbot
Copy link

Change https://golang.org/cl/287692 mentions this issue: runtime: document pointer write atomicity for memclrNoHeapPointers

gopherbot pushed a commit that referenced this issue Jan 29, 2021
memclrNoHeapPointers is the underlying implementation of
typedmemclr and memclrHasPointers, so it still needs to write
pointer-aligned words atomically. Document this requirement.

Updates #41428.

Change-Id: Ice00dee5de7a96a50e51ff019fcef069e8a8406a
Reviewed-on: https://go-review.googlesource.com/c/go/+/287692
Trust: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
@laboger
Copy link
Contributor Author

laboger commented Feb 9, 2021

I think the solution for this is to have Power asm versions of both memmove and memclr. We can't have a C memclr because the libc memset doesn't ensure 8 byte atomicity and the compiler will in some cases determine that C code could be done by the memset builtin and use that. If memset must be asm then might as well do memmove also.

Changed my mind again -- better to use C.

@gopherbot
Copy link

Change https://golang.org/cl/294931 mentions this issue: libgo: ensure memmove, memset 8 byte atomicity on ppc64x

nstester pushed a commit to nstester/gcc that referenced this issue Feb 26, 2021
Go requires that pointer moves are done 8 bytes at a time,
but gccgo uses libc's memmove and memset which does not require
that, and there are some cases where an 8 byte move might be
done as 4+4.

To enforce 8 byte moves for memmove and memset, this adds a
C implementation in libgo/runtime for memmove and memset to be
used on ppc64le and ppc64. Asm implementations were considered
but discarded to avoid different implementations for different
target ISAs.

Fixes golang/go#41428

Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/294931
@golang golang locked and limited conversation to collaborators Feb 26, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

9 participants