{"action":"created","issue":{"url":"https://api.github.com/repos/golang/go/issues/14876","repository_url":"https://api.github.com/repos/golang/go","labels_url":"https://api.github.com/repos/golang/go/issues/14876/labels{/name}","comments_url":"https://api.github.com/repos/golang/go/issues/14876/comments","events_url":"https://api.github.com/repos/golang/go/issues/14876/events","html_url":"https://github.com/golang/go/issues/14876","id":142036580,"number":14876,"title":"memmove's register usage doesn't respect windows/amd64 calling convention?","user":{"login":"sqweek","id":1590301,"avatar_url":"https://avatars.githubusercontent.com/u/1590301?v=3","gravatar_id":"","url":"https://api.github.com/users/sqweek","html_url":"https://github.com/sqweek","followers_url":"https://api.github.com/users/sqweek/followers","following_url":"https://api.github.com/users/sqweek/following{/other_user}","gists_url":"https://api.github.com/users/sqweek/gists{/gist_id}","starred_url":"https://api.github.com/users/sqweek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sqweek/subscriptions","organizations_url":"https://api.github.com/users/sqweek/orgs","repos_url":"https://api.github.com/users/sqweek/repos","events_url":"https://api.github.com/users/sqweek/events{/privacy}","received_events_url":"https://api.github.com/users/sqweek/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":1,"created_at":"2016-03-19T07:22:32Z","updated_at":"2016-03-19T09:19:32Z","closed_at":null,"body":"This was a bastard to diagnose. To summarise:\r\n\r\n1. Under windows the floating-point registers XMM6 to XMM15 are categorised as \"non-volatile\" across function calls (https://msdn.microsoft.com/en-us/library/9z1stfyw.aspx)\r\n2. `memmove_amd64.s` clobbers these registers in some cases without restoring their original values (specifically the 65 through 256 byte cases)\r\n\r\nFor pure go code this is perhaps a non-issue. In my case I have a long-lived C thread (spawned by portaudio) repeatedly calling back into go. After two days debugging it has become apparent that gcc is using XMM6 for a certain floating-point calculation, and is expecting the register to remain unmodified after executing the callback (which seems to be within its rights according to the MSDN link).\r\n\r\nHowever my go code calls the builtin `copy`. At some stage it tries to copy 66 bytes, causing memmove to clobber XMM6. Once the callback returns the C code keeps referring to XMM6 in further floating point operations, producing garbage.\r\n\r\nThis particular set of circumstances has left me unable to create a simple reproducer (my workspace is currently full of debugging hacks at every level), but I am entirely satisfied with this explanation. I've observed XMM6 being clobbered after the callback via gcc, and any of the following changes cause the symptom to disappear:\r\n\r\n1. Restructure the code so that every call to `copy` is of the same size (2,304 bytes)\r\n2. Avoid the 66-byte `copy` invocation \r\n3. Comment out the `JBE\tmove_65through128` and `JBE\tmove_129through256` lines in `memmove_amd64.s`\r\n4. Add a conditional `printf` to the C function (even if the condition is never met at runtime - presumably gcc decides storing the value in a register is no longer worth it?)\r\n\r\ngdb's register info before the callback:\r\n```\r\nxmm6           {..., v2_int64 = {0x40caafb7a95d3d0e, 0x0}, ...}\r\nxmm7           {..., v2_int64 = {0x3fe0000000000000, 0x0}, ...}\r\n```\r\n\r\nie. `float64(xmm6) = 13663.434856085598` which is inline with previous timings reported by my program:\r\n\r\n```\r\nAdc Time:                0, Current:  13663.414530729, Dac:  13663.498625786\r\nAdc Time:                0, Current:  13663.424691487, Dac:  13663.511714507\r\n```\r\n\r\nAfter the callback does its 66-byte memmove, gdb says:\r\n```\r\nxmm6           {..., v2_int64 = {0x7bf99add7ce993ee, 0x79bbab5b7ae9a2b2}, ...}\r\nxmm7           {..., v2_int64 = {0x7701bed9786db4c5, 0x73d0d4a57578c981}, ...}\r\n```\r\n\r\nAfter accounting for endianness this exactly matches the last 32 bytes in the buffer I passed to `copy`. So there is little doubt left as to what is happening - the only remaining question is whether gcc is in error for expecting the register to remain unchanged, or go is in error for not restoring the register's value. As I said earlier it looks like go's fault, but I am not an expert in this area and a single MSDN article is perhaps not entirely authoritative.\r\n\r\nI'm going to take a break from this problem now and enjoy the rest of my weekend, but happy to provide further information/prepare a minimal-as-possible reproducer as desired. \r\n\r\nFor completeness, my environment details:\r\n\r\n##### What version of Go are you using (`go version`)?\r\n\r\n```\r\ngo version go1.3.3 windows/amd64\r\ngo version go1.6 windows/amd64\r\n```\r\n(I've mainly been using 1.3.3 but have confirmed the problem still exists in 1.6)\r\n\r\n##### What operating system and processor architecture are you using (`go env`)?\r\n\r\n```\r\n$ go env\r\nset GOARCH=amd64\r\nset GOBIN=\r\nset GOCHAR=6\r\nset GOEXE=.exe\r\nset GOHOSTARCH=amd64\r\nset GOHOSTOS=windows\r\nset GOOS=windows\r\nset GOPATH=d:/code/go\r\nset GORACE=\r\nset GOROOT=C:\\Go\r\nset GOTOOLDIR=C:\\Go\\pkg\\tool\\windows_amd64\r\nset CC=gcc\r\nset GOGCCFLAGS=-m64 -mthreads -fmessage-length=0\r\nset CXX=g++\r\nset CGO_ENABLED=1\r\n\r\n$ gcc --version\r\ngcc.exe (rev5, Built by MinGW-W64 project) 4.8.1\r\n```\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/golang/go/issues/comments/198672025","html_url":"https://github.com/golang/go/issues/14876#issuecomment-198672025","issue_url":"https://api.github.com/repos/golang/go/issues/14876","id":198672025,"user":{"login":"sqweek","id":1590301,"avatar_url":"https://avatars.githubusercontent.com/u/1590301?v=3","gravatar_id":"","url":"https://api.github.com/users/sqweek","html_url":"https://github.com/sqweek","followers_url":"https://api.github.com/users/sqweek/followers","following_url":"https://api.github.com/users/sqweek/following{/other_user}","gists_url":"https://api.github.com/users/sqweek/gists{/gist_id}","starred_url":"https://api.github.com/users/sqweek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sqweek/subscriptions","organizations_url":"https://api.github.com/users/sqweek/orgs","repos_url":"https://api.github.com/users/sqweek/repos","events_url":"https://api.github.com/users/sqweek/events{/privacy}","received_events_url":"https://api.github.com/users/sqweek/received_events","type":"User","site_admin":false},"created_at":"2016-03-19T09:19:32Z","updated_at":"2016-03-19T09:19:32Z","body":"Apparently its not just gcc, [Wikipedia](https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions) has this to say:\r\n> In x86-64, Visual Studio 2008 stores floating point numbers in XMM6 and XMM7 (as well as XMM8 through XMM15); consequently, for x86-64, user-written assembly language routines must preserve XMM6 and XMM7 (as compared to x86 wherein user-written assembly language routines did not need to preserve XMM6 and XMM7). In other words, user-written assembly language routines must be updated to save/restore XMM6 and XMM7 before/after the function when being ported from x86 to x86-64."}}