if(p->as != ACHECKNIL)
continue;
if(debug_checknil && p->lineno > 1) // p->lineno==1 in generated wrappers
- warnl(p->lineno, "nil check %D", &p->from);
+ warnl(p->lineno, "generated nil check");
if(p->from.type != D_REG)
fatal("invalid nil check %P", p);
reg = p->from.reg;
return 0;
}
+int
+sameaddr(Adr *a, Adr *v)
+{
+ if(a->type != v->type)
+ return 0;
+ if(regtyp(v) && a->reg == v->reg)
+ return 1;
+ if(v->type == D_AUTO || v->type == D_PARAM) {
+ if(v->offset == a->offset)
+ return 1;
+ }
+ return 0;
+}
+
/*
* either direct or indirect
*/
return 1;
return 0;
}
+
+int
+stackaddr(Addr *a)
+{
+ return regtyp(a) && a->reg == REGSP;
+}
+
+int
+smallindir(Addr *a, Addr *reg)
+{
+ return reg->type == D_REG && a->type == D_OREG &&
+ a->reg == reg->reg &&
+ 0 <= a->offset && a->offset < 4096;
+}
[AMOVHU]= {SizeW | LeftRead | RightWrite | Conv},
// Jumps.
- [AB]= {Jump},
+ [AB]= {Jump | Break},
[ABL]= {Call},
[ABEQ]= {Cjmp},
[ABNE]= {Cjmp},
print(" pred:");
for(; r1 != nil; r1 = r1->p2link)
print(" %.4ud", r1->prog->loc);
+ if(r->p1 != nil)
+ print(" (and %.4ud)", r->p1->prog->loc);
+ else
+ print(" (only)");
print("\n");
}
// r1 = r->s1;
fixlargeoffset(a);
return;
}
+ break;
}
agenr(n, a, res);
if(p->as != ACHECKNIL)
continue;
if(debug_checknil && p->lineno > 1) // p->lineno==1 in generated wrappers
- warnl(p->lineno, "nil check %D", &p->from);
+ warnl(p->lineno, "generated nil check");
// check is
// CMP arg, $0
// JNE 2(PC) (likely)
return 0;
}
+int
+sameaddr(Addr *a, Addr *v)
+{
+ if(a->type != v->type)
+ return 0;
+ if(regtyp(v))
+ return 1;
+ if(v->type == D_AUTO || v->type == D_PARAM)
+ if(v->offset == a->offset)
+ return 1;
+ return 0;
+}
+
/*
* either direct or indirect
*/
break;
}
}
+
+int
+smallindir(Addr *a, Addr *reg)
+{
+ return regtyp(reg) &&
+ a->type == D_INDIR + reg->type &&
+ a->index == D_NONE &&
+ 0 <= a->offset && a->offset < 4096;
+}
+
+int
+stackaddr(Addr *a)
+{
+ return regtyp(a) && a->type == D_SP;
+}
a->xoffset = fp->width;
a->type = n->type;
return;
+
+ case OINDEX:
+ // Index of fixed-size array by constant can
+ // put the offset in the addressing.
+ // Could do the same for slice except that we need
+ // to use the real index for the bounds checking.
+ if(isfixedarray(n->left->type) ||
+ (isptr[n->left->type->etype] && isfixedarray(n->left->left->type)))
+ if(isconst(n->right, CTINT)) {
+ // Compute &a.
+ if(!isptr[n->left->type->etype])
+ igen(n->left, a, res);
+ else {
+ igen(n->left, &n1, res);
+ cgen_checknil(&n1);
+ regalloc(a, types[tptr], res);
+ gmove(&n1, a);
+ regfree(&n1);
+ a->op = OINDREG;
+ }
+
+ // Compute &a[i] as &a + i*width.
+ a->type = n->type;
+ a->xoffset += mpgetfix(n->right->val.u.xval)*n->type->width;
+ return;
+ }
+ break;
}
+
// release register for now, to avoid
// confusing tempname.
if(res != N && res->op == OREGISTER)
if(p->as != ACHECKNIL)
continue;
if(debug_checknil && p->lineno > 1) // p->lineno==1 in generated wrappers
- warnl(p->lineno, "nil check %D", &p->from);
+ warnl(p->lineno, "generated nil check");
// check is
// CMP arg, $0
// JNE 2(PC) (likely)
return 0;
}
+int
+sameaddr(Addr *a, Addr *v)
+{
+ if(a->type != v->type)
+ return 0;
+ if(regtyp(v))
+ return 1;
+ if(v->type == D_AUTO || v->type == D_PARAM)
+ if(v->offset == a->offset)
+ return 1;
+ return 0;
+}
+
/*
* either direct or indirect
*/
break;
}
}
+
+int
+smallindir(Addr *a, Addr *reg)
+{
+ return regtyp(reg) &&
+ a->type == D_INDIR + reg->type &&
+ a->index == D_NONE &&
+ 0 <= a->offset && a->offset < 4096;
+}
+
+int
+stackaddr(Addr *a)
+{
+ return regtyp(a) && a->type == D_SP;
+}
case OPLUS:
cgen_discard(nr->left);
break;
+
+ case OIND:
+ cgen_checknil(nr->left);
+ break;
// special enough to just evaluate
default:
if(!debug['N'] || debug['R'] || debug['P']) {
regopt(ptxt);
+ nilopt(ptxt);
}
expandchecks(ptxt);
if(disable_checknil)
return;
- while(n->op == ODOT || (n->op == OINDEX && isfixedarray(n->left->type->type))) // NOTE: not ODOTPTR
- n = n->left;
+ // Ideally we wouldn't see any TUINTPTR here, but we do.
+ if(n->type == T || (!isptr[n->type->etype] && n->type->etype != TUINTPTR && n->type->etype != TUNSAFEPTR)) {
+ dump("checknil", n);
+ fatal("bad checknil");
+ }
if((thechar == '5' && n->op != OREGISTER) || !n->addable) {
regalloc(®, types[tptr], n);
cgen(n, ®);
for(r2 = (TempFlow*)r->f.p2; r2 != nil; r2 = (TempFlow*)r2->f.p2link)
mergewalk(v, r2, gen);
}
+
+// Eliminate redundant nil pointer checks.
+//
+// The code generation pass emits a CHECKNIL for every possibly nil pointer.
+// This pass removes a CHECKNIL if every predecessor path has already
+// checked this value for nil.
+//
+// Simple backwards flood from check to definition.
+// Run prog loop backward from end of program to beginning to avoid quadratic
+// behavior removing a run of checks.
+//
+// Assume that stack variables with address not taken can be loaded multiple times
+// from memory without being rechecked. Other variables need to be checked on
+// each load.
+
+typedef struct NilVar NilVar;
+typedef struct NilFlow NilFlow;
+
+struct NilFlow {
+ Flow f;
+ int kill;
+};
+
+static void nilwalkback(NilFlow *rcheck);
+static void nilwalkfwd(NilFlow *rcheck);
+
+void
+nilopt(Prog *firstp)
+{
+ NilFlow *r;
+ Prog *p;
+ uint32 gen;
+ Graph *g;
+ int ncheck, nkill;
+
+ g = flowstart(firstp, sizeof(NilFlow));
+ if(g == nil)
+ return;
+
+ if(debug_checknil > 1 /* || strcmp(curfn->nname->sym->name, "f1") == 0 */)
+ dumpit("nilopt", g->start, 0);
+
+ gen = 0;
+ ncheck = 0;
+ nkill = 0;
+ for(r = (NilFlow*)g->start; r != nil; r = (NilFlow*)r->f.link) {
+ p = r->f.prog;
+ if(p->as != ACHECKNIL || !regtyp(&p->from))
+ continue;
+ ncheck++;
+ if(stackaddr(&p->from)) {
+ if(debug_checknil && p->lineno > 1)
+ warnl(p->lineno, "removed nil check of SP address");
+ r->kill = 1;
+ continue;
+ }
+ nilwalkfwd(r);
+ if(r->kill) {
+ if(debug_checknil && p->lineno > 1)
+ warnl(p->lineno, "removed nil check before indirect");
+ continue;
+ }
+ nilwalkback(r);
+ if(r->kill) {
+ if(debug_checknil && p->lineno > 1)
+ warnl(p->lineno, "removed repeated nil check");
+ continue;
+ }
+ }
+
+ for(r = (NilFlow*)g->start; r != nil; r = (NilFlow*)r->f.link) {
+ if(r->kill) {
+ nkill++;
+ excise(&r->f);
+ }
+ }
+
+ flowend(g);
+
+ if(debug_checknil > 1)
+ print("%S: removed %d of %d nil checks\n", curfn->nname->sym, nkill, ncheck);
+}
+
+static void
+nilwalkback(NilFlow *rcheck)
+{
+ Prog *p;
+ ProgInfo info;
+ NilFlow *r;
+
+ for(r = rcheck; r != nil; r = (NilFlow*)uniqp(&r->f)) {
+ p = r->f.prog;
+ proginfo(&info, p);
+ if((info.flags & RightWrite) && sameaddr(&p->to, &rcheck->f.prog->from)) {
+ // Found initialization of value we're checking for nil.
+ // without first finding the check, so this one is unchecked.
+ return;
+ }
+ if(r != rcheck && p->as == ACHECKNIL && sameaddr(&p->from, &rcheck->f.prog->from)) {
+ rcheck->kill = 1;
+ return;
+ }
+ }
+
+ // Here is a more complex version that scans backward across branches.
+ // It assumes rcheck->kill = 1 has been set on entry, and its job is to find a reason
+ // to keep the check (setting rcheck->kill = 0).
+ // It doesn't handle copying of aggregates as well as I would like,
+ // nor variables with their address taken,
+ // and it's too subtle to turn on this late in Go 1.2. Perhaps for Go 1.3.
+ /*
+ for(r1 = r0; r1 != nil; r1 = (NilFlow*)r1->f.p1) {
+ if(r1->f.active == gen)
+ break;
+ r1->f.active = gen;
+ p = r1->f.prog;
+
+ // If same check, stop this loop but still check
+ // alternate predecessors up to this point.
+ if(r1 != rcheck && p->as == ACHECKNIL && sameaddr(&p->from, &rcheck->f.prog->from))
+ break;
+
+ proginfo(&info, p);
+ if((info.flags & RightWrite) && sameaddr(&p->to, &rcheck->f.prog->from)) {
+ // Found initialization of value we're checking for nil.
+ // without first finding the check, so this one is unchecked.
+ rcheck->kill = 0;
+ return;
+ }
+
+ if(r1->f.p1 == nil && r1->f.p2 == nil) {
+ print("lost pred for %P\n", rcheck->f.prog);
+ for(r1=r0; r1!=nil; r1=(NilFlow*)r1->f.p1) {
+ proginfo(&info, r1->f.prog);
+ print("\t%P %d %d %D %D\n", r1->f.prog, info.flags&RightWrite, sameaddr(&r1->f.prog->to, &rcheck->f.prog->from), &r1->f.prog->to, &rcheck->f.prog->from);
+ }
+ fatal("lost pred trail");
+ }
+ }
+
+ for(r = r0; r != r1; r = (NilFlow*)r->f.p1)
+ for(r2 = (NilFlow*)r->f.p2; r2 != nil; r2 = (NilFlow*)r2->f.p2link)
+ nilwalkback(rcheck, r2, gen);
+ */
+}
+
+static void
+nilwalkfwd(NilFlow *rcheck)
+{
+ NilFlow *r;
+ Prog *p;
+ ProgInfo info;
+
+ // If the path down from rcheck dereferences the address
+ // (possibly with a small offset) before writing to memory
+ // and before any subsequent checks, it's okay to wait for
+ // that implicit check. Only consider this basic block to
+ // avoid problems like:
+ // _ = *x // should panic
+ // for {} // no writes but infinite loop may be considered visible
+ for(r = (NilFlow*)uniqs(&rcheck->f); r != nil; r = (NilFlow*)uniqs(&r->f)) {
+ p = r->f.prog;
+ proginfo(&info, p);
+
+ if((info.flags & LeftRead) && smallindir(&p->from, &rcheck->f.prog->from)) {
+ rcheck->kill = 1;
+ return;
+ }
+ if((info.flags & (RightRead|RightWrite)) && smallindir(&p->to, &rcheck->f.prog->from)) {
+ rcheck->kill = 1;
+ return;
+ }
+
+ // Stop if another nil check happens.
+ if(p->as == ACHECKNIL)
+ return;
+ // Stop if value is lost.
+ if((info.flags & RightWrite) && sameaddr(&p->to, &rcheck->f.prog->from))
+ return;
+ // Stop if memory write.
+ if((info.flags & RightWrite) && !regtyp(&p->to))
+ return;
+ }
+}
void flowrpo(Graph*);
void flowend(Graph*);
void mergetemp(Prog*);
+void nilopt(Prog*);
int noreturn(Prog*);
int regtyp(Addr*);
+int sameaddr(Addr*, Addr*);
+int smallindir(Addr*, Addr*);
+int stackaddr(Addr*);
Flow* uniqp(Flow*);
Flow* uniqs(Flow*);
goto ret;
case OIND:
- if(n->left->type->type->width == 0) {
- // No actual copy will be generated, so emit an explicit nil check.
- n->left = cheapexpr(n->left, init);
- checknil(n->left, init);
- }
walkexpr(&n->left, init);
goto ret;
--- /dev/null
+// errorcheck -0 -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 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
+ _ = *array0p // ERROR "generated nil check"
+ _ = *array0p // ERROR "removed repeated nil check" 386
+
+ _ = *intp // ERROR "removed repeated nil check"
+ _ = *arrayp // ERROR "removed repeated nil check"
+ _ = *structp // ERROR "generated nil check"
+ _ = *emptyp // ERROR "generated nil check"
+ _ = *arrayp // ERROR "removed repeated 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 repeated nil check"
+ _ = *intp // ERROR "removed repeated nil check"
+ _ = *arrayp // ERROR "removed repeated nil check"
+ _ = *structp // ERROR "generated nil check"
+ _ = *emptyp // ERROR "generated nil check"
+ _ = *arrayp // ERROR "removed repeated 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 "generated nil check"
+ break
+ }
+ }
+
+ x = fx10k()
+ _ = x[9999] // ERROR "generated nil check"
+ if b {
+ _ = x[9999] // ERROR "removed repeated nil check"
+ } else {
+ _ = x[9999] // ERROR "removed repeated nil check"
+ }
+ _ = x[9999] // ERROR "generated 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 "generated nil check"
+}
+
+func f3a() {
+ x := fx10k()
+ y := fx10k()
+ z := fx10k()
+ _ = &x[9] // ERROR "generated nil check"
+ y = z
+ _ = &x[9] // ERROR "removed repeated 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 repeated nil check"
+ x = y
+ _ = &x[9] // ERROR "removed repeated 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 "removed nil check before indirect"
+
+ for {
+ if x[9] != 0 { // ERROR "removed nil check before indirect"
+ break
+ }
+ }
+
+ x = fx10()
+ _ = x[9] // ERROR "removed nil check before indirect"
+ if b {
+ _ = x[9] // ERROR "removed nil check before indirect"
+ } else {
+ _ = x[9] // ERROR "removed nil check before indirect"
+ }
+ _ = x[9] // ERROR "removed nil check before indirect"
+
+ x = fx10()
+ if b {
+ _ = x[9] // ERROR "removed nil check before indirect"
+ } else {
+ _ = &x[9] // ERROR "generated nil check"
+ }
+ _ = x[9] // ERROR "removed nil check before indirect"
+
+ fx10()
+ _ = x[9] // ERROR "removed nil check before indirect"
+
+ x = fx10()
+ y := fx10()
+ _ = &x[9] // ERROR "generated nil check"
+ y = x
+ _ = &x[9] // ERROR "removed repeated nil check"
+ x = y
+ _ = &x[9] // ERROR "removed repeated nil check"
+}
+