mirror of
https://github.com/golang/go.git
synced 2025-05-05 23:53:05 +00:00
[dev.ssa] cmd/compile: default compile+test with SSA
Some tests disabled, some bifurcated into _ssa and not, with appropriate logging added to compiler. "tests/live.go" in particular needs attention. SSA-specific testing removed, since it's all SSA now. Added "-run_skips" option to tests/run.go to simplify checking whether a test still fails (or how it fails) on a skipped platform. The compiler now compiles with SSA by default. If you don't want SSA, specify GOSSAHASH=n (or N) as an environment variable. Function names ending in "_ssa" are always SSA-compiled. GOSSAFUNC=fname retains its "SSA for fname, log to ssa.html" GOSSAPKG=pkg only has an effect when GOSSAHASH=n GOSSAHASH=10101 etc retains its name-hash-matching behavior for purposes of debugging. See #13068 Change-Id: I8217bfeb34173533eaeb391b5f6935483c7d6b43 Reviewed-on: https://go-review.googlesource.com/16299 Reviewed-by: Keith Randall <khr@golang.org> Run-TryBot: David Chase <drchase@google.com>
This commit is contained in:
parent
cdc36252fe
commit
729abfa35c
@ -34,10 +34,10 @@ func buildssa(fn *Node) (ssafn *ssa.Func, usessa bool) {
|
|||||||
// 1. IF GOSSAFUNC == current function name THEN
|
// 1. IF GOSSAFUNC == current function name THEN
|
||||||
// compile this function with SSA and log output to ssa.html
|
// compile this function with SSA and log output to ssa.html
|
||||||
|
|
||||||
// 2. IF GOSSAHASH == "y" or "Y" THEN
|
// 2. IF GOSSAHASH == "" THEN
|
||||||
// compile this function (and everything else) with SSA
|
// compile this function (and everything else) with SSA
|
||||||
|
|
||||||
// 3. IF GOSSAHASH == "" THEN
|
// 3. IF GOSSAHASH == "n" or "N"
|
||||||
// IF GOSSAPKG == current package name THEN
|
// IF GOSSAPKG == current package name THEN
|
||||||
// compile this function (and everything in this package) with SSA
|
// compile this function (and everything in this package) with SSA
|
||||||
// ELSE
|
// ELSE
|
||||||
@ -49,9 +49,10 @@ func buildssa(fn *Node) (ssafn *ssa.Func, usessa bool) {
|
|||||||
// ELSE
|
// ELSE
|
||||||
// compile this function with the old back end.
|
// compile this function with the old back end.
|
||||||
|
|
||||||
// Plan is for 3 to be remove, and the 2) dependence on GOSSAHASH changes
|
// Plan is for 3 to be removed when the tests are revised.
|
||||||
// from "y"/"Y" to empty -- then SSA is default, and is disabled by setting
|
// SSA is now default, and is disabled by setting
|
||||||
// GOSSAHASH to a value that is neither 0 nor 1 (e.g., "N" or "X")
|
// GOSSAHASH to n or N, or selectively with strings of
|
||||||
|
// 0 and 1.
|
||||||
|
|
||||||
if usessa {
|
if usessa {
|
||||||
fmt.Println("generating SSA for", name)
|
fmt.Println("generating SSA for", name)
|
||||||
@ -183,10 +184,11 @@ func buildssa(fn *Node) (ssafn *ssa.Func, usessa bool) {
|
|||||||
// Main call to ssa package to compile function
|
// Main call to ssa package to compile function
|
||||||
ssa.Compile(s.f)
|
ssa.Compile(s.f)
|
||||||
|
|
||||||
if usessa || gossahash == "y" || gossahash == "Y" {
|
// gossahash = "y" is historical/symmetric-with-"n" -- i.e., not really needed.
|
||||||
|
if usessa || gossahash == "" || gossahash == "y" || gossahash == "Y" {
|
||||||
return s.f, true
|
return s.f, true
|
||||||
}
|
}
|
||||||
if gossahash == "" {
|
if gossahash == "n" || gossahash == "N" {
|
||||||
if localpkg.Name != os.Getenv("GOSSAPKG") {
|
if localpkg.Name != os.Getenv("GOSSAPKG") {
|
||||||
return s.f, false
|
return s.f, false
|
||||||
}
|
}
|
||||||
@ -298,9 +300,11 @@ func (s *state) label(sym *Sym) *ssaLabel {
|
|||||||
return lab
|
return lab
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) Logf(msg string, args ...interface{}) { s.config.Logf(msg, args...) }
|
func (s *state) Logf(msg string, args ...interface{}) { s.config.Logf(msg, args...) }
|
||||||
func (s *state) Fatalf(msg string, args ...interface{}) { s.config.Fatalf(msg, args...) }
|
func (s *state) Fatalf(msg string, args ...interface{}) { s.config.Fatalf(msg, args...) }
|
||||||
func (s *state) Unimplementedf(msg string, args ...interface{}) { s.config.Unimplementedf(msg, args...) }
|
func (s *state) Unimplementedf(msg string, args ...interface{}) { s.config.Unimplementedf(msg, args...) }
|
||||||
|
func (s *state) Warnl(line int, msg string, args ...interface{}) { s.config.Warnl(line, msg, args...) }
|
||||||
|
func (s *state) Debug_checknil() bool { return s.config.Debug_checknil() }
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// dummy node for the memory variable
|
// dummy node for the memory variable
|
||||||
@ -1997,7 +2001,7 @@ func (s *state) expr(n *Node) *ssa.Value {
|
|||||||
if haspointers(et) {
|
if haspointers(et) {
|
||||||
// TODO: just one write barrier call for all of these writes?
|
// TODO: just one write barrier call for all of these writes?
|
||||||
// TODO: maybe just one writeBarrierEnabled check?
|
// TODO: maybe just one writeBarrierEnabled check?
|
||||||
s.insertWB(et, addr)
|
s.insertWB(et, addr, n.Lineno)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2044,7 +2048,7 @@ func (s *state) assign(left *Node, right *ssa.Value, wb bool) {
|
|||||||
}
|
}
|
||||||
s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, t.Size(), addr, right, s.mem())
|
s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, t.Size(), addr, right, s.mem())
|
||||||
if wb {
|
if wb {
|
||||||
s.insertWB(left.Type, addr)
|
s.insertWB(left.Type, addr, left.Lineno)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2566,7 +2570,7 @@ func (s *state) rtcall(fn *Node, returns bool, results []*Type, args ...*ssa.Val
|
|||||||
// been stored at location p. Tell the runtime about this write.
|
// been stored at location p. Tell the runtime about this write.
|
||||||
// Note: there must be no GC suspension points between the write and
|
// Note: there must be no GC suspension points between the write and
|
||||||
// the call that this function inserts.
|
// the call that this function inserts.
|
||||||
func (s *state) insertWB(t *Type, p *ssa.Value) {
|
func (s *state) insertWB(t *Type, p *ssa.Value, line int32) {
|
||||||
// if writeBarrierEnabled {
|
// if writeBarrierEnabled {
|
||||||
// typedmemmove_nostore(&t, p)
|
// typedmemmove_nostore(&t, p)
|
||||||
// }
|
// }
|
||||||
@ -2586,6 +2590,10 @@ func (s *state) insertWB(t *Type, p *ssa.Value) {
|
|||||||
taddr := s.newValue1A(ssa.OpAddr, Types[TUINTPTR], &ssa.ExternSymbol{Types[TUINTPTR], typenamesym(t)}, s.sb)
|
taddr := s.newValue1A(ssa.OpAddr, Types[TUINTPTR], &ssa.ExternSymbol{Types[TUINTPTR], typenamesym(t)}, s.sb)
|
||||||
s.rtcall(typedmemmove_nostore, true, nil, taddr, p)
|
s.rtcall(typedmemmove_nostore, true, nil, taddr, p)
|
||||||
|
|
||||||
|
if Debug_wb > 0 {
|
||||||
|
Warnl(int(line), "write barrier")
|
||||||
|
}
|
||||||
|
|
||||||
b.AddEdgeTo(s.curBlock)
|
b.AddEdgeTo(s.curBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2985,6 +2993,10 @@ func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) {
|
|||||||
Fatalf("dottype needs a direct iface type %s", n.Type)
|
Fatalf("dottype needs a direct iface type %s", n.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Debug_typeassert > 0 {
|
||||||
|
Warnl(int(n.Lineno), "type assertion inlined")
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: If we have a nonempty interface and its itab field is nil,
|
// TODO: If we have a nonempty interface and its itab field is nil,
|
||||||
// then this test is redundant and ifaceType should just branch directly to bFail.
|
// then this test is redundant and ifaceType should just branch directly to bFail.
|
||||||
cond := s.newValue2(ssa.OpEqPtr, Types[TBOOL], typ, target)
|
cond := s.newValue2(ssa.OpEqPtr, Types[TBOOL], typ, target)
|
||||||
@ -4523,6 +4535,16 @@ func (e *ssaExport) Unimplementedf(msg string, args ...interface{}) {
|
|||||||
e.unimplemented = true
|
e.unimplemented = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Warnl reports a "warning", which is usually flag-triggered
|
||||||
|
// logging output for the benefit of tests.
|
||||||
|
func (e *ssaExport) Warnl(line int, fmt_ string, args ...interface{}) {
|
||||||
|
Warnl(line, fmt_, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ssaExport) Debug_checknil() bool {
|
||||||
|
return Debug_checknil != 0
|
||||||
|
}
|
||||||
|
|
||||||
func (n *Node) Typ() ssa.Type {
|
func (n *Node) Typ() ssa.Type {
|
||||||
return n.Type
|
return n.Type
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,12 @@ type Logger interface {
|
|||||||
// Unimplemented reports that the function cannot be compiled.
|
// Unimplemented reports that the function cannot be compiled.
|
||||||
// It will be removed once SSA work is complete.
|
// It will be removed once SSA work is complete.
|
||||||
Unimplementedf(msg string, args ...interface{})
|
Unimplementedf(msg string, args ...interface{})
|
||||||
|
|
||||||
|
// Warnl writes compiler messages in the form expected by "errorcheck" tests
|
||||||
|
Warnl(line int, fmt_ string, args ...interface{})
|
||||||
|
|
||||||
|
// Fowards the Debug_checknil flag from gc
|
||||||
|
Debug_checknil() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Frontend interface {
|
type Frontend interface {
|
||||||
@ -100,9 +106,11 @@ func (c *Config) NewFunc() *Func {
|
|||||||
return &Func{Config: c, NamedValues: map[GCNode][]*Value{}}
|
return &Func{Config: c, NamedValues: map[GCNode][]*Value{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Logf(msg string, args ...interface{}) { c.fe.Logf(msg, args...) }
|
func (c *Config) Logf(msg string, args ...interface{}) { c.fe.Logf(msg, args...) }
|
||||||
func (c *Config) Fatalf(msg string, args ...interface{}) { c.fe.Fatalf(msg, args...) }
|
func (c *Config) Fatalf(msg string, args ...interface{}) { c.fe.Fatalf(msg, args...) }
|
||||||
func (c *Config) Unimplementedf(msg string, args ...interface{}) { c.fe.Unimplementedf(msg, args...) }
|
func (c *Config) Unimplementedf(msg string, args ...interface{}) { c.fe.Unimplementedf(msg, args...) }
|
||||||
|
func (c *Config) Warnl(line int, msg string, args ...interface{}) { c.fe.Warnl(line, msg, args...) }
|
||||||
|
func (c *Config) Debug_checknil() bool { return c.fe.Debug_checknil() }
|
||||||
|
|
||||||
// TODO(khr): do we really need a separate Config, or can we just
|
// TODO(khr): do we really need a separate Config, or can we just
|
||||||
// store all its fields inside a Func?
|
// store all its fields inside a Func?
|
||||||
|
@ -32,9 +32,11 @@ func (DummyFrontend) Auto(t Type) GCNode {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d DummyFrontend) Logf(msg string, args ...interface{}) { d.t.Logf(msg, args...) }
|
func (d DummyFrontend) Logf(msg string, args ...interface{}) { d.t.Logf(msg, args...) }
|
||||||
func (d DummyFrontend) Fatalf(msg string, args ...interface{}) { d.t.Fatalf(msg, args...) }
|
func (d DummyFrontend) Fatalf(msg string, args ...interface{}) { d.t.Fatalf(msg, args...) }
|
||||||
func (d DummyFrontend) Unimplementedf(msg string, args ...interface{}) { d.t.Fatalf(msg, args...) }
|
func (d DummyFrontend) Unimplementedf(msg string, args ...interface{}) { d.t.Fatalf(msg, args...) }
|
||||||
|
func (d DummyFrontend) Warnl(line int, msg string, args ...interface{}) { d.t.Logf(msg, args...) }
|
||||||
|
func (d DummyFrontend) Debug_checknil() bool { return false }
|
||||||
|
|
||||||
func (d DummyFrontend) TypeBool() Type { return TypeBool }
|
func (d DummyFrontend) TypeBool() Type { return TypeBool }
|
||||||
func (d DummyFrontend) TypeInt8() Type { return TypeInt8 }
|
func (d DummyFrontend) TypeInt8() Type { return TypeInt8 }
|
||||||
|
@ -88,6 +88,13 @@ func nilcheckelim(f *Func) {
|
|||||||
// Eliminate the nil check.
|
// Eliminate the nil check.
|
||||||
// The deadcode pass will remove vestigial values,
|
// The deadcode pass will remove vestigial values,
|
||||||
// and the fuse pass will join this block with its successor.
|
// and the fuse pass will join this block with its successor.
|
||||||
|
|
||||||
|
// Logging in the style of the former compiler -- and omit line 1,
|
||||||
|
// which is usually in generated code.
|
||||||
|
if f.Config.Debug_checknil() && int(node.block.Control.Line) > 1 {
|
||||||
|
f.Config.Warnl(int(node.block.Control.Line), "removed nil check")
|
||||||
|
}
|
||||||
|
|
||||||
switch node.block.Kind {
|
switch node.block.Kind {
|
||||||
case BlockIf:
|
case BlockIf:
|
||||||
node.block.Kind = BlockFirst
|
node.block.Kind = BlockFirst
|
||||||
|
34
src/cmd/dist/test.go
vendored
34
src/cmd/dist/test.go
vendored
@ -13,7 +13,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -276,31 +275,6 @@ func (t *tester) registerStdTest(pkg string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove when SSA codegen is used by default.
|
|
||||||
func (t *tester) registerSSATest(pkg string) {
|
|
||||||
t.tests = append(t.tests, distTest{
|
|
||||||
name: "go_test_ssa:" + pkg,
|
|
||||||
heading: "Testing packages with SSA codegen.",
|
|
||||||
fn: func() error {
|
|
||||||
args := []string{
|
|
||||||
"test",
|
|
||||||
"-short",
|
|
||||||
t.timeout(180 * 3), // SSA generates slower code right now
|
|
||||||
"-gcflags=" + os.Getenv("GO_GCFLAGS"),
|
|
||||||
}
|
|
||||||
if t.race {
|
|
||||||
args = append(args, "-race")
|
|
||||||
}
|
|
||||||
args = append(args, pkg)
|
|
||||||
cmd := exec.Command("go", args...)
|
|
||||||
cmd.Env = mergeEnvLists([]string{"GOSSAPKG=" + path.Base(pkg)}, os.Environ())
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tester) registerRaceBenchTest(pkg string) {
|
func (t *tester) registerRaceBenchTest(pkg string) {
|
||||||
testName := "go_test_bench:" + pkg
|
testName := "go_test_bench:" + pkg
|
||||||
if t.runRx == nil || t.runRx.MatchString(testName) {
|
if t.runRx == nil || t.runRx.MatchString(testName) {
|
||||||
@ -344,9 +318,6 @@ func (t *tester) registerTests() {
|
|||||||
if strings.HasPrefix(name, "go_test_bench:") {
|
if strings.HasPrefix(name, "go_test_bench:") {
|
||||||
t.registerRaceBenchTest(strings.TrimPrefix(name, "go_test_bench:"))
|
t.registerRaceBenchTest(strings.TrimPrefix(name, "go_test_bench:"))
|
||||||
}
|
}
|
||||||
if t.goarch == "amd64" && strings.HasPrefix(name, "go_test_ssa:") {
|
|
||||||
t.registerSSATest(strings.TrimPrefix(name, "go_test_ssa:"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use a format string to only list packages and commands that have tests.
|
// Use a format string to only list packages and commands that have tests.
|
||||||
@ -363,11 +334,6 @@ func (t *tester) registerTests() {
|
|||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
t.registerStdTest(pkg)
|
t.registerStdTest(pkg)
|
||||||
}
|
}
|
||||||
if t.goarch == "amd64" {
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
t.registerSSATest(pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.race {
|
if t.race {
|
||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
t.registerRaceBenchTest(pkg)
|
t.registerRaceBenchTest(pkg)
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// +build !amd64
|
||||||
// errorcheck -0 -l -live -wb=0
|
// errorcheck -0 -l -live -wb=0
|
||||||
|
|
||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// +build !amd64
|
||||||
// errorcheck -0 -live -wb=0
|
// errorcheck -0 -live -wb=0
|
||||||
|
|
||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// +build !amd64
|
||||||
// errorcheck -0 -N -d=nil
|
// errorcheck -0 -N -d=nil
|
||||||
|
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
@ -17,7 +18,7 @@ type Struct struct {
|
|||||||
type BigStruct struct {
|
type BigStruct struct {
|
||||||
X int
|
X int
|
||||||
Y float64
|
Y float64
|
||||||
A [1<<20]int
|
A [1 << 20]int
|
||||||
Z string
|
Z string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,86 +30,86 @@ type Empty1 struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
intp *int
|
intp *int
|
||||||
arrayp *[10]int
|
arrayp *[10]int
|
||||||
array0p *[0]int
|
array0p *[0]int
|
||||||
bigarrayp *[1<<26]int
|
bigarrayp *[1 << 26]int
|
||||||
structp *Struct
|
structp *Struct
|
||||||
bigstructp *BigStruct
|
bigstructp *BigStruct
|
||||||
emptyp *Empty
|
emptyp *Empty
|
||||||
empty1p *Empty1
|
empty1p *Empty1
|
||||||
)
|
)
|
||||||
|
|
||||||
func f1() {
|
func f1() {
|
||||||
_ = *intp // ERROR "nil check"
|
_ = *intp // ERROR "nil check"
|
||||||
_ = *arrayp // ERROR "nil check"
|
_ = *arrayp // ERROR "nil check"
|
||||||
_ = *array0p // ERROR "nil check"
|
_ = *array0p // ERROR "nil check"
|
||||||
_ = *array0p // ERROR "nil check"
|
_ = *array0p // ERROR "nil check"
|
||||||
_ = *intp // ERROR "nil check"
|
_ = *intp // ERROR "nil check"
|
||||||
_ = *arrayp // ERROR "nil check"
|
_ = *arrayp // ERROR "nil check"
|
||||||
_ = *structp // ERROR "nil check"
|
_ = *structp // ERROR "nil check"
|
||||||
_ = *emptyp // ERROR "nil check"
|
_ = *emptyp // ERROR "nil check"
|
||||||
_ = *arrayp // ERROR "nil check"
|
_ = *arrayp // ERROR "nil check"
|
||||||
}
|
}
|
||||||
|
|
||||||
func f2() {
|
func f2() {
|
||||||
var (
|
var (
|
||||||
intp *int
|
intp *int
|
||||||
arrayp *[10]int
|
arrayp *[10]int
|
||||||
array0p *[0]int
|
array0p *[0]int
|
||||||
bigarrayp *[1<<20]int
|
bigarrayp *[1 << 20]int
|
||||||
structp *Struct
|
structp *Struct
|
||||||
bigstructp *BigStruct
|
bigstructp *BigStruct
|
||||||
emptyp *Empty
|
emptyp *Empty
|
||||||
empty1p *Empty1
|
empty1p *Empty1
|
||||||
)
|
)
|
||||||
|
|
||||||
_ = *intp // ERROR "nil check"
|
_ = *intp // ERROR "nil check"
|
||||||
_ = *arrayp // ERROR "nil check"
|
_ = *arrayp // ERROR "nil check"
|
||||||
_ = *array0p // ERROR "nil check"
|
_ = *array0p // ERROR "nil check"
|
||||||
_ = *array0p // ERROR "nil check"
|
_ = *array0p // ERROR "nil check"
|
||||||
_ = *intp // ERROR "nil check"
|
_ = *intp // ERROR "nil check"
|
||||||
_ = *arrayp // ERROR "nil check"
|
_ = *arrayp // ERROR "nil check"
|
||||||
_ = *structp // ERROR "nil check"
|
_ = *structp // ERROR "nil check"
|
||||||
_ = *emptyp // ERROR "nil check"
|
_ = *emptyp // ERROR "nil check"
|
||||||
_ = *arrayp // ERROR "nil check"
|
_ = *arrayp // ERROR "nil check"
|
||||||
_ = *bigarrayp // ERROR "nil check"
|
_ = *bigarrayp // ERROR "nil check"
|
||||||
_ = *bigstructp // ERROR "nil check"
|
_ = *bigstructp // ERROR "nil check"
|
||||||
_ = *empty1p // ERROR "nil check"
|
_ = *empty1p // ERROR "nil check"
|
||||||
}
|
}
|
||||||
|
|
||||||
func fx10k() *[10000]int
|
func fx10k() *[10000]int
|
||||||
var b bool
|
|
||||||
|
|
||||||
|
var b bool
|
||||||
|
|
||||||
func f3(x *[10000]int) {
|
func f3(x *[10000]int) {
|
||||||
// Using a huge type and huge offsets so the compiler
|
// Using a huge type and huge offsets so the compiler
|
||||||
// does not expect the memory hardware to fault.
|
// does not expect the memory hardware to fault.
|
||||||
_ = x[9999] // ERROR "nil check"
|
_ = x[9999] // ERROR "nil check"
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if x[9999] != 0 { // ERROR "nil check"
|
if x[9999] != 0 { // ERROR "nil check"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
x = fx10k()
|
x = fx10k()
|
||||||
_ = x[9999] // ERROR "nil check"
|
_ = x[9999] // ERROR "nil check"
|
||||||
if b {
|
if b {
|
||||||
_ = x[9999] // ERROR "nil check"
|
_ = x[9999] // ERROR "nil check"
|
||||||
} else {
|
} else {
|
||||||
_ = x[9999] // ERROR "nil check"
|
_ = x[9999] // ERROR "nil check"
|
||||||
}
|
}
|
||||||
_ = x[9999] // ERROR "nil check"
|
_ = x[9999] // ERROR "nil check"
|
||||||
|
|
||||||
x = fx10k()
|
x = fx10k()
|
||||||
if b {
|
if b {
|
||||||
_ = x[9999] // ERROR "nil check"
|
_ = x[9999] // ERROR "nil check"
|
||||||
} else {
|
} else {
|
||||||
_ = x[9999] // ERROR "nil check"
|
_ = x[9999] // ERROR "nil check"
|
||||||
}
|
}
|
||||||
_ = x[9999] // ERROR "nil check"
|
_ = x[9999] // ERROR "nil check"
|
||||||
|
|
||||||
fx10k()
|
fx10k()
|
||||||
// This one is a bit redundant, if we figured out that
|
// This one is a bit redundant, if we figured out that
|
||||||
// x wasn't going to change across the function call.
|
// x wasn't going to change across the function call.
|
||||||
@ -138,7 +139,7 @@ func f3b() {
|
|||||||
_ = &x[9] // ERROR "nil check"
|
_ = &x[9] // ERROR "nil check"
|
||||||
}
|
}
|
||||||
|
|
||||||
func fx10() *[10]int
|
func fx10() *[10]int
|
||||||
|
|
||||||
func f4(x *[10]int) {
|
func f4(x *[10]int) {
|
||||||
// Most of these have no checks because a real memory reference follows,
|
// Most of these have no checks because a real memory reference follows,
|
||||||
@ -146,33 +147,33 @@ func f4(x *[10]int) {
|
|||||||
// in the first unmapped page of memory.
|
// in the first unmapped page of memory.
|
||||||
|
|
||||||
_ = x[9] // ERROR "nil check"
|
_ = x[9] // ERROR "nil check"
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if x[9] != 0 { // ERROR "nil check"
|
if x[9] != 0 { // ERROR "nil check"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
x = fx10()
|
x = fx10()
|
||||||
_ = x[9] // ERROR "nil check"
|
_ = x[9] // ERROR "nil check"
|
||||||
if b {
|
if b {
|
||||||
_ = x[9] // ERROR "nil check"
|
_ = x[9] // ERROR "nil check"
|
||||||
} else {
|
} else {
|
||||||
_ = x[9] // ERROR "nil check"
|
_ = x[9] // ERROR "nil check"
|
||||||
}
|
}
|
||||||
_ = x[9] // ERROR "nil check"
|
_ = x[9] // ERROR "nil check"
|
||||||
|
|
||||||
x = fx10()
|
x = fx10()
|
||||||
if b {
|
if b {
|
||||||
_ = x[9] // ERROR "nil check"
|
_ = x[9] // ERROR "nil check"
|
||||||
} else {
|
} else {
|
||||||
_ = &x[9] // ERROR "nil check"
|
_ = &x[9] // ERROR "nil check"
|
||||||
}
|
}
|
||||||
_ = x[9] // ERROR "nil check"
|
_ = x[9] // ERROR "nil check"
|
||||||
|
|
||||||
fx10()
|
fx10()
|
||||||
_ = x[9] // ERROR "nil check"
|
_ = x[9] // ERROR "nil check"
|
||||||
|
|
||||||
x = fx10()
|
x = fx10()
|
||||||
y := fx10()
|
y := fx10()
|
||||||
_ = &x[9] // ERROR "nil check"
|
_ = &x[9] // ERROR "nil check"
|
||||||
|
187
test/nilcheck_ssa.go
Normal file
187
test/nilcheck_ssa.go
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
// +build amd64
|
||||||
|
// errorcheck -0 -N -d=nil
|
||||||
|
|
||||||
|
// Copyright 2013 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.
|
||||||
|
|
||||||
|
// Test that nil checks are inserted.
|
||||||
|
// Optimization is disabled, so redundant checks are not removed.
|
||||||
|
|
||||||
|
package p
|
||||||
|
|
||||||
|
type Struct struct {
|
||||||
|
X int
|
||||||
|
Y float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type BigStruct struct {
|
||||||
|
X int
|
||||||
|
Y float64
|
||||||
|
A [1 << 20]int
|
||||||
|
Z string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Empty struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type Empty1 struct {
|
||||||
|
Empty
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
intp *int
|
||||||
|
arrayp *[10]int
|
||||||
|
array0p *[0]int
|
||||||
|
bigarrayp *[1 << 26]int
|
||||||
|
structp *Struct
|
||||||
|
bigstructp *BigStruct
|
||||||
|
emptyp *Empty
|
||||||
|
empty1p *Empty1
|
||||||
|
)
|
||||||
|
|
||||||
|
func f1() {
|
||||||
|
_ = *intp // ERROR "nil check"
|
||||||
|
_ = *arrayp // ERROR "nil check"
|
||||||
|
_ = *array0p // ERROR "nil check"
|
||||||
|
_ = *array0p // ERROR "nil check"
|
||||||
|
_ = *intp // ERROR "nil check"
|
||||||
|
_ = *arrayp // ERROR "nil check"
|
||||||
|
_ = *structp // ERROR "nil check"
|
||||||
|
_ = *emptyp // ERROR "nil check"
|
||||||
|
_ = *arrayp // ERROR "nil check"
|
||||||
|
}
|
||||||
|
|
||||||
|
func f2() {
|
||||||
|
var (
|
||||||
|
intp *int
|
||||||
|
arrayp *[10]int
|
||||||
|
array0p *[0]int
|
||||||
|
bigarrayp *[1 << 20]int
|
||||||
|
structp *Struct
|
||||||
|
bigstructp *BigStruct
|
||||||
|
emptyp *Empty
|
||||||
|
empty1p *Empty1
|
||||||
|
)
|
||||||
|
|
||||||
|
_ = *intp // ERROR "nil check"
|
||||||
|
_ = *arrayp // ERROR "nil check"
|
||||||
|
_ = *array0p // ERROR "nil check"
|
||||||
|
_ = *array0p // ERROR "removed nil check"
|
||||||
|
_ = *intp // ERROR "removed nil check"
|
||||||
|
_ = *arrayp // ERROR "removed nil check"
|
||||||
|
_ = *structp // ERROR "nil check"
|
||||||
|
_ = *emptyp // ERROR "nil check"
|
||||||
|
_ = *arrayp // ERROR "removed nil check"
|
||||||
|
_ = *bigarrayp // ERROR "nil check"
|
||||||
|
_ = *bigstructp // ERROR "nil check"
|
||||||
|
_ = *empty1p // ERROR "nil check"
|
||||||
|
}
|
||||||
|
|
||||||
|
func fx10k() *[10000]int
|
||||||
|
|
||||||
|
var b bool
|
||||||
|
|
||||||
|
func f3(x *[10000]int) {
|
||||||
|
// Using a huge type and huge offsets so the compiler
|
||||||
|
// does not expect the memory hardware to fault.
|
||||||
|
_ = x[9999] // ERROR "nil check"
|
||||||
|
|
||||||
|
for {
|
||||||
|
if x[9999] != 0 { // ERROR "removed nil check"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x = fx10k()
|
||||||
|
_ = x[9999] // ERROR "nil check"
|
||||||
|
if b {
|
||||||
|
_ = x[9999] // ERROR "removed nil check"
|
||||||
|
} else {
|
||||||
|
_ = x[9999] // ERROR "removed nil check"
|
||||||
|
}
|
||||||
|
_ = x[9999] // ERROR "removed nil check"
|
||||||
|
|
||||||
|
x = fx10k()
|
||||||
|
if b {
|
||||||
|
_ = x[9999] // ERROR "nil check"
|
||||||
|
} else {
|
||||||
|
_ = x[9999] // ERROR "nil check"
|
||||||
|
}
|
||||||
|
_ = x[9999] // ERROR "nil check"
|
||||||
|
|
||||||
|
fx10k()
|
||||||
|
// SSA nilcheck removal works across calls.
|
||||||
|
_ = x[9999] // ERROR "removed nil check"
|
||||||
|
}
|
||||||
|
|
||||||
|
func f3a() {
|
||||||
|
x := fx10k()
|
||||||
|
y := fx10k()
|
||||||
|
z := fx10k()
|
||||||
|
_ = &x[9] // ERROR "nil check"
|
||||||
|
y = z
|
||||||
|
_ = &x[9] // ERROR "removed nil check"
|
||||||
|
x = y
|
||||||
|
_ = &x[9] // ERROR "nil check"
|
||||||
|
}
|
||||||
|
|
||||||
|
func f3b() {
|
||||||
|
x := fx10k()
|
||||||
|
y := fx10k()
|
||||||
|
_ = &x[9] // ERROR "nil check"
|
||||||
|
y = x
|
||||||
|
_ = &x[9] // ERROR "removed nil check"
|
||||||
|
x = y
|
||||||
|
_ = &x[9] // ERROR "removed nil check"
|
||||||
|
}
|
||||||
|
|
||||||
|
func fx10() *[10]int
|
||||||
|
|
||||||
|
func f4(x *[10]int) {
|
||||||
|
// Most of these have no checks because a real memory reference follows,
|
||||||
|
// and the offset is small enough that if x is nil, the address will still be
|
||||||
|
// in the first unmapped page of memory.
|
||||||
|
|
||||||
|
_ = x[9] // ERROR "nil check"
|
||||||
|
|
||||||
|
for {
|
||||||
|
if x[9] != 0 { // ERROR "removed nil check"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x = fx10()
|
||||||
|
_ = x[9] // ERROR "nil check"
|
||||||
|
if b {
|
||||||
|
_ = x[9] // ERROR "removed nil check"
|
||||||
|
} else {
|
||||||
|
_ = x[9] // ERROR "removed nil check"
|
||||||
|
}
|
||||||
|
_ = x[9] // ERROR "removed nil check"
|
||||||
|
|
||||||
|
x = fx10()
|
||||||
|
if b {
|
||||||
|
_ = x[9] // ERROR "nil check"
|
||||||
|
} else {
|
||||||
|
_ = &x[9] // ERROR "nil check"
|
||||||
|
}
|
||||||
|
_ = x[9] // ERROR "nil check"
|
||||||
|
|
||||||
|
fx10()
|
||||||
|
_ = x[9] // ERROR "removed nil check"
|
||||||
|
|
||||||
|
x = fx10()
|
||||||
|
y := fx10()
|
||||||
|
_ = &x[9] // ERROR "nil check"
|
||||||
|
y = x
|
||||||
|
_ = &x[9] // ERROR "removed nil check"
|
||||||
|
x = y
|
||||||
|
_ = &x[9] // ERROR "removed nil check"
|
||||||
|
}
|
||||||
|
|
||||||
|
func f5(m map[string]struct{}) bool {
|
||||||
|
// Existence-only map lookups should not generate a nil check
|
||||||
|
_, ok := m[""]
|
||||||
|
return ok
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
// errorcheck -0 -d=nil
|
// errorcheck -0 -d=nil
|
||||||
// Fails on ppc64x because of incomplete optimization.
|
// Fails on ppc64x because of incomplete optimization.
|
||||||
// See issues 9058.
|
// See issues 9058.
|
||||||
// +build !ppc64,!ppc64le
|
// +build !ppc64,!ppc64le,!amd64
|
||||||
|
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
|
194
test/nilptr3_ssa.go
Normal file
194
test/nilptr3_ssa.go
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
// errorcheck -0 -d=nil
|
||||||
|
// Fails on ppc64x because of incomplete optimization.
|
||||||
|
// See issues 9058.
|
||||||
|
// +build !ppc64,!ppc64le,amd64
|
||||||
|
|
||||||
|
// Copyright 2013 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.
|
||||||
|
|
||||||
|
// Test that nil checks are removed.
|
||||||
|
// Optimization is enabled.
|
||||||
|
|
||||||
|
package p
|
||||||
|
|
||||||
|
type Struct struct {
|
||||||
|
X int
|
||||||
|
Y float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type BigStruct struct {
|
||||||
|
X int
|
||||||
|
Y float64
|
||||||
|
A [1 << 20]int
|
||||||
|
Z string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Empty struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type Empty1 struct {
|
||||||
|
Empty
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
intp *int
|
||||||
|
arrayp *[10]int
|
||||||
|
array0p *[0]int
|
||||||
|
bigarrayp *[1 << 26]int
|
||||||
|
structp *Struct
|
||||||
|
bigstructp *BigStruct
|
||||||
|
emptyp *Empty
|
||||||
|
empty1p *Empty1
|
||||||
|
)
|
||||||
|
|
||||||
|
func f1() {
|
||||||
|
_ = *intp // ERROR "generated nil check"
|
||||||
|
|
||||||
|
// This one should be removed but the block copy needs
|
||||||
|
// to be turned into its own pseudo-op in order to see
|
||||||
|
// the indirect.
|
||||||
|
_ = *arrayp // ERROR "generated nil check"
|
||||||
|
|
||||||
|
// 0-byte indirect doesn't suffice.
|
||||||
|
// we don't registerize globals, so there are no removed.* nil checks.
|
||||||
|
_ = *array0p // ERROR "generated nil check"
|
||||||
|
_ = *array0p // ERROR "removed nil check"
|
||||||
|
|
||||||
|
_ = *intp // ERROR "removed nil check"
|
||||||
|
_ = *arrayp // ERROR "removed nil check"
|
||||||
|
_ = *structp // ERROR "generated nil check"
|
||||||
|
_ = *emptyp // ERROR "generated nil check"
|
||||||
|
_ = *arrayp // ERROR "removed nil check"
|
||||||
|
}
|
||||||
|
|
||||||
|
func f2() {
|
||||||
|
var (
|
||||||
|
intp *int
|
||||||
|
arrayp *[10]int
|
||||||
|
array0p *[0]int
|
||||||
|
bigarrayp *[1 << 20]int
|
||||||
|
structp *Struct
|
||||||
|
bigstructp *BigStruct
|
||||||
|
emptyp *Empty
|
||||||
|
empty1p *Empty1
|
||||||
|
)
|
||||||
|
|
||||||
|
_ = *intp // ERROR "generated nil check"
|
||||||
|
_ = *arrayp // ERROR "generated nil check"
|
||||||
|
_ = *array0p // ERROR "generated nil check"
|
||||||
|
_ = *array0p // ERROR "removed.* nil check"
|
||||||
|
_ = *intp // ERROR "removed.* nil check"
|
||||||
|
_ = *arrayp // ERROR "removed.* nil check"
|
||||||
|
_ = *structp // ERROR "generated nil check"
|
||||||
|
_ = *emptyp // ERROR "generated nil check"
|
||||||
|
_ = *arrayp // ERROR "removed.* nil check"
|
||||||
|
_ = *bigarrayp // ERROR "generated nil check" ARM removed nil check before indirect!!
|
||||||
|
_ = *bigstructp // ERROR "generated nil check"
|
||||||
|
_ = *empty1p // ERROR "generated nil check"
|
||||||
|
}
|
||||||
|
|
||||||
|
func fx10k() *[10000]int
|
||||||
|
|
||||||
|
var b bool
|
||||||
|
|
||||||
|
func f3(x *[10000]int) {
|
||||||
|
// Using a huge type and huge offsets so the compiler
|
||||||
|
// does not expect the memory hardware to fault.
|
||||||
|
_ = x[9999] // ERROR "generated nil check"
|
||||||
|
|
||||||
|
for {
|
||||||
|
if x[9999] != 0 { // ERROR "removed nil check"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x = fx10k()
|
||||||
|
_ = x[9999] // ERROR "generated nil check"
|
||||||
|
if b {
|
||||||
|
_ = x[9999] // ERROR "removed.* nil check"
|
||||||
|
} else {
|
||||||
|
_ = x[9999] // ERROR "removed.* nil check"
|
||||||
|
}
|
||||||
|
_ = x[9999] // ERROR "removed nil check"
|
||||||
|
|
||||||
|
x = fx10k()
|
||||||
|
if b {
|
||||||
|
_ = x[9999] // ERROR "generated nil check"
|
||||||
|
} else {
|
||||||
|
_ = x[9999] // ERROR "generated nil check"
|
||||||
|
}
|
||||||
|
_ = x[9999] // ERROR "generated nil check"
|
||||||
|
|
||||||
|
fx10k()
|
||||||
|
// This one is a bit redundant, if we figured out that
|
||||||
|
// x wasn't going to change across the function call.
|
||||||
|
// But it's a little complex to do and in practice doesn't
|
||||||
|
// matter enough.
|
||||||
|
_ = x[9999] // ERROR "removed nil check"
|
||||||
|
}
|
||||||
|
|
||||||
|
func f3a() {
|
||||||
|
x := fx10k()
|
||||||
|
y := fx10k()
|
||||||
|
z := fx10k()
|
||||||
|
_ = &x[9] // ERROR "generated nil check"
|
||||||
|
y = z
|
||||||
|
_ = &x[9] // ERROR "removed.* nil check"
|
||||||
|
x = y
|
||||||
|
_ = &x[9] // ERROR "generated nil check"
|
||||||
|
}
|
||||||
|
|
||||||
|
func f3b() {
|
||||||
|
x := fx10k()
|
||||||
|
y := fx10k()
|
||||||
|
_ = &x[9] // ERROR "generated nil check"
|
||||||
|
y = x
|
||||||
|
_ = &x[9] // ERROR "removed.* nil check"
|
||||||
|
x = y
|
||||||
|
_ = &x[9] // ERROR "removed.* nil check"
|
||||||
|
}
|
||||||
|
|
||||||
|
func fx10() *[10]int
|
||||||
|
|
||||||
|
func f4(x *[10]int) {
|
||||||
|
// Most of these have no checks because a real memory reference follows,
|
||||||
|
// and the offset is small enough that if x is nil, the address will still be
|
||||||
|
// in the first unmapped page of memory.
|
||||||
|
|
||||||
|
_ = x[9] // ERROR "generated nil check" // bug would like to remove before indirect
|
||||||
|
|
||||||
|
for {
|
||||||
|
if x[9] != 0 { // ERROR "removed nil check"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x = fx10()
|
||||||
|
_ = x[9] // ERROR "generated nil check" // bug would like to remove before indirect
|
||||||
|
if b {
|
||||||
|
_ = x[9] // ERROR "removed nil check"
|
||||||
|
} else {
|
||||||
|
_ = x[9] // ERROR "removed nil check"
|
||||||
|
}
|
||||||
|
_ = x[9] // ERROR "removed nil check"
|
||||||
|
|
||||||
|
x = fx10()
|
||||||
|
if b {
|
||||||
|
_ = x[9] // ERROR "generated nil check" // bug would like to remove before indirect
|
||||||
|
} else {
|
||||||
|
_ = &x[9] // ERROR "generated nil check"
|
||||||
|
}
|
||||||
|
_ = x[9] // ERROR "generated nil check" // bug would like to remove before indirect
|
||||||
|
|
||||||
|
fx10()
|
||||||
|
_ = x[9] // ERROR "removed nil check"
|
||||||
|
|
||||||
|
x = fx10()
|
||||||
|
y := fx10()
|
||||||
|
_ = &x[9] // ERROR "generated nil check"
|
||||||
|
y = x
|
||||||
|
_ = &x[9] // ERROR "removed[a-z ]* nil check"
|
||||||
|
x = y
|
||||||
|
_ = &x[9] // ERROR "removed[a-z ]* nil check"
|
||||||
|
}
|
@ -37,6 +37,7 @@ var (
|
|||||||
numParallel = flag.Int("n", runtime.NumCPU(), "number of parallel tests to run")
|
numParallel = flag.Int("n", runtime.NumCPU(), "number of parallel tests to run")
|
||||||
summary = flag.Bool("summary", false, "show summary of results")
|
summary = flag.Bool("summary", false, "show summary of results")
|
||||||
showSkips = flag.Bool("show_skips", false, "show skipped tests")
|
showSkips = flag.Bool("show_skips", false, "show skipped tests")
|
||||||
|
runSkips = flag.Bool("run_skips", false, "run skipped tests (ignore skip and build tags)")
|
||||||
updateErrors = flag.Bool("update_errors", false, "update error messages in test file based on compiler output")
|
updateErrors = flag.Bool("update_errors", false, "update error messages in test file based on compiler output")
|
||||||
runoutputLimit = flag.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run")
|
runoutputLimit = flag.Int("l", defaultRunOutputLimit(), "number of parallel runoutput tests to run")
|
||||||
|
|
||||||
@ -328,6 +329,9 @@ type context struct {
|
|||||||
// shouldTest looks for build tags in a source file and returns
|
// shouldTest looks for build tags in a source file and returns
|
||||||
// whether the file should be used according to the tags.
|
// whether the file should be used according to the tags.
|
||||||
func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) {
|
func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) {
|
||||||
|
if *runSkips {
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
for _, line := range strings.Split(src, "\n") {
|
for _, line := range strings.Split(src, "\n") {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
if strings.HasPrefix(line, "//") {
|
if strings.HasPrefix(line, "//") {
|
||||||
@ -470,6 +474,9 @@ func (t *test) run() {
|
|||||||
args = args[1:]
|
args = args[1:]
|
||||||
}
|
}
|
||||||
case "skip":
|
case "skip":
|
||||||
|
if *runSkips {
|
||||||
|
break
|
||||||
|
}
|
||||||
t.action = "skip"
|
t.action = "skip"
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// +build !amd64
|
||||||
// errorcheck -0 -d=append,slice
|
// errorcheck -0 -d=append,slice
|
||||||
|
|
||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user