Commit cff0de3d authored by Chris Broadfoot's avatar Chris Broadfoot

[release-branch.go1.9] all: merge master into release-branch.go1.9

57912032 runtime: mapassign_* should use typedmemmove to update keys
38052559 all: remove some manual hyphenation
f096b5b3 runtime: mark activeModules nosplit/nowritebarrier
3e3da546 math/bits: fix example for OnesCount64
9b1e7cf2 math/bits: add examples for OnesCount functions
b01db023 misc/cgo/testsanitizers: also skip tsan11/tsan12 when using GCC
a279b53a reflect: document how DeepEqual handles cycles
909f409a doc: mention handling of moved GOROOT in 1.9 release notes
58ad0176 doc: use better wording to explain type-aware completion
92dac21d doc: replace paid with commercial
9bb98e02 doc/1.9: add CL 43712, ReverseProxy of HTTP/2 trailers to the release notes.
78d74fc2 doc: clarify that Gogland is for paid IntelliJ platform IDEs
54950472 doc/1.9: fix broken html link in CL 53030/53210
890e0e86 doc: fix bad link in go1.9 release notes
be596f04 doc/1.9: fix stray html in CL 53030
0173631d encoding/binary: add examples for varint functions
ac0ccf3c doc/1.9: add CL 36696 for crypto/x509 to the release notes
cc402c2c doc: hide blog content for golang.google.cn
f396fa42 internal/poll: don't add non-sockets to runtime poller
664cd26c cmd/vet: don't exit with failure on type checking error
a8730cd9 doc: hide video and share if being served from CN
b63db76c testsanitizers: check that tsan program runs, skip tsan10 on gcc
193eda72 time: skip ZoneAbbr test in timezones with no abbreviation
6f08c935 cmd/go: show examples with empty output in go test -list
f20944de cmd/compile: set/unset base register for better assembly print
623e2c46 runtime: map bitmap and spans during heap initialization
780249ee runtime: fall back to small mmaps if we fail to grow reservation
31b2c4cc .github: add .md extension to SUPPORT file
ac29f30d plugin: mention that there are known bugs with plugins
45a4609c cmd/dist: skip moved GOROOT on Go's Windows builders when not sharding tests
e157fac0 test: add README
835dfef9 runtime/pprof: prevent a deadlock that SIGPROF might create on mips{,le}
df91b804 doc: list editor options by name, not plugin name
3d9475c0 doc: cleanup editor page
b9661a14 doc: add Atom to editor guide
ee392ac1 cmd/compile: consider exported flag in namedata

Change-Id: I3a48493e8c05d97cb3b61635503ef0ccd646e5cb
parents 196492a2 57912032
...@@ -19,11 +19,12 @@ editing, navigation, testing, and debugging experience. ...@@ -19,11 +19,12 @@ editing, navigation, testing, and debugging experience.
</p> </p>
<ul> <ul>
<li><a href="https://github.com/fatih/vim-go">Vim Go</a>: a plugin for Vim to provide Go programming language support</li> <li><a href="https://github.com/fatih/vim-go">vim</a>: vim-go plugin provides Go programming language support</li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=lukehoban.Go">Visual Studio Code Go</a>: <li><a href="https://marketplace.visualstudio.com/items?itemName=lukehoban.Go">Visual Studio Code</a>:
an extension for Visual Studio Code to provide support for the Go programming language</li> Go extension provides support for the Go programming language</li>
<li><a href="https://www.jetbrains.com/go">Gogland</a>: Gogland is distributed either as a standalone IDE <li><a href="https://www.jetbrains.com/go">Gogland</a>: Gogland is distributed either as a standalone IDE
or as a plugin for the IntelliJ Platform IDEs</li> or as a plugin for the commercial IntelliJ Platform IDEs</li>
<li><a href="https://atom.io/packages/go-plus">Atom</a>: Go-Plus is an Atom package that provides enhanced Go support</li>
</ul> </ul>
<p> <p>
...@@ -41,133 +42,155 @@ The following feature matrix lists and compares the most significant features. ...@@ -41,133 +42,155 @@ The following feature matrix lists and compares the most significant features.
<table class="features-matrix"> <table class="features-matrix">
<tr> <tr>
<th></th> <th></th>
<th><img title="Vim Go" src="/doc/editors/vimgo.png"><br>Vim Go</th> <th><img title="Vim Go" src="/doc/editors/vimgo.png"><br>vim</th>
<th><img title="Visual Studio Code" src="/doc/editors/vscodego.png"><br>Visual Studio Code Go</th> <th><img title="Visual Studio Code" src="/doc/editors/vscodego.png"><br>Visual Studio Code</th>
<th><img title="Gogland" src="/doc/editors/gogland.png"><br>Gogland</th> <th><img title="Gogland" src="/doc/editors/gogland.png"><br>Gogland</th>
<th><img title="Go-Plus" src="/doc/editors/go-plus.png"><br>Atom</th>
</tr> </tr>
<tr> <tr>
<td class="feature-row" colspan="4">Editing features</td> <td class="feature-row" colspan="5">Editing features</td>
</tr> </tr>
<tr> <tr>
<td>Build and run from the editor/IDE</td> <td>Build and run from the editor/IDE</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td>
</tr> </tr>
<tr> <tr>
<td>Autocompletion of identifers (variable, method, and function names)</td> <td>Autocompletion of identifers (variable, method, and function names)</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td>
</tr> </tr>
<tr> <tr>
<td>Autocompletion based on type</td> <td>Type-aware autocompletion</td>
<td class="no">No</td> <td class="no">No</td>
<td class="no">No</td> <td class="no">No</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="no">No</td>
</tr> </tr>
<tr> <tr>
<td>Rename identifiers</td> <td>Rename identifiers</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td>
</tr> </tr>
<tr> <tr>
<td>Auto format, build, vet, and lint on save</td> <td>Auto format, build, vet, and lint on save</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes<sup>1</sup></td> <td class="yes">Yes<sup>1</sup></td>
<td class="yes">Yes</td>
</tr> </tr>
<tr> <tr>
<td>Auto insert import paths and remove unused on save</td> <td>Auto insert import paths and remove unused on save</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes<sup>2</sup></td> <td class="yes">Yes<sup>2</sup></td>
<td class="yes">Yes</td>
</tr> </tr>
<tr> <tr>
<td>Auto generate JSON, XML tags for struct fields</td> <td>Auto generate JSON, XML tags for struct fields</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td>
</tr> </tr>
<tr> <tr>
<td class="feature-row" colspan="4">Navigation features</td> <td class="feature-row" colspan="5">Navigation features</td>
</tr> </tr>
<tr> <tr>
<td>Display documentation inline, or open godoc in browser</td> <td>Display documentation inline, or open godoc in browser</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td>
</tr> </tr>
<tr> <tr>
<td>Switch between <code>*.go</code> and <code>*_test.go</code> file</td> <td>Switch between <code>*.go</code> and <code>*_test.go</code> file</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">No</td>
</tr> </tr>
<tr> <tr>
<td>Jump to definition and referees</td> <td>Jump to definition and referees</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td>
</tr> </tr>
<tr> <tr>
<td>Look up for interface implementations</td> <td>Look up for interface implementations</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td>
</tr> </tr>
<tr> <tr>
<td>Search for callers and callees</td> <td>Search for callers and callees</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td>
</tr> </tr>
<tr> <tr>
<td class="feature-row" colspan="4">Testing and debugging features</td> <td class="feature-row" colspan="5">Testing and debugging features</td>
</tr> </tr>
<tr> <tr>
<td>Debugger support</td> <td>Debugger support</td>
<td class="no">No</td> <td class="no">No</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes<sup>3</sup></td>
</tr> </tr>
<tr> <tr>
<td>Run a single test case, all tests from file, or all tests from a package</td> <td>Run a single test case, all tests from file, or all tests from a package</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="no">No</td>
</tr> </tr>
<tr> <tr>
<td>Auto generate tests for packages, files and identifiers</td> <td>Auto generate tests for packages, files and identifiers</td>
<td class="no">No</td> <td class="no">No</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="no">No</td> <td class="no">No</td>
<td class="no">No</td>
</tr> </tr>
<tr> <tr>
<td>Debug tests</td> <td>Debug tests</td>
<td class="no">No</td> <td class="no">No</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes<sup>3</sup></td>
</tr> </tr>
<tr> <tr>
<td>Display test coverage</td> <td>Display test coverage</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td> <td class="yes">Yes</td>
<td class="yes">Yes</td>
</tr> </tr>
<tr class="download"> <tr class="download">
<td></td> <td></td>
<td><a href="https://github.com/fatih/vim-go">Install<a/></td> <td><a href="https://github.com/fatih/vim-go">Install<a/></td>
<td><a href="https://marketplace.visualstudio.com/items?itemName=lukehoban.Go">Install<a/></td> <td><a href="https://marketplace.visualstudio.com/items?itemName=lukehoban.Go">Install<a/></td>
<td><a href="https://www.jetbrains.com/go">Install<a/></td> <td><a href="https://www.jetbrains.com/go">Install<a/></td>
<td><a href="https://atom.io/packages/go-plus">Install</a></td>
</tr> </tr>
</table> </table>
<p> <p>
<sup>1</sup>: Possible when enabled via Settings &gt; Go &gt; On Save, <code>go</code> <code>vet</code> and <code>golint</code> are available via plugins. Also runs tests on save if configured. <sup>1</sup>Possible when enabled via Settings &gt; Go &gt; On Save, <code>go</code> <code>vet</code> and <code>golint</code> are available via plugins. Also runs tests on save if configured.
<br> <br>
<sup>2</sup>: Additionally, user input can disambiguate when two or more options are available. <sup>2</sup>Additionally, user input can disambiguate when two or more options are available.
<br>
<sup>3</sup>Available if the <a href="https://atom.io/packages/go-debug">go-debug</a> package is installed.
</p> </p>
</div> </div>
...@@ -206,5 +229,3 @@ The following feature matrix lists and compares the most significant features. ...@@ -206,5 +229,3 @@ The following feature matrix lists and compares the most significant features.
font-weight: bold; font-weight: bold;
} }
</style> </style>
<!--TODO(jbd): Add the Atom comparison-->
\ No newline at end of file
...@@ -156,6 +156,21 @@ type T1 = T2 ...@@ -156,6 +156,21 @@ type T1 = T2
directories, write <code>./vendor/...</code>. directories, write <code>./vendor/...</code>.
</p> </p>
<h3 id="goroot">Moved GOROOT</h3>
<p><!-- CL 42533 -->
The <a href="/cmd/go/">go tool</a> will now use the path from which it
was invoked to attempt to locate the root of the Go install tree.
This means that if the entire Go installation is moved to a new
location, the go tool should continue to work as usual.
This may be overriden by setting <code>GOROOT</code> in the environment,
which should only be done in unusual circumstances.
Note that this does not affect the result of
the <a href="/pkg/runtime/#GOROOT">runtime.GOROOT</a> function, which
will continue to report the original installation location;
this may be fixed in later releases.
</p>
<h3 id="compiler">Compiler Toolchain</h3> <h3 id="compiler">Compiler Toolchain</h3>
<p><!-- CL 37441 --> <p><!-- CL 37441 -->
...@@ -473,6 +488,15 @@ version of gccgo. ...@@ -473,6 +488,15 @@ version of gccgo.
populated. populated.
</p> </p>
<p><!-- CL 36696 -->
If any SAN extension, including with no DSN names, is present
in the certificate, then the Common Name from
<a href="/pkg/crypto/x509/#Certificate.Subject"><code>Subject</code></a> is ignored.
In previous releases, the code tested only whether DNS-name SANs were
present in a certificate.
</p>
</dl><!-- crypto/x509 --> </dl><!-- crypto/x509 -->
<dl id="database/sql"><dt><a href="/pkg/database/sql/">database/sql</a></dt> <dl id="database/sql"><dt><a href="/pkg/database/sql/">database/sql</a></dt>
...@@ -728,7 +752,7 @@ version of gccgo. ...@@ -728,7 +752,7 @@ version of gccgo.
<li><!-- CL 35488 --> <li><!-- CL 35488 -->
The <a href="/pkg/net/http/#Transport"><code>Transport</code></a> The <a href="/pkg/net/http/#Transport"><code>Transport</code></a>
now supports making requests via SOCKS5 proxy when the URL returned by now supports making requests via SOCKS5 proxy when the URL returned by
<a href="/net/http/#Transport.Proxy"><code>Transport.Proxy</code></a> <a href="/pkg/net/http/#Transport.Proxy"><code>Transport.Proxy</code></a>
has the scheme <code>socks5</code>. has the scheme <code>socks5</code>.
</li> </li>
</ul> </ul>
...@@ -764,6 +788,16 @@ version of gccgo. ...@@ -764,6 +788,16 @@ version of gccgo.
</dl><!-- net/http/httptest --> </dl><!-- net/http/httptest -->
<dl id="net/http/httputil"><dt><a href="/pkg/net/http/httputil/">net/http/httputil</a></dt>
<dd>
<p><!-- CL 43712 -->
The <a href="/pkg/net/http/httputil/#ReverseProxy"><code>ReverseProxy</code></a>
now proxies all HTTP/2 response trailers, even those not declared in the initial response
header. Such undeclared trailers are used by the gRPC protocol.
</p>
</dl><!-- net/http/httputil -->
<dl id="os"><dt><a href="/pkg/os/">os</a></dt> <dl id="os"><dt><a href="/pkg/os/">os</a></dt>
<dd> <dd>
<p><!-- CL 36800 --> <p><!-- CL 36800 -->
......
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
<div class="left"> <div class="left">
<div id="learn"> <div id="learn">
{{if not $.GoogleCN}}
<a class="popout share">Pop-out</a> <a class="popout share">Pop-out</a>
{{end}}
<div class="rootHeading">Try Go</div> <div class="rootHeading">Try Go</div>
<div class="input"> <div class="input">
<textarea spellcheck="false" class="code">// You can edit this code! <textarea spellcheck="false" class="code">// You can edit this code!
...@@ -26,10 +28,10 @@ Hello, 世界 ...@@ -26,10 +28,10 @@ Hello, 世界
</div> </div>
<div class="buttons"> <div class="buttons">
<a class="run" href="#" title="Run this code [shift-enter]">Run</a> <a class="run" href="#" title="Run this code [shift-enter]">Run</a>
{{if $.Share}} {{if not $.GoogleCN}}
<a class="share" href="#" title="Share this code">Share</a> <a class="share" href="#" title="Share this code">Share</a>
{{end}}
<a class="tour" href="//tour.golang.org/" title="Learn Go from your browser">Tour</a> <a class="tour" href="//tour.golang.org/" title="Learn Go from your browser">Tour</a>
{{end}}
</div> </div>
<div class="toys"> <div class="toys">
<select> <select>
...@@ -68,85 +70,91 @@ Linux, Mac OS X, Windows, and more. ...@@ -68,85 +70,91 @@ Linux, Mac OS X, Windows, and more.
<div style="clear: both"></div> <div style="clear: both"></div>
{{if not $.GoogleCN}}
<div class="left"> <div class="left">
<div id="video">
<div id="video"> <div class="rootHeading">Featured video</div>
<div class="rootHeading">Featured video</div> <iframe width="415" height="241" src="//www.youtube.com/embed/ytEkHepK08c" frameborder="0" allowfullscreen></iframe>
<iframe width="415" height="241" src="//www.youtube.com/embed/ytEkHepK08c" frameborder="0" allowfullscreen></iframe> </div>
</div>
</div> </div>
<div class="right"> <div class="right">
<div id="blog">
<div id="blog"> <div class="rootHeading">Featured articles</div>
<div class="rootHeading">Featured articles</div> <div class="read"><a href="//blog.golang.org/">Read more</a></div>
<div class="read"><a href="//blog.golang.org/">Read more</a></div> </div>
</div>
</div> </div>
{{end}}
<div style="clear: both;"></div> <div style="clear: both;"></div>
<script type="text/javascript"> <script>
(function() {
function readableTime(t) { 'use strict';
var m = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"]; window.initFuncs.push(function() {
var p = t.substring(0, t.indexOf("T")).split("-"); // Set up playground if enabled.
var d = new Date(p[0], p[1]-1, p[2]); if (window.playground) {
return d.getDate() + " " + m[d.getMonth()] + " " + d.getFullYear(); window.playground({
} "codeEl": "#learn .code",
"outputEl": "#learn .output",
function feedLoaded(result) { "runEl": "#learn .run",
var blog = document.getElementById("blog"); "shareEl": "#learn .share",
var read = blog.getElementsByClassName("read")[0]; "shareRedirect": "//play.golang.org/p/",
for (var i = 0; i < result.length && i < 2; i++) { "toysEl": "#learn .toys select"
var entry = result[i]; });
var title = document.createElement("a"); } else {
title.className = "title"; $('#learn').hide()
title.href = entry.Link; }
title.innerHTML = entry.Title; });
blog.insertBefore(title, read);
var extract = document.createElement("div"); {{if not $.GoogleCN}}
extract.className = "extract";
extract.innerHTML = entry.Summary; function readableTime(t) {
blog.insertBefore(extract, read); var m = ["January", "February", "March", "April", "May", "June", "July",
var when = document.createElement("div"); "August", "September", "October", "November", "December"];
when.className = "when"; var p = t.substring(0, t.indexOf("T")).split("-");
when.innerHTML = "Published " + readableTime(entry.Time); var d = new Date(p[0], p[1]-1, p[2]);
blog.insertBefore(when, read); return d.getDate() + " " + m[d.getMonth()] + " " + d.getFullYear();
}
}
window.initFuncs.push(function() {
// Set up playground if enabled.
if (window.playground) {
window.playground({
"codeEl": "#learn .code",
"outputEl": "#learn .output",
"runEl": "#learn .run",
"shareEl": "#learn .share",
"shareRedirect": "//play.golang.org/p/",
"toysEl": "#learn .toys select"
});
} else {
$('#learn').hide()
} }
// Load blog feed. window.feedLoaded = function(result) {
$('<script/>').attr('text', 'text/javascript') var blog = document.getElementById("blog");
.attr('src', '//blog.golang.org/.json?jsonp=feedLoaded') var read = blog.getElementsByClassName("read")[0];
.appendTo('body'); for (var i = 0; i < result.length && i < 2; i++) {
var entry = result[i];
// Set the video at random. var title = document.createElement("a");
var videos = [ title.className = "title";
{h: 241, s: "//www.youtube.com/embed/ytEkHepK08c"}, // Tour of Go title.href = entry.Link;
{h: 241, s: "//www.youtube.com/embed/f6kdp27TYZs"}, // Concurrency Patterns title.innerHTML = entry.Title;
{h: 233, s: "//player.vimeo.com/video/69237265"} // Simple environment blog.insertBefore(title, read);
]; var extract = document.createElement("div");
var v = videos[Math.floor(Math.random()*videos.length)]; extract.className = "extract";
$('#video iframe').attr('height', v.h).attr('src', v.s); extract.innerHTML = entry.Summary;
}); blog.insertBefore(extract, read);
var when = document.createElement("div");
when.className = "when";
when.innerHTML = "Published " + readableTime(entry.Time);
blog.insertBefore(when, read);
}
}
window.initFuncs.push(function() {
// Load blog feed.
$('<script/>').attr('text', 'text/javascript')
.attr('src', '//blog.golang.org/.json?jsonp=feedLoaded')
.appendTo('body');
// Set the video at random.
var videos = [
{h: 241, s: "//www.youtube.com/embed/ytEkHepK08c"}, // Tour of Go
{h: 241, s: "//www.youtube.com/embed/f6kdp27TYZs"}, // Concurrency Patterns
{h: 233, s: "//player.vimeo.com/video/69237265"} // Simple environment
];
var v = videos[Math.floor(Math.random()*videos.length)];
$('#video iframe').attr('height', v.h).attr('src', v.s);
});
{{end}}
})();
</script> </script>
...@@ -343,6 +343,14 @@ var ptrTests = []ptrTest{ ...@@ -343,6 +343,14 @@ var ptrTests = []ptrTest{
body: `var b C.char; p := &b; C.f((*C.u)(unsafe.Pointer(&p)))`, body: `var b C.char; p := &b; C.f((*C.u)(unsafe.Pointer(&p)))`,
fail: false, fail: false,
}, },
{
// Issue #21306.
name: "preempt-during-call",
c: `void f() {}`,
imports: []string{"runtime", "sync"},
body: `var wg sync.WaitGroup; wg.Add(100); for i := 0; i < 100; i++ { go func(i int) { for j := 0; j < 100; j++ { C.f(); runtime.GOMAXPROCS(i) }; wg.Done() }(i) }; wg.Wait()`,
fail: false,
},
} }
func main() { func main() {
......
...@@ -156,15 +156,18 @@ if test "$tsan" = "yes"; then ...@@ -156,15 +156,18 @@ if test "$tsan" = "yes"; then
if ! $CC -fsanitize=thread ${TMPDIR}/testsanitizers$$.c -o ${TMPDIR}/testsanitizers$$ &> ${TMPDIR}/testsanitizers$$.err; then if ! $CC -fsanitize=thread ${TMPDIR}/testsanitizers$$.c -o ${TMPDIR}/testsanitizers$$ &> ${TMPDIR}/testsanitizers$$.err; then
ok=no ok=no
fi fi
if grep "unrecognized" ${TMPDIR}/testsanitizers$$.err >& /dev/null; then if grep "unrecognized" ${TMPDIR}/testsanitizers$$.err >& /dev/null; then
echo "skipping tsan tests: -fsanitize=thread not supported" echo "skipping tsan tests: -fsanitize=thread not supported"
tsan=no tsan=no
elif test "$ok" != "yes"; then elif test "$ok" != "yes"; then
cat ${TMPDIR}/testsanitizers$$.err cat ${TMPDIR}/testsanitizers$$.err
echo "skipping tsan tests: -fsanitizer=thread build failed" echo "skipping tsan tests: -fsanitizer=thread build failed"
tsan=no tsan=no
fi elif ! ${TMPDIR}/testsanitizers$$ 2>&1; then
rm -f ${TMPDIR}/testsanitizers$$* echo "skipping tsan tests: running tsan program failed"
tsan=no
fi
rm -f ${TMPDIR}/testsanitizers$$*
fi fi
# Run a TSAN test. # Run a TSAN test.
...@@ -196,8 +199,10 @@ if test "$tsan" = "yes"; then ...@@ -196,8 +199,10 @@ if test "$tsan" = "yes"; then
# These tests are only reliable using clang or GCC version 7 or later. # These tests are only reliable using clang or GCC version 7 or later.
# Otherwise runtime/cgo/libcgo.h can't tell whether TSAN is in use. # Otherwise runtime/cgo/libcgo.h can't tell whether TSAN is in use.
ok=false ok=false
clang=false
if ${CC} --version | grep clang >/dev/null 2>&1; then if ${CC} --version | grep clang >/dev/null 2>&1; then
ok=true ok=true
clang=true
else else
ver=$($CC -dumpversion) ver=$($CC -dumpversion)
major=$(echo $ver | sed -e 's/\([0-9]*\).*/\1/') major=$(echo $ver | sed -e 's/\([0-9]*\).*/\1/')
...@@ -213,9 +218,13 @@ if test "$tsan" = "yes"; then ...@@ -213,9 +218,13 @@ if test "$tsan" = "yes"; then
testtsan tsan5.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan" testtsan tsan5.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
testtsan tsan6.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan" testtsan tsan6.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
testtsan tsan7.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan" testtsan tsan7.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
testtsan tsan10.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
testtsan tsan11.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan" # The remaining tests reportedly hang when built with GCC; issue #21196.
testtsan tsan12.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan" if test "$clang" = "true"; then
testtsan tsan10.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
testtsan tsan11.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
testtsan tsan12.go "CGO_CFLAGS=-fsanitize=thread CGO_LDFLAGS=-fsanitize=thread" "-installsuffix=tsan"
fi
testtsanshared testtsanshared
fi fi
......
...@@ -464,6 +464,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ...@@ -464,6 +464,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
case ssa.OpARMMOVWaddr: case ssa.OpARMMOVWaddr:
p := s.Prog(arm.AMOVW) p := s.Prog(arm.AMOVW)
p.From.Type = obj.TYPE_ADDR p.From.Type = obj.TYPE_ADDR
p.From.Reg = v.Args[0].Reg()
p.To.Type = obj.TYPE_REG p.To.Type = obj.TYPE_REG
p.To.Reg = v.Reg() p.To.Reg = v.Reg()
...@@ -485,7 +486,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ...@@ -485,7 +486,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
case nil: case nil:
// No sym, just MOVW $off(SP), R // No sym, just MOVW $off(SP), R
wantreg = "SP" wantreg = "SP"
p.From.Reg = arm.REGSP
p.From.Offset = v.AuxInt p.From.Offset = v.AuxInt
} }
if reg := v.Args[0].RegName(); reg != wantreg { if reg := v.Args[0].RegName(); reg != wantreg {
......
...@@ -260,6 +260,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ...@@ -260,6 +260,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
case ssa.OpARM64MOVDaddr: case ssa.OpARM64MOVDaddr:
p := s.Prog(arm64.AMOVD) p := s.Prog(arm64.AMOVD)
p.From.Type = obj.TYPE_ADDR p.From.Type = obj.TYPE_ADDR
p.From.Reg = v.Args[0].Reg()
p.To.Type = obj.TYPE_REG p.To.Type = obj.TYPE_REG
p.To.Reg = v.Reg() p.To.Reg = v.Reg()
...@@ -281,7 +282,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ...@@ -281,7 +282,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
case nil: case nil:
// No sym, just MOVD $off(SP), R // No sym, just MOVD $off(SP), R
wantreg = "SP" wantreg = "SP"
p.From.Reg = arm64.REGSP
p.From.Offset = v.AuxInt p.From.Offset = v.AuxInt
} }
if reg := v.Args[0].RegName(); reg != wantreg { if reg := v.Args[0].RegName(); reg != wantreg {
......
...@@ -898,6 +898,17 @@ var linuxAMD64Tests = []*asmTest{ ...@@ -898,6 +898,17 @@ var linuxAMD64Tests = []*asmTest{
}`, }`,
[]string{"\tCMPL\t[A-Z]"}, []string{"\tCMPL\t[A-Z]"},
}, },
{
// make sure assembly output has matching offset and base register.
`
func f72(a, b int) int {
var x [16]byte // use some frame
_ = x
return b
}
`,
[]string{"b\\+40\\(SP\\)"},
},
} }
var linux386Tests = []*asmTest{ var linux386Tests = []*asmTest{
...@@ -1302,6 +1313,17 @@ var linuxARMTests = []*asmTest{ ...@@ -1302,6 +1313,17 @@ var linuxARMTests = []*asmTest{
`, `,
[]string{"\tCLZ\t"}, []string{"\tCLZ\t"},
}, },
{
// make sure assembly output has matching offset and base register.
`
func f13(a, b int) int {
var x [16]byte // use some frame
_ = x
return b
}
`,
[]string{"b\\+4\\(FP\\)"},
},
} }
var linuxARM64Tests = []*asmTest{ var linuxARM64Tests = []*asmTest{
...@@ -1473,7 +1495,7 @@ var linuxARM64Tests = []*asmTest{ ...@@ -1473,7 +1495,7 @@ var linuxARM64Tests = []*asmTest{
return return
} }
`, `,
[]string{"\tMOVD\t\"\"\\.a\\+[0-9]+\\(RSP\\), R[0-9]+", "\tMOVD\tR[0-9]+, \"\"\\.b\\+[0-9]+\\(RSP\\)"}, []string{"\tMOVD\t\"\"\\.a\\+[0-9]+\\(FP\\), R[0-9]+", "\tMOVD\tR[0-9]+, \"\"\\.b\\+[0-9]+\\(FP\\)"},
}, },
} }
......
...@@ -273,6 +273,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ...@@ -273,6 +273,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
case ssa.OpMIPSMOVWaddr: case ssa.OpMIPSMOVWaddr:
p := s.Prog(mips.AMOVW) p := s.Prog(mips.AMOVW)
p.From.Type = obj.TYPE_ADDR p.From.Type = obj.TYPE_ADDR
p.From.Reg = v.Args[0].Reg()
var wantreg string var wantreg string
// MOVW $sym+off(base), R // MOVW $sym+off(base), R
// the assembler expands it as the following: // the assembler expands it as the following:
...@@ -291,7 +292,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ...@@ -291,7 +292,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
case nil: case nil:
// No sym, just MOVW $off(SP), R // No sym, just MOVW $off(SP), R
wantreg = "SP" wantreg = "SP"
p.From.Reg = mips.REGSP
p.From.Offset = v.AuxInt p.From.Offset = v.AuxInt
} }
if reg := v.Args[0].RegName(); reg != wantreg { if reg := v.Args[0].RegName(); reg != wantreg {
......
...@@ -247,6 +247,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ...@@ -247,6 +247,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
case ssa.OpMIPS64MOVVaddr: case ssa.OpMIPS64MOVVaddr:
p := s.Prog(mips.AMOVV) p := s.Prog(mips.AMOVV)
p.From.Type = obj.TYPE_ADDR p.From.Type = obj.TYPE_ADDR
p.From.Reg = v.Args[0].Reg()
var wantreg string var wantreg string
// MOVV $sym+off(base), R // MOVV $sym+off(base), R
// the assembler expands it as the following: // the assembler expands it as the following:
...@@ -265,7 +266,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ...@@ -265,7 +266,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
case nil: case nil:
// No sym, just MOVV $off(SP), R // No sym, just MOVV $off(SP), R
wantreg = "SP" wantreg = "SP"
p.From.Reg = mips.REGSP
p.From.Offset = v.AuxInt p.From.Offset = v.AuxInt
} }
if reg := v.Args[0].RegName(); reg != wantreg { if reg := v.Args[0].RegName(); reg != wantreg {
......
...@@ -638,6 +638,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ...@@ -638,6 +638,7 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
case ssa.OpPPC64MOVDaddr: case ssa.OpPPC64MOVDaddr:
p := s.Prog(ppc64.AMOVD) p := s.Prog(ppc64.AMOVD)
p.From.Type = obj.TYPE_ADDR p.From.Type = obj.TYPE_ADDR
p.From.Reg = v.Args[0].Reg()
p.To.Type = obj.TYPE_REG p.To.Type = obj.TYPE_REG
p.To.Reg = v.Reg() p.To.Reg = v.Reg()
...@@ -660,7 +661,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) { ...@@ -660,7 +661,6 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
case nil: case nil:
// No sym, just MOVD $off(SP), R // No sym, just MOVD $off(SP), R
wantreg = "SP" wantreg = "SP"
p.From.Reg = ppc64.REGSP
p.From.Offset = v.AuxInt p.From.Offset = v.AuxInt
} }
if reg := v.Args[0].RegName(); reg != wantreg { if reg := v.Args[0].RegName(); reg != wantreg {
......
...@@ -4314,3 +4314,20 @@ func TestTestRegexps(t *testing.T) { ...@@ -4314,3 +4314,20 @@ func TestTestRegexps(t *testing.T) {
t.Errorf("reduced output:<<<\n%s>>> want:<<<\n%s>>>", have, want) t.Errorf("reduced output:<<<\n%s>>> want:<<<\n%s>>>", have, want)
} }
} }
func TestListTests(t *testing.T) {
var tg *testgoData
testWith := func(listName, expected string) func(*testing.T) {
return func(t *testing.T) {
tg = testgo(t)
defer tg.cleanup()
tg.run("test", "./testdata/src/testlist/...", fmt.Sprintf("-list=%s", listName))
tg.grepStdout(expected, fmt.Sprintf("-test.list=%s returned %q, expected %s", listName, tg.getStdout(), expected))
}
}
t.Run("Test", testWith("Test", "TestSimple"))
t.Run("Bench", testWith("Benchmark", "BenchmarkSimple"))
t.Run("Example1", testWith("Example", "ExampleSimple"))
t.Run("Example2", testWith("Example", "ExampleWithEmptyOutput"))
}
package testlist
import (
"fmt"
"testing"
)
func BenchmarkSimplefunc(b *testing.B) {
b.StopTimer()
b.StartTimer()
for i := 0; i < b.N; i++ {
_ = fmt.Sprint("Test for bench")
}
}
package testlist
import (
"fmt"
)
func ExampleSimple() {
fmt.Println("Test with Output.")
// Output: Test with Output.
}
func ExampleWithEmptyOutput() {
fmt.Println("")
// Output:
}
func ExampleNoOutput() {
_ = fmt.Sprint("Test with no output")
}
package testlist
import (
"fmt"
"testing"
)
func TestSimple(t *testing.T) {
_ = fmt.Sprint("Test simple")
}
...@@ -1167,6 +1167,11 @@ func (c *ctxt5) aclass(a *obj.Addr) int { ...@@ -1167,6 +1167,11 @@ func (c *ctxt5) aclass(a *obj.Addr) int {
return C_ADDR return C_ADDR
case obj.NAME_AUTO: case obj.NAME_AUTO:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-SP.
a.Reg = obj.REG_NONE
}
c.instoffset = c.autosize + a.Offset c.instoffset = c.autosize + a.Offset
if t := immaddr(int32(c.instoffset)); t != 0 { if t := immaddr(int32(c.instoffset)); t != 0 {
if immhalf(int32(c.instoffset)) { if immhalf(int32(c.instoffset)) {
...@@ -1185,6 +1190,11 @@ func (c *ctxt5) aclass(a *obj.Addr) int { ...@@ -1185,6 +1190,11 @@ func (c *ctxt5) aclass(a *obj.Addr) int {
return C_LAUTO return C_LAUTO
case obj.NAME_PARAM: case obj.NAME_PARAM:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-FP.
a.Reg = obj.REG_NONE
}
c.instoffset = c.autosize + a.Offset + 4 c.instoffset = c.autosize + a.Offset + 4
if t := immaddr(int32(c.instoffset)); t != 0 { if t := immaddr(int32(c.instoffset)); t != 0 {
if immhalf(int32(c.instoffset)) { if immhalf(int32(c.instoffset)) {
...@@ -1285,10 +1295,20 @@ func (c *ctxt5) aclass(a *obj.Addr) int { ...@@ -1285,10 +1295,20 @@ func (c *ctxt5) aclass(a *obj.Addr) int {
return C_LCONADDR return C_LCONADDR
case obj.NAME_AUTO: case obj.NAME_AUTO:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-SP.
a.Reg = obj.REG_NONE
}
c.instoffset = c.autosize + a.Offset c.instoffset = c.autosize + a.Offset
return c.aconsize() return c.aconsize()
case obj.NAME_PARAM: case obj.NAME_PARAM:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-FP.
a.Reg = obj.REG_NONE
}
c.instoffset = c.autosize + a.Offset + 4 c.instoffset = c.autosize + a.Offset + 4
return c.aconsize() return c.aconsize()
} }
......
...@@ -1149,10 +1149,20 @@ func (c *ctxt7) aclass(a *obj.Addr) int { ...@@ -1149,10 +1149,20 @@ func (c *ctxt7) aclass(a *obj.Addr) int {
return C_GOTADDR return C_GOTADDR
case obj.NAME_AUTO: case obj.NAME_AUTO:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-SP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset c.instoffset = int64(c.autosize) + a.Offset
return autoclass(c.instoffset) return autoclass(c.instoffset)
case obj.NAME_PARAM: case obj.NAME_PARAM:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-FP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset + 8 c.instoffset = int64(c.autosize) + a.Offset + 8
return autoclass(c.instoffset) return autoclass(c.instoffset)
...@@ -1228,10 +1238,20 @@ func (c *ctxt7) aclass(a *obj.Addr) int { ...@@ -1228,10 +1238,20 @@ func (c *ctxt7) aclass(a *obj.Addr) int {
return C_VCONADDR return C_VCONADDR
case obj.NAME_AUTO: case obj.NAME_AUTO:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-SP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset c.instoffset = int64(c.autosize) + a.Offset
goto aconsize goto aconsize
case obj.NAME_PARAM: case obj.NAME_PARAM:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-FP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset + 8 c.instoffset = int64(c.autosize) + a.Offset + 8
goto aconsize goto aconsize
} }
......
...@@ -556,6 +556,11 @@ func (c *ctxt0) aclass(a *obj.Addr) int { ...@@ -556,6 +556,11 @@ func (c *ctxt0) aclass(a *obj.Addr) int {
return C_LEXT return C_LEXT
case obj.NAME_AUTO: case obj.NAME_AUTO:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-SP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset c.instoffset = int64(c.autosize) + a.Offset
if c.instoffset >= -BIG && c.instoffset < BIG { if c.instoffset >= -BIG && c.instoffset < BIG {
return C_SAUTO return C_SAUTO
...@@ -563,6 +568,11 @@ func (c *ctxt0) aclass(a *obj.Addr) int { ...@@ -563,6 +568,11 @@ func (c *ctxt0) aclass(a *obj.Addr) int {
return C_LAUTO return C_LAUTO
case obj.NAME_PARAM: case obj.NAME_PARAM:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-FP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize() c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize()
if c.instoffset >= -BIG && c.instoffset < BIG { if c.instoffset >= -BIG && c.instoffset < BIG {
return C_SAUTO return C_SAUTO
...@@ -616,6 +626,11 @@ func (c *ctxt0) aclass(a *obj.Addr) int { ...@@ -616,6 +626,11 @@ func (c *ctxt0) aclass(a *obj.Addr) int {
return C_LECON return C_LECON
case obj.NAME_AUTO: case obj.NAME_AUTO:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-SP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset c.instoffset = int64(c.autosize) + a.Offset
if c.instoffset >= -BIG && c.instoffset < BIG { if c.instoffset >= -BIG && c.instoffset < BIG {
return C_SACON return C_SACON
...@@ -623,6 +638,11 @@ func (c *ctxt0) aclass(a *obj.Addr) int { ...@@ -623,6 +638,11 @@ func (c *ctxt0) aclass(a *obj.Addr) int {
return C_LACON return C_LACON
case obj.NAME_PARAM: case obj.NAME_PARAM:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-FP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize() c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize()
if c.instoffset >= -BIG && c.instoffset < BIG { if c.instoffset >= -BIG && c.instoffset < BIG {
return C_SACON return C_SACON
......
...@@ -758,6 +758,11 @@ func (c *ctxt9) aclass(a *obj.Addr) int { ...@@ -758,6 +758,11 @@ func (c *ctxt9) aclass(a *obj.Addr) int {
return C_GOTADDR return C_GOTADDR
case obj.NAME_AUTO: case obj.NAME_AUTO:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-SP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset c.instoffset = int64(c.autosize) + a.Offset
if c.instoffset >= -BIG && c.instoffset < BIG { if c.instoffset >= -BIG && c.instoffset < BIG {
return C_SAUTO return C_SAUTO
...@@ -765,6 +770,11 @@ func (c *ctxt9) aclass(a *obj.Addr) int { ...@@ -765,6 +770,11 @@ func (c *ctxt9) aclass(a *obj.Addr) int {
return C_LAUTO return C_LAUTO
case obj.NAME_PARAM: case obj.NAME_PARAM:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-FP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize() c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize()
if c.instoffset >= -BIG && c.instoffset < BIG { if c.instoffset >= -BIG && c.instoffset < BIG {
return C_SAUTO return C_SAUTO
...@@ -817,6 +827,11 @@ func (c *ctxt9) aclass(a *obj.Addr) int { ...@@ -817,6 +827,11 @@ func (c *ctxt9) aclass(a *obj.Addr) int {
return C_LCON return C_LCON
case obj.NAME_AUTO: case obj.NAME_AUTO:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-SP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset c.instoffset = int64(c.autosize) + a.Offset
if c.instoffset >= -BIG && c.instoffset < BIG { if c.instoffset >= -BIG && c.instoffset < BIG {
return C_SACON return C_SACON
...@@ -824,6 +839,11 @@ func (c *ctxt9) aclass(a *obj.Addr) int { ...@@ -824,6 +839,11 @@ func (c *ctxt9) aclass(a *obj.Addr) int {
return C_LACON return C_LACON
case obj.NAME_PARAM: case obj.NAME_PARAM:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-FP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize() c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize()
if c.instoffset >= -BIG && c.instoffset < BIG { if c.instoffset >= -BIG && c.instoffset < BIG {
return C_SACON return C_SACON
......
...@@ -505,6 +505,11 @@ func (c *ctxtz) aclass(a *obj.Addr) int { ...@@ -505,6 +505,11 @@ func (c *ctxtz) aclass(a *obj.Addr) int {
return C_GOTADDR return C_GOTADDR
case obj.NAME_AUTO: case obj.NAME_AUTO:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-SP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset c.instoffset = int64(c.autosize) + a.Offset
if c.instoffset >= -BIG && c.instoffset < BIG { if c.instoffset >= -BIG && c.instoffset < BIG {
return C_SAUTO return C_SAUTO
...@@ -512,6 +517,11 @@ func (c *ctxtz) aclass(a *obj.Addr) int { ...@@ -512,6 +517,11 @@ func (c *ctxtz) aclass(a *obj.Addr) int {
return C_LAUTO return C_LAUTO
case obj.NAME_PARAM: case obj.NAME_PARAM:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-FP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize() c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize()
if c.instoffset >= -BIG && c.instoffset < BIG { if c.instoffset >= -BIG && c.instoffset < BIG {
return C_SAUTO return C_SAUTO
...@@ -567,6 +577,11 @@ func (c *ctxtz) aclass(a *obj.Addr) int { ...@@ -567,6 +577,11 @@ func (c *ctxtz) aclass(a *obj.Addr) int {
return C_SYMADDR return C_SYMADDR
case obj.NAME_AUTO: case obj.NAME_AUTO:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-SP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset c.instoffset = int64(c.autosize) + a.Offset
if c.instoffset >= -BIG && c.instoffset < BIG { if c.instoffset >= -BIG && c.instoffset < BIG {
return C_SACON return C_SACON
...@@ -574,6 +589,11 @@ func (c *ctxtz) aclass(a *obj.Addr) int { ...@@ -574,6 +589,11 @@ func (c *ctxtz) aclass(a *obj.Addr) int {
return C_LACON return C_LACON
case obj.NAME_PARAM: case obj.NAME_PARAM:
if a.Reg == REGSP {
// unset base register for better printing, since
// a.Offset is still relative to pseudo-FP.
a.Reg = obj.REG_NONE
}
c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize() c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize()
if c.instoffset >= -BIG && c.instoffset < BIG { if c.instoffset >= -BIG && c.instoffset < BIG {
return C_SACON return C_SACON
......
...@@ -349,8 +349,9 @@ func doPackage(directory string, names []string, basePkg *Package) *Package { ...@@ -349,8 +349,9 @@ func doPackage(directory string, names []string, basePkg *Package) *Package {
pkg.files = files pkg.files = files
// Type check the package. // Type check the package.
err := pkg.check(fs, astFiles) err := pkg.check(fs, astFiles)
if err != nil && *verbose { if err != nil {
warnf("%s", err) // Note that we only report this error when *verbose.
Println(err)
} }
// Check. // Check.
......
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Used by TestVetVerbose to test that vet -v doesn't fail because it
// can't find "C".
package testdata
import "C"
func F() {
}
...@@ -205,3 +205,15 @@ func TestTags(t *testing.T) { ...@@ -205,3 +205,15 @@ func TestTags(t *testing.T) {
}) })
} }
} }
// Issue #21188.
func TestVetVerbose(t *testing.T) {
t.Parallel()
Build(t)
cmd := exec.Command("./"+binary, "-v", "-all", "testdata/cgo/cgo3.go")
out, err := cmd.CombinedOutput()
if err != nil {
t.Logf("%s", out)
t.Error(err)
}
}
...@@ -68,3 +68,94 @@ func ExampleByteOrder_get() { ...@@ -68,3 +68,94 @@ func ExampleByteOrder_get() {
// Output: // Output:
// 0x03e8 0x07d0 // 0x03e8 0x07d0
} }
func ExamplePutUvarint() {
buf := make([]byte, binary.MaxVarintLen64)
for _, x := range []uint64{1, 2, 127, 128, 255, 256} {
n := binary.PutUvarint(buf, x)
fmt.Printf("%x\n", buf[:n])
}
// Output:
// 01
// 02
// 7f
// 8001
// ff01
// 8002
}
func ExamplePutVarint() {
buf := make([]byte, binary.MaxVarintLen64)
for _, x := range []int64{-65, -64, -2, -1, 0, 1, 2, 63, 64} {
n := binary.PutVarint(buf, x)
fmt.Printf("%x\n", buf[:n])
}
// Output:
// 8101
// 7f
// 03
// 01
// 00
// 02
// 04
// 7e
// 8001
}
func ExampleUvarint() {
inputs := [][]byte{
[]byte{0x01},
[]byte{0x02},
[]byte{0x7f},
[]byte{0x80, 0x01},
[]byte{0xff, 0x01},
[]byte{0x80, 0x02},
}
for _, b := range inputs {
x, n := binary.Uvarint(b)
if n != len(b) {
fmt.Println("Uvarint did not consume all of in")
}
fmt.Println(x)
}
// Output:
// 1
// 2
// 127
// 128
// 255
// 256
}
func ExampleVarint() {
inputs := [][]byte{
[]byte{0x81, 0x01},
[]byte{0x7f},
[]byte{0x03},
[]byte{0x01},
[]byte{0x00},
[]byte{0x02},
[]byte{0x04},
[]byte{0x7e},
[]byte{0x80, 0x01},
}
for _, b := range inputs {
x, n := binary.Varint(b)
if n != len(b) {
fmt.Println("Varint did not consume all of in")
}
fmt.Println(x)
}
// Output:
// -65
// -64
// -2
// -1
// 0
// 1
// 2
// 63
// 64
}
...@@ -969,14 +969,14 @@ func (*FuncDecl) declNode() {} ...@@ -969,14 +969,14 @@ func (*FuncDecl) declNode() {}
// //
// For correct printing of source code containing comments (using packages // For correct printing of source code containing comments (using packages
// go/format and go/printer), special care must be taken to update comments // go/format and go/printer), special care must be taken to update comments
// when a File's syntax tree is modified: For printing, comments are inter- // when a File's syntax tree is modified: For printing, comments are interspersed
// spersed between tokens based on their position. If syntax tree nodes are // between tokens based on their position. If syntax tree nodes are
// removed or moved, relevant comments in their vicinity must also be removed // removed or moved, relevant comments in their vicinity must also be removed
// (from the File.Comments list) or moved accordingly (by updating their // (from the File.Comments list) or moved accordingly (by updating their
// positions). A CommentMap may be used to facilitate some of these operations. // positions). A CommentMap may be used to facilitate some of these operations.
// //
// Whether and how a comment is associated with a node depends on the inter- // Whether and how a comment is associated with a node depends on the
// pretation of the syntax tree by the manipulating program: Except for Doc // interpretation of the syntax tree by the manipulating program: Except for Doc
// and Comment comments directly associated with nodes, the remaining comments // and Comment comments directly associated with nodes, the remaining comments
// are "free-floating" (see also issues #18593, #20744). // are "free-floating" (see also issues #18593, #20744).
// //
......
...@@ -1707,8 +1707,8 @@ func (p *parser) parseSimpleStmt(mode int) (ast.Stmt, bool) { ...@@ -1707,8 +1707,8 @@ func (p *parser) parseSimpleStmt(mode int) (ast.Stmt, bool) {
} }
// The label declaration typically starts at x[0].Pos(), but the label // The label declaration typically starts at x[0].Pos(), but the label
// declaration may be erroneous due to a token after that position (and // declaration may be erroneous due to a token after that position (and
// before the ':'). If SpuriousErrors is not set, the (only) error re- // before the ':'). If SpuriousErrors is not set, the (only) error
// ported for the line is the illegal label error instead of the token // reported for the line is the illegal label error instead of the token
// before the ':' that caused the problem. Thus, use the (latest) colon // before the ':' that caused the problem. Thus, use the (latest) colon
// position for error reporting. // position for error reporting.
p.error(colon, "illegal label declaration") p.error(colon, "illegal label declaration")
......
...@@ -154,6 +154,10 @@ func (s *ioSrv) ProcessRemoteIO() { ...@@ -154,6 +154,10 @@ func (s *ioSrv) ProcessRemoteIO() {
// is available. Alternatively, it passes the request onto // is available. Alternatively, it passes the request onto
// runtime netpoll and waits for completion or cancels request. // runtime netpoll and waits for completion or cancels request.
func (s *ioSrv) ExecIO(o *operation, submit func(o *operation) error) (int, error) { func (s *ioSrv) ExecIO(o *operation, submit func(o *operation) error) (int, error) {
if o.fd.pd.runtimeCtx == 0 {
return 0, errors.New("internal error: polling on unsupported descriptor type")
}
if !canCancelIO { if !canCancelIO {
onceStartServer.Do(startServer) onceStartServer.Do(startServer)
} }
...@@ -315,8 +319,21 @@ func (fd *FD) Init(net string) (string, error) { ...@@ -315,8 +319,21 @@ func (fd *FD) Init(net string) (string, error) {
return "", errors.New("internal error: unknown network type " + net) return "", errors.New("internal error: unknown network type " + net)
} }
if err := fd.pd.init(fd); err != nil { if !fd.isFile && !fd.isConsole && !fd.isDir {
return "", err // Only call init for a network socket.
// This means that we don't add files to the runtime poller.
// Adding files to the runtime poller can confuse matters
// if the user is doing their own overlapped I/O.
// See issue #21172.
//
// In general the code below avoids calling the ExecIO
// method for non-network sockets. If some method does
// somehow call ExecIO, then ExecIO, and therefore the
// calling method, will return an error, because
// fd.pd.runtimeCtx will be 0.
if err := fd.pd.init(fd); err != nil {
return "", err
}
} }
if hasLoadSetFileCompletionNotificationModes { if hasLoadSetFileCompletionNotificationModes {
// We do not use events, so we can skip them always. // We do not use events, so we can skip them always.
......
...@@ -40,8 +40,8 @@ func (z *Rat) Scan(s fmt.ScanState, ch rune) error { ...@@ -40,8 +40,8 @@ func (z *Rat) Scan(s fmt.ScanState, ch rune) error {
// SetString sets z to the value of s and returns z and a boolean indicating // SetString sets z to the value of s and returns z and a boolean indicating
// success. s can be given as a fraction "a/b" or as a floating-point number // success. s can be given as a fraction "a/b" or as a floating-point number
// optionally followed by an exponent. The entire string (not just a prefix) // optionally followed by an exponent. The entire string (not just a prefix)
// must be valid for success. If the operation failed, the value of z is un- // must be valid for success. If the operation failed, the value of z is
// defined but the returned value is nil. // undefined but the returned value is nil.
func (z *Rat) SetString(s string) (*Rat, bool) { func (z *Rat) SetString(s string) (*Rat, bool) {
if len(s) == 0 { if len(s) == 0 {
return nil, false return nil, false
......
...@@ -36,3 +36,43 @@ func ExampleLeadingZeros64() { ...@@ -36,3 +36,43 @@ func ExampleLeadingZeros64() {
// 64 // 64
// 63 // 63
} }
func ExampleOnesCount() {
fmt.Printf("%b\n", 14)
fmt.Println(bits.OnesCount(14))
// Output:
// 1110
// 3
}
func ExampleOnesCount8() {
fmt.Printf("%b\n", 14)
fmt.Println(bits.OnesCount8(14))
// Output:
// 1110
// 3
}
func ExampleOnesCount16() {
fmt.Printf("%b\n", 14)
fmt.Println(bits.OnesCount16(14))
// Output:
// 1110
// 3
}
func ExampleOnesCount32() {
fmt.Printf("%b\n", 14)
fmt.Println(bits.OnesCount32(14))
// Output:
// 1110
// 3
}
func ExampleOnesCount64() {
fmt.Printf("%b\n", 14)
fmt.Println(bits.OnesCount64(14))
// Output:
// 1110
// 3
}
...@@ -4,8 +4,6 @@ ...@@ -4,8 +4,6 @@
// Package plugin implements loading and symbol resolution of Go plugins. // Package plugin implements loading and symbol resolution of Go plugins.
// //
// Currently plugins only work on Linux.
//
// A plugin is a Go main package with exported functions and variables that // A plugin is a Go main package with exported functions and variables that
// has been built with: // has been built with:
// //
...@@ -14,6 +12,9 @@ ...@@ -14,6 +12,9 @@
// When a plugin is first opened, the init functions of all packages not // When a plugin is first opened, the init functions of all packages not
// already part of the program are called. The main function is not run. // already part of the program are called. The main function is not run.
// A plugin is only initialized once, and cannot be closed. // A plugin is only initialized once, and cannot be closed.
//
// The plugin support is currently incomplete, only supports Linux,
// and has known bugs. Please report any issues.
package plugin package plugin
// Plugin is a loaded Go plugin. // Plugin is a loaded Go plugin.
......
...@@ -178,6 +178,12 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool, depth int) bool { ...@@ -178,6 +178,12 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool, depth int) bool {
// DeepEqual has been defined so that the same short-cut applies // DeepEqual has been defined so that the same short-cut applies
// to slices and maps: if x and y are the same slice or the same map, // to slices and maps: if x and y are the same slice or the same map,
// they are deeply equal regardless of content. // they are deeply equal regardless of content.
//
// As DeepEqual traverses the data values it may find a cycle. The
// second and subsequent times that DeepEqual compares two pointer
// values that have been compared before, it treats the values as
// equal rather than examining the values to which they point.
// This ensures that DeepEqual terminates.
func DeepEqual(x, y interface{}) bool { func DeepEqual(x, y interface{}) bool {
if x == nil || y == nil { if x == nil || y == nil {
return x == y return x == y
......
...@@ -163,6 +163,15 @@ func (p *cpuProfile) addExtra() { ...@@ -163,6 +163,15 @@ func (p *cpuProfile) addExtra() {
} }
} }
func (p *cpuProfile) addLostAtomic64(count uint64) {
hdr := [1]uint64{count}
lostStk := [2]uintptr{
funcPC(_LostSIGPROFDuringAtomic64) + sys.PCQuantum,
funcPC(_System) + sys.PCQuantum,
}
cpuprof.log.write(nil, 0, hdr[:], lostStk[:])
}
// CPUProfile panics. // CPUProfile panics.
// It formerly provided raw access to chunks of // It formerly provided raw access to chunks of
// a pprof-format profile generated by the runtime. // a pprof-format profile generated by the runtime.
......
...@@ -495,7 +495,7 @@ again: ...@@ -495,7 +495,7 @@ again:
} }
// store new key/value at insert position // store new key/value at insert position
*((*uint32)(insertk)) = key typedmemmove(t.key, insertk, unsafe.Pointer(&key))
*inserti = top *inserti = top
h.count++ h.count++
...@@ -583,7 +583,7 @@ again: ...@@ -583,7 +583,7 @@ again:
} }
// store new key/value at insert position // store new key/value at insert position
*((*uint64)(insertk)) = key typedmemmove(t.key, insertk, unsafe.Pointer(&key))
*inserti = top *inserti = top
h.count++ h.count++
...@@ -723,7 +723,7 @@ func mapdelete_fast32(t *maptype, h *hmap, key uint32) { ...@@ -723,7 +723,7 @@ func mapdelete_fast32(t *maptype, h *hmap, key uint32) {
if key != *k { if key != *k {
continue continue
} }
*k = 0 typedmemclr(t.key, unsafe.Pointer(k))
v := unsafe.Pointer(uintptr(unsafe.Pointer(b)) + dataOffset + bucketCnt*4 + i*uintptr(t.valuesize)) v := unsafe.Pointer(uintptr(unsafe.Pointer(b)) + dataOffset + bucketCnt*4 + i*uintptr(t.valuesize))
typedmemclr(t.elem, v) typedmemclr(t.elem, v)
b.tophash[i] = empty b.tophash[i] = empty
...@@ -778,7 +778,7 @@ func mapdelete_fast64(t *maptype, h *hmap, key uint64) { ...@@ -778,7 +778,7 @@ func mapdelete_fast64(t *maptype, h *hmap, key uint64) {
if key != *k { if key != *k {
continue continue
} }
*k = 0 typedmemclr(t.key, unsafe.Pointer(k))
v := unsafe.Pointer(uintptr(unsafe.Pointer(b)) + dataOffset + bucketCnt*8 + i*uintptr(t.valuesize)) v := unsafe.Pointer(uintptr(unsafe.Pointer(b)) + dataOffset + bucketCnt*8 + i*uintptr(t.valuesize))
typedmemclr(t.elem, v) typedmemclr(t.elem, v)
b.tophash[i] = empty b.tophash[i] = empty
......
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"fmt" "fmt"
"internal/testenv" "internal/testenv"
"io" "io"
"io/ioutil"
"math/big" "math/big"
"os" "os"
"os/exec" "os/exec"
...@@ -20,6 +21,7 @@ import ( ...@@ -20,6 +21,7 @@ import (
"runtime/pprof/internal/profile" "runtime/pprof/internal/profile"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"testing" "testing"
"time" "time"
) )
...@@ -713,3 +715,32 @@ func TestCPUProfileLabel(t *testing.T) { ...@@ -713,3 +715,32 @@ func TestCPUProfileLabel(t *testing.T) {
}) })
}) })
} }
// Check that there is no deadlock when the program receives SIGPROF while in
// 64bit atomics' critical section. Used to happen on mips{,le}. See #20146.
func TestAtomicLoadStore64(t *testing.T) {
f, err := ioutil.TempFile("", "profatomic")
if err != nil {
t.Fatalf("TempFile: %v", err)
}
defer os.Remove(f.Name())
defer f.Close()
if err := StartCPUProfile(f); err != nil {
t.Fatal(err)
}
defer StopCPUProfile()
var flag uint64
done := make(chan bool, 1)
go func() {
for atomic.LoadUint64(&flag) == 0 {
runtime.Gosched()
}
done <- true
}()
time.Sleep(50 * time.Millisecond)
atomic.StoreUint64(&flag, 1)
<-done
}
...@@ -3232,10 +3232,14 @@ var prof struct { ...@@ -3232,10 +3232,14 @@ var prof struct {
hz int32 hz int32
} }
func _System() { _System() } func _System() { _System() }
func _ExternalCode() { _ExternalCode() } func _ExternalCode() { _ExternalCode() }
func _LostExternalCode() { _LostExternalCode() } func _LostExternalCode() { _LostExternalCode() }
func _GC() { _GC() } func _GC() { _GC() }
func _LostSIGPROFDuringAtomic64() { _LostSIGPROFDuringAtomic64() }
// Counts SIGPROFs received while in atomic64 critical section, on mips{,le}
var lostAtomic64Count uint64
// Called if we receive a SIGPROF signal. // Called if we receive a SIGPROF signal.
// Called by the signal handler, may run during STW. // Called by the signal handler, may run during STW.
...@@ -3245,6 +3249,21 @@ func sigprof(pc, sp, lr uintptr, gp *g, mp *m) { ...@@ -3245,6 +3249,21 @@ func sigprof(pc, sp, lr uintptr, gp *g, mp *m) {
return return
} }
// On mips{,le}, 64bit atomics are emulated with spinlocks, in
// runtime/internal/atomic. If SIGPROF arrives while the program is inside
// the critical section, it creates a deadlock (when writing the sample).
// As a workaround, create a counter of SIGPROFs while in critical section
// to store the count, and pass it to sigprof.add() later when SIGPROF is
// received from somewhere else (with _LostSIGPROFDuringAtomic64 as pc).
if GOARCH == "mips" || GOARCH == "mipsle" {
if f := findfunc(pc); f.valid() {
if hasprefix(funcname(f), "runtime/internal/atomic") {
lostAtomic64Count++
return
}
}
}
// Profiling runs concurrently with GC, so it must not allocate. // Profiling runs concurrently with GC, so it must not allocate.
// Set a trap in case the code does allocate. // Set a trap in case the code does allocate.
// Note that on windows, one thread takes profiles of all the // Note that on windows, one thread takes profiles of all the
...@@ -3371,6 +3390,10 @@ func sigprof(pc, sp, lr uintptr, gp *g, mp *m) { ...@@ -3371,6 +3390,10 @@ func sigprof(pc, sp, lr uintptr, gp *g, mp *m) {
} }
if prof.hz != 0 { if prof.hz != 0 {
if (GOARCH == "mips" || GOARCH == "mipsle") && lostAtomic64Count > 0 {
cpuprof.addLostAtomic64(lostAtomic64Count)
lostAtomic64Count = 0
}
cpuprof.add(gp, stk[:n]) cpuprof.add(gp, stk[:n])
} }
getg().m.mallocing-- getg().m.mallocing--
......
...@@ -409,6 +409,11 @@ var modulesSlice unsafe.Pointer // see activeModules ...@@ -409,6 +409,11 @@ var modulesSlice unsafe.Pointer // see activeModules
// //
// A module is active once its gcdatamask and gcbssmask have been // A module is active once its gcdatamask and gcbssmask have been
// assembled and it is usable by the GC. // assembled and it is usable by the GC.
//
// This is nosplit/nowritebarrier because it is called by the
// cgo pointer checking code.
//go:nosplit
//go:nowritebarrier
func activeModules() []*moduledata { func activeModules() []*moduledata {
p := (*[]*moduledata)(atomic.Loadp(unsafe.Pointer(&modulesSlice))) p := (*[]*moduledata)(atomic.Loadp(unsafe.Pointer(&modulesSlice)))
if p == nil { if p == nil {
......
...@@ -970,7 +970,7 @@ func listTests(matchString func(pat, str string) (bool, error), tests []Internal ...@@ -970,7 +970,7 @@ func listTests(matchString func(pat, str string) (bool, error), tests []Internal
} }
} }
for _, example := range examples { for _, example := range examples {
if ok, _ := matchString(*matchList, example.Name); ok && example.Output != "" { if ok, _ := matchString(*matchList, example.Name); ok {
fmt.Println(example.Name) fmt.Println(example.Name)
} }
} }
......
...@@ -14,6 +14,14 @@ func testZoneAbbr(t *testing.T) { ...@@ -14,6 +14,14 @@ func testZoneAbbr(t *testing.T) {
t1 := Now() t1 := Now()
// discard nsec // discard nsec
t1 = Date(t1.Year(), t1.Month(), t1.Day(), t1.Hour(), t1.Minute(), t1.Second(), 0, t1.Location()) t1 = Date(t1.Year(), t1.Month(), t1.Day(), t1.Hour(), t1.Minute(), t1.Second(), 0, t1.Location())
// Skip the test if we're in a timezone with no abbreviation.
// Format will fallback to the numeric abbreviation, and
// Parse(RFC1123, ..) will fail (see Issue 21183).
if tz := t1.Format("MST"); tz[0] == '-' || tz[0] == '+' {
t.Skip("No zone abbreviation")
}
t2, err := Parse(RFC1123, t1.Format(RFC1123)) t2, err := Parse(RFC1123, t1.Format(RFC1123))
if err != nil { if err != nil {
t.Fatalf("Parse failed: %v", err) t.Fatalf("Parse failed: %v", err)
......
The test directory contains tests of the Go tool chain and runtime.
It includes black box tests, regression tests, and error output tests.
They are run as part of all.bash.
To run just these tests, execute:
go run run.go
Standard library tests should be written as regular Go tests in the appropriate package.
The tool chain and runtime also have regular Go tests in their packages.
The main reasons to add a new test to this directory are:
* it is most naturally expressed using the test runner; or
* it is also applicable to `gccgo` and other Go tool chains.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment