2009-03-31 01:22:59 -06:00
|
|
|
// Copyright 2009 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.
|
|
|
|
|
|
|
|
#undef EXTERN
|
|
|
|
#define EXTERN
|
2011-08-25 14:25:10 -06:00
|
|
|
#include <u.h>
|
|
|
|
#include <libc.h>
|
2009-03-31 01:22:59 -06:00
|
|
|
#include "gg.h"
|
2009-11-06 17:51:49 -07:00
|
|
|
#include "opt.h"
|
2009-03-31 01:22:59 -06:00
|
|
|
|
2014-02-19 15:08:55 -07:00
|
|
|
static Prog *appendpp(Prog*, int, int, vlong, int, vlong);
|
|
|
|
|
2009-03-31 01:22:59 -06:00
|
|
|
void
|
cmd/5g, cmd/5l, cmd/6g, cmd/6l, cmd/8g, cmd/8l, cmd/gc, runtime: generate pointer maps by liveness analysis
This change allows the garbage collector to examine stack
slots that are determined as live and containing a pointer
value by the garbage collector. This results in a mean
reduction of 65% in the number of stack slots scanned during
an invocation of "GOGC=1 all.bash".
Unfortunately, this does not yet allow garbage collection to
be precise for the stack slots computed as live. Pointers
confound the determination of what definitions reach a given
instruction. In general, this problem is not solvable without
runtime cost but some advanced cooperation from the compiler
might mitigate common cases.
R=golang-dev, rsc, cshapiro
CC=golang-dev
https://golang.org/cl/14430048
2013-12-05 18:35:22 -07:00
|
|
|
defframe(Prog *ptxt)
|
2009-03-31 01:22:59 -06:00
|
|
|
{
|
2013-08-09 21:10:58 -06:00
|
|
|
uint32 frame;
|
2014-02-19 15:08:55 -07:00
|
|
|
Prog *p;
|
2014-03-19 16:41:34 -06:00
|
|
|
vlong i;
|
cmd/gc: liveness-related bug fixes
1. On entry to a function, only zero the ambiguously live stack variables.
Before, we were zeroing all stack variables containing pointers.
The zeroing is pretty inefficient right now (issue 7624), but there are also
too many stack variables detected as ambiguously live (issue 7345),
and that must be addressed before deciding how to improve the zeroing code.
(Changes in 5g/ggen.c, 6g/ggen.c, 8g/ggen.c, gc/pgen.c)
Fixes #7647.
2. Make the regopt word-based liveness analysis preserve the
whole-variable liveness property expected by the garbage collection
bitmap liveness analysis. That is, if the regopt liveness decides that
one word in a struct needs to be preserved, make sure it preserves
the entire struct. This is particularly important for multiword values
such as strings, slices, and interfaces, in which all the words need
to be present in order to understand the meaning.
(Changes in 5g/reg.c, 6g/reg.c, 8g/reg.c.)
Fixes #7591.
3. Make the regopt word-based liveness analysis treat a variable
as having its address taken - which makes it preserved across
all future calls - whenever n->addrtaken is set, for consistency
with the gc bitmap liveness analysis, even if there is no machine
instruction actually taking the address. In this case n->addrtaken
is incorrect (a nicer way to put it is overconservative), and ideally
there would be no such cases, but they can happen and the two
analyses need to agree.
(Changes in 5g/reg.c, 6g/reg.c, 8g/reg.c; test in bug484.go.)
Fixes crashes found by turning off "zero everything" in step 1.
4. Remove spurious VARDEF annotations. As the comment in
gc/pgen.c explains, the VARDEF must immediately precede
the initialization. It cannot be too early, and it cannot be too late.
In particular, if a function call sits between the VARDEF and the
actual machine instructions doing the initialization, the variable
will be treated as live during that function call even though it is
uninitialized, leading to problems.
(Changes in gc/gen.c; test in live.go.)
Fixes crashes found by turning off "zero everything" in step 1.
5. Do not treat loading the address of a wide value as a signal
that the value must be initialized. Instead depend on the existence
of a VARDEF or the first actual read/write of a word in the value.
If the load is in order to pass the address to a function that does
the actual initialization, treating the load as an implicit VARDEF
causes the same problems as described in step 4.
The alternative is to arrange to zero every such value before
passing it to the real initialization function, but this is a much
easier and more efficient change.
(Changes in gc/plive.c.)
Fixes crashes found by turning off "zero everything" in step 1.
6. Treat wide input parameters with their address taken as
initialized on entry to the function. Otherwise they look
"ambiguously live" and we will try to emit code to zero them.
(Changes in gc/plive.c.)
Fixes crashes found by turning off "zero everything" in step 1.
7. An array of length 0 has no pointers, even if the element type does.
Without this change, the zeroing code complains when asked to
clear a 0-length array.
(Changes in gc/reflect.c.)
LGTM=khr
R=khr
CC=golang-codereviews
https://golang.org/cl/80160044
2014-03-27 12:05:57 -06:00
|
|
|
NodeList *l;
|
|
|
|
Node *n;
|
2013-08-09 21:10:58 -06:00
|
|
|
|
2009-03-31 01:22:59 -06:00
|
|
|
// fill in argument size
|
2010-12-13 09:57:41 -07:00
|
|
|
ptxt->to.offset2 = rnd(curfn->type->argwid, widthptr);
|
2009-03-31 01:22:59 -06:00
|
|
|
|
|
|
|
// fill in final stack size
|
2014-02-25 15:43:53 -07:00
|
|
|
frame = rnd(stksize+maxarg, widthptr);
|
2013-08-09 21:10:58 -06:00
|
|
|
ptxt->to.offset = frame;
|
2014-02-19 15:08:55 -07:00
|
|
|
|
|
|
|
// insert code to contain ambiguously live variables
|
|
|
|
// so that garbage collector only sees initialized values
|
|
|
|
// when it looks for pointers.
|
cmd/gc: liveness-related bug fixes
1. On entry to a function, only zero the ambiguously live stack variables.
Before, we were zeroing all stack variables containing pointers.
The zeroing is pretty inefficient right now (issue 7624), but there are also
too many stack variables detected as ambiguously live (issue 7345),
and that must be addressed before deciding how to improve the zeroing code.
(Changes in 5g/ggen.c, 6g/ggen.c, 8g/ggen.c, gc/pgen.c)
Fixes #7647.
2. Make the regopt word-based liveness analysis preserve the
whole-variable liveness property expected by the garbage collection
bitmap liveness analysis. That is, if the regopt liveness decides that
one word in a struct needs to be preserved, make sure it preserves
the entire struct. This is particularly important for multiword values
such as strings, slices, and interfaces, in which all the words need
to be present in order to understand the meaning.
(Changes in 5g/reg.c, 6g/reg.c, 8g/reg.c.)
Fixes #7591.
3. Make the regopt word-based liveness analysis treat a variable
as having its address taken - which makes it preserved across
all future calls - whenever n->addrtaken is set, for consistency
with the gc bitmap liveness analysis, even if there is no machine
instruction actually taking the address. In this case n->addrtaken
is incorrect (a nicer way to put it is overconservative), and ideally
there would be no such cases, but they can happen and the two
analyses need to agree.
(Changes in 5g/reg.c, 6g/reg.c, 8g/reg.c; test in bug484.go.)
Fixes crashes found by turning off "zero everything" in step 1.
4. Remove spurious VARDEF annotations. As the comment in
gc/pgen.c explains, the VARDEF must immediately precede
the initialization. It cannot be too early, and it cannot be too late.
In particular, if a function call sits between the VARDEF and the
actual machine instructions doing the initialization, the variable
will be treated as live during that function call even though it is
uninitialized, leading to problems.
(Changes in gc/gen.c; test in live.go.)
Fixes crashes found by turning off "zero everything" in step 1.
5. Do not treat loading the address of a wide value as a signal
that the value must be initialized. Instead depend on the existence
of a VARDEF or the first actual read/write of a word in the value.
If the load is in order to pass the address to a function that does
the actual initialization, treating the load as an implicit VARDEF
causes the same problems as described in step 4.
The alternative is to arrange to zero every such value before
passing it to the real initialization function, but this is a much
easier and more efficient change.
(Changes in gc/plive.c.)
Fixes crashes found by turning off "zero everything" in step 1.
6. Treat wide input parameters with their address taken as
initialized on entry to the function. Otherwise they look
"ambiguously live" and we will try to emit code to zero them.
(Changes in gc/plive.c.)
Fixes crashes found by turning off "zero everything" in step 1.
7. An array of length 0 has no pointers, even if the element type does.
Without this change, the zeroing code complains when asked to
clear a 0-length array.
(Changes in gc/reflect.c.)
LGTM=khr
R=khr
CC=golang-codereviews
https://golang.org/cl/80160044
2014-03-27 12:05:57 -06:00
|
|
|
//
|
|
|
|
// TODO: determine best way to zero the given values.
|
|
|
|
// among other problems, AX is initialized to 0 multiple times,
|
|
|
|
// but that's really the tip of the iceberg.
|
2014-02-19 15:08:55 -07:00
|
|
|
p = ptxt;
|
cmd/gc: liveness-related bug fixes
1. On entry to a function, only zero the ambiguously live stack variables.
Before, we were zeroing all stack variables containing pointers.
The zeroing is pretty inefficient right now (issue 7624), but there are also
too many stack variables detected as ambiguously live (issue 7345),
and that must be addressed before deciding how to improve the zeroing code.
(Changes in 5g/ggen.c, 6g/ggen.c, 8g/ggen.c, gc/pgen.c)
Fixes #7647.
2. Make the regopt word-based liveness analysis preserve the
whole-variable liveness property expected by the garbage collection
bitmap liveness analysis. That is, if the regopt liveness decides that
one word in a struct needs to be preserved, make sure it preserves
the entire struct. This is particularly important for multiword values
such as strings, slices, and interfaces, in which all the words need
to be present in order to understand the meaning.
(Changes in 5g/reg.c, 6g/reg.c, 8g/reg.c.)
Fixes #7591.
3. Make the regopt word-based liveness analysis treat a variable
as having its address taken - which makes it preserved across
all future calls - whenever n->addrtaken is set, for consistency
with the gc bitmap liveness analysis, even if there is no machine
instruction actually taking the address. In this case n->addrtaken
is incorrect (a nicer way to put it is overconservative), and ideally
there would be no such cases, but they can happen and the two
analyses need to agree.
(Changes in 5g/reg.c, 6g/reg.c, 8g/reg.c; test in bug484.go.)
Fixes crashes found by turning off "zero everything" in step 1.
4. Remove spurious VARDEF annotations. As the comment in
gc/pgen.c explains, the VARDEF must immediately precede
the initialization. It cannot be too early, and it cannot be too late.
In particular, if a function call sits between the VARDEF and the
actual machine instructions doing the initialization, the variable
will be treated as live during that function call even though it is
uninitialized, leading to problems.
(Changes in gc/gen.c; test in live.go.)
Fixes crashes found by turning off "zero everything" in step 1.
5. Do not treat loading the address of a wide value as a signal
that the value must be initialized. Instead depend on the existence
of a VARDEF or the first actual read/write of a word in the value.
If the load is in order to pass the address to a function that does
the actual initialization, treating the load as an implicit VARDEF
causes the same problems as described in step 4.
The alternative is to arrange to zero every such value before
passing it to the real initialization function, but this is a much
easier and more efficient change.
(Changes in gc/plive.c.)
Fixes crashes found by turning off "zero everything" in step 1.
6. Treat wide input parameters with their address taken as
initialized on entry to the function. Otherwise they look
"ambiguously live" and we will try to emit code to zero them.
(Changes in gc/plive.c.)
Fixes crashes found by turning off "zero everything" in step 1.
7. An array of length 0 has no pointers, even if the element type does.
Without this change, the zeroing code complains when asked to
clear a 0-length array.
(Changes in gc/reflect.c.)
LGTM=khr
R=khr
CC=golang-codereviews
https://golang.org/cl/80160044
2014-03-27 12:05:57 -06:00
|
|
|
for(l=curfn->dcl; l != nil; l = l->next) {
|
|
|
|
n = l->n;
|
|
|
|
if(!n->needzero)
|
|
|
|
continue;
|
|
|
|
if(n->class != PAUTO)
|
|
|
|
fatal("needzero class %d", n->class);
|
|
|
|
if(n->type->width % widthptr != 0 || n->xoffset % widthptr != 0 || n->type->width == 0)
|
|
|
|
fatal("var %lN has size %d offset %d", n, (int)n->type->width, (int)n->xoffset);
|
|
|
|
if(n->type->width <= 2*widthptr) {
|
|
|
|
for(i = 0; i < n->type->width; i += widthptr)
|
|
|
|
p = appendpp(p, AMOVL, D_CONST, 0, D_SP+D_INDIR, frame+n->xoffset+i);
|
|
|
|
} else if(n->type->width <= 16*widthptr) {
|
|
|
|
p = appendpp(p, AMOVL, D_CONST, 0, D_AX, 0);
|
|
|
|
for(i = 0; i < n->type->width; i += widthptr)
|
|
|
|
p = appendpp(p, AMOVL, D_AX, 0, D_SP+D_INDIR, frame+n->xoffset+i);
|
|
|
|
} else {
|
|
|
|
p = appendpp(p, AMOVL, D_CONST, 0, D_AX, 0);
|
|
|
|
p = appendpp(p, AMOVL, D_CONST, n->type->width/widthptr, D_CX, 0);
|
|
|
|
p = appendpp(p, ALEAL, D_SP+D_INDIR, frame+n->xoffset, D_DI, 0);
|
|
|
|
p = appendpp(p, AREP, D_NONE, 0, D_NONE, 0);
|
|
|
|
p = appendpp(p, ASTOSL, D_NONE, 0, D_NONE, 0);
|
2014-03-19 16:41:34 -06:00
|
|
|
}
|
2014-02-19 15:08:55 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static Prog*
|
|
|
|
appendpp(Prog *p, int as, int ftype, vlong foffset, int ttype, vlong toffset)
|
|
|
|
{
|
|
|
|
Prog *q;
|
|
|
|
q = mal(sizeof(*q));
|
|
|
|
clearp(q);
|
|
|
|
q->as = as;
|
|
|
|
q->lineno = p->lineno;
|
|
|
|
q->from.type = ftype;
|
|
|
|
q->from.offset = foffset;
|
|
|
|
q->to.type = ttype;
|
|
|
|
q->to.offset = toffset;
|
|
|
|
q->link = p->link;
|
|
|
|
p->link = q;
|
|
|
|
return q;
|
2009-03-31 01:22:59 -06:00
|
|
|
}
|
|
|
|
|
2011-06-14 09:03:37 -06:00
|
|
|
// Sweep the prog list to mark any used nodes.
|
|
|
|
void
|
|
|
|
markautoused(Prog* p)
|
|
|
|
{
|
|
|
|
for (; p; p = p->link) {
|
cmd/gc: shorten temporary lifetimes when possible
The new channel and map runtime routines take pointers
to values, typically temporaries. Without help, the compiler
cannot tell when those temporaries stop being needed,
because it isn't sure what happened to the pointer.
Arrange to insert explicit VARKILL instructions for these
temporaries so that the liveness analysis can avoid seeing
them as "ambiguously live".
The change is made in order.c, which was already in charge of
introducing temporaries to preserve the order-of-evaluation
guarantees. Now its job has expanded to include introducing
temporaries as needed by runtime routines, and then also
inserting the VARKILL annotations for all these temporaries,
so that their lifetimes can be shortened.
In order to do its job for the map runtime routines, order.c arranges
that all map lookups or map assignments have the form:
x = m[k]
x, y = m[k]
m[k] = x
where x, y, and k are simple variables (often temporaries).
Likewise, receiving from a channel is now always:
x = <-c
In order to provide the map guarantee, order.c is responsible for
rewriting x op= y into x = x op y, so that m[k] += z becomes
t = m[k]
t2 = t + z
m[k] = t2
While here, fix a few bugs in order.c's traversal: it was failing to
walk into select and switch case bodies, so order of evaluation
guarantees were not preserved in those situations.
Added tests to test/reorder2.go.
Fixes #7671.
In gc/popt's temporary-merging optimization, allow merging
of temporaries with their address taken as long as the liveness
ranges do not intersect. (There is a good chance of that now
that we have VARKILL annotations to limit the liveness range.)
Explicitly killing temporaries cuts the number of ambiguously
live temporaries that must be zeroed in the godoc binary from
860 to 711, or -17%. There is more work to be done, but this
is a good checkpoint.
Update #7345
LGTM=khr
R=khr
CC=golang-codereviews
https://golang.org/cl/81940043
2014-04-01 11:31:38 -06:00
|
|
|
if (p->as == ATYPE || p->as == AVARDEF || p->as == AVARKILL)
|
cmd/gc: emit explicit type information for local variables
The type information is (and for years has been) included
as an extra field in the address chunk of an instruction.
Unfortunately, suppose there is a string at a+24(FP) and
we have an instruction reading its length. It will say:
MOVQ x+32(FP), AX
and the type of *that* argument is int (not slice), because
it is the length being read. This confuses the picture seen
by debuggers and now, worse, by the garbage collector.
Instead of attaching the type information to all uses,
emit an explicit list of TYPE instructions with the information.
The TYPE instructions are no-ops whose only role is to
provide an address to attach type information to.
For example, this function:
func f(x, y, z int) (a, b string) {
return
}
now compiles into:
--- prog list "f" ---
0000 (/Users/rsc/x.go:3) TEXT f+0(SB),$0-56
0001 (/Users/rsc/x.go:3) LOCALS ,
0002 (/Users/rsc/x.go:3) TYPE x+0(FP){int},$8
0003 (/Users/rsc/x.go:3) TYPE y+8(FP){int},$8
0004 (/Users/rsc/x.go:3) TYPE z+16(FP){int},$8
0005 (/Users/rsc/x.go:3) TYPE a+24(FP){string},$16
0006 (/Users/rsc/x.go:3) TYPE b+40(FP){string},$16
0007 (/Users/rsc/x.go:3) MOVQ $0,b+40(FP)
0008 (/Users/rsc/x.go:3) MOVQ $0,b+48(FP)
0009 (/Users/rsc/x.go:3) MOVQ $0,a+24(FP)
0010 (/Users/rsc/x.go:3) MOVQ $0,a+32(FP)
0011 (/Users/rsc/x.go:4) RET ,
The { } show the formerly hidden type information.
The { } syntax is used when printing from within the gc compiler.
It is not accepted by the assemblers.
The same type information is now included on global variables:
0055 (/Users/rsc/x.go:15) GLOBL slice+0(SB){[]string},$24(AL*0)
This more accurate type information fixes a bug in the
garbage collector's precise heap collection.
The linker only cares about globals right now, but having the
local information should make things a little nicer for Carl
in the future.
Fixes #4907.
R=ken2
CC=golang-dev
https://golang.org/cl/7395056
2013-02-25 10:13:47 -07:00
|
|
|
continue;
|
|
|
|
|
2014-02-15 08:58:55 -07:00
|
|
|
if (p->from.node)
|
2012-02-20 22:38:01 -07:00
|
|
|
p->from.node->used = 1;
|
2011-06-14 09:03:37 -06:00
|
|
|
|
2014-02-15 08:58:55 -07:00
|
|
|
if (p->to.node)
|
2012-02-20 22:38:01 -07:00
|
|
|
p->to.node->used = 1;
|
2011-06-14 09:03:37 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-28 13:06:25 -06:00
|
|
|
// Fixup instructions after allocauto (formerly compactframe) has moved all autos around.
|
2011-06-14 09:03:37 -06:00
|
|
|
void
|
|
|
|
fixautoused(Prog* p)
|
|
|
|
{
|
cmd/gc: emit explicit type information for local variables
The type information is (and for years has been) included
as an extra field in the address chunk of an instruction.
Unfortunately, suppose there is a string at a+24(FP) and
we have an instruction reading its length. It will say:
MOVQ x+32(FP), AX
and the type of *that* argument is int (not slice), because
it is the length being read. This confuses the picture seen
by debuggers and now, worse, by the garbage collector.
Instead of attaching the type information to all uses,
emit an explicit list of TYPE instructions with the information.
The TYPE instructions are no-ops whose only role is to
provide an address to attach type information to.
For example, this function:
func f(x, y, z int) (a, b string) {
return
}
now compiles into:
--- prog list "f" ---
0000 (/Users/rsc/x.go:3) TEXT f+0(SB),$0-56
0001 (/Users/rsc/x.go:3) LOCALS ,
0002 (/Users/rsc/x.go:3) TYPE x+0(FP){int},$8
0003 (/Users/rsc/x.go:3) TYPE y+8(FP){int},$8
0004 (/Users/rsc/x.go:3) TYPE z+16(FP){int},$8
0005 (/Users/rsc/x.go:3) TYPE a+24(FP){string},$16
0006 (/Users/rsc/x.go:3) TYPE b+40(FP){string},$16
0007 (/Users/rsc/x.go:3) MOVQ $0,b+40(FP)
0008 (/Users/rsc/x.go:3) MOVQ $0,b+48(FP)
0009 (/Users/rsc/x.go:3) MOVQ $0,a+24(FP)
0010 (/Users/rsc/x.go:3) MOVQ $0,a+32(FP)
0011 (/Users/rsc/x.go:4) RET ,
The { } show the formerly hidden type information.
The { } syntax is used when printing from within the gc compiler.
It is not accepted by the assemblers.
The same type information is now included on global variables:
0055 (/Users/rsc/x.go:15) GLOBL slice+0(SB){[]string},$24(AL*0)
This more accurate type information fixes a bug in the
garbage collector's precise heap collection.
The linker only cares about globals right now, but having the
local information should make things a little nicer for Carl
in the future.
Fixes #4907.
R=ken2
CC=golang-dev
https://golang.org/cl/7395056
2013-02-25 10:13:47 -07:00
|
|
|
Prog **lp;
|
|
|
|
|
|
|
|
for (lp=&p; (p=*lp) != P; ) {
|
|
|
|
if (p->as == ATYPE && p->from.node && p->from.type == D_AUTO && !p->from.node->used) {
|
|
|
|
*lp = p->link;
|
|
|
|
continue;
|
|
|
|
}
|
cmd/gc: shorten temporary lifetimes when possible
The new channel and map runtime routines take pointers
to values, typically temporaries. Without help, the compiler
cannot tell when those temporaries stop being needed,
because it isn't sure what happened to the pointer.
Arrange to insert explicit VARKILL instructions for these
temporaries so that the liveness analysis can avoid seeing
them as "ambiguously live".
The change is made in order.c, which was already in charge of
introducing temporaries to preserve the order-of-evaluation
guarantees. Now its job has expanded to include introducing
temporaries as needed by runtime routines, and then also
inserting the VARKILL annotations for all these temporaries,
so that their lifetimes can be shortened.
In order to do its job for the map runtime routines, order.c arranges
that all map lookups or map assignments have the form:
x = m[k]
x, y = m[k]
m[k] = x
where x, y, and k are simple variables (often temporaries).
Likewise, receiving from a channel is now always:
x = <-c
In order to provide the map guarantee, order.c is responsible for
rewriting x op= y into x = x op y, so that m[k] += z becomes
t = m[k]
t2 = t + z
m[k] = t2
While here, fix a few bugs in order.c's traversal: it was failing to
walk into select and switch case bodies, so order of evaluation
guarantees were not preserved in those situations.
Added tests to test/reorder2.go.
Fixes #7671.
In gc/popt's temporary-merging optimization, allow merging
of temporaries with their address taken as long as the liveness
ranges do not intersect. (There is a good chance of that now
that we have VARKILL annotations to limit the liveness range.)
Explicitly killing temporaries cuts the number of ambiguously
live temporaries that must be zeroed in the godoc binary from
860 to 711, or -17%. There is more work to be done, but this
is a good checkpoint.
Update #7345
LGTM=khr
R=khr
CC=golang-codereviews
https://golang.org/cl/81940043
2014-04-01 11:31:38 -06:00
|
|
|
if ((p->as == AVARDEF || p->as == AVARKILL) && p->to.node && !p->to.node->used) {
|
2014-02-15 08:58:55 -07:00
|
|
|
// Cannot remove VARDEF instruction, because - unlike TYPE handled above -
|
|
|
|
// VARDEFs are interspersed with other code, and a jump might be using the
|
|
|
|
// VARDEF as a target. Replace with a no-op instead. A later pass will remove
|
|
|
|
// the no-ops.
|
|
|
|
p->to.type = D_NONE;
|
|
|
|
p->to.node = N;
|
|
|
|
p->as = ANOP;
|
|
|
|
continue;
|
|
|
|
}
|
cmd/gc: emit explicit type information for local variables
The type information is (and for years has been) included
as an extra field in the address chunk of an instruction.
Unfortunately, suppose there is a string at a+24(FP) and
we have an instruction reading its length. It will say:
MOVQ x+32(FP), AX
and the type of *that* argument is int (not slice), because
it is the length being read. This confuses the picture seen
by debuggers and now, worse, by the garbage collector.
Instead of attaching the type information to all uses,
emit an explicit list of TYPE instructions with the information.
The TYPE instructions are no-ops whose only role is to
provide an address to attach type information to.
For example, this function:
func f(x, y, z int) (a, b string) {
return
}
now compiles into:
--- prog list "f" ---
0000 (/Users/rsc/x.go:3) TEXT f+0(SB),$0-56
0001 (/Users/rsc/x.go:3) LOCALS ,
0002 (/Users/rsc/x.go:3) TYPE x+0(FP){int},$8
0003 (/Users/rsc/x.go:3) TYPE y+8(FP){int},$8
0004 (/Users/rsc/x.go:3) TYPE z+16(FP){int},$8
0005 (/Users/rsc/x.go:3) TYPE a+24(FP){string},$16
0006 (/Users/rsc/x.go:3) TYPE b+40(FP){string},$16
0007 (/Users/rsc/x.go:3) MOVQ $0,b+40(FP)
0008 (/Users/rsc/x.go:3) MOVQ $0,b+48(FP)
0009 (/Users/rsc/x.go:3) MOVQ $0,a+24(FP)
0010 (/Users/rsc/x.go:3) MOVQ $0,a+32(FP)
0011 (/Users/rsc/x.go:4) RET ,
The { } show the formerly hidden type information.
The { } syntax is used when printing from within the gc compiler.
It is not accepted by the assemblers.
The same type information is now included on global variables:
0055 (/Users/rsc/x.go:15) GLOBL slice+0(SB){[]string},$24(AL*0)
This more accurate type information fixes a bug in the
garbage collector's precise heap collection.
The linker only cares about globals right now, but having the
local information should make things a little nicer for Carl
in the future.
Fixes #4907.
R=ken2
CC=golang-dev
https://golang.org/cl/7395056
2013-02-25 10:13:47 -07:00
|
|
|
|
2011-06-14 09:03:37 -06:00
|
|
|
if (p->from.type == D_AUTO && p->from.node)
|
|
|
|
p->from.offset += p->from.node->stkdelta;
|
|
|
|
|
|
|
|
if (p->to.type == D_AUTO && p->to.node)
|
|
|
|
p->to.offset += p->to.node->stkdelta;
|
cmd/gc: emit explicit type information for local variables
The type information is (and for years has been) included
as an extra field in the address chunk of an instruction.
Unfortunately, suppose there is a string at a+24(FP) and
we have an instruction reading its length. It will say:
MOVQ x+32(FP), AX
and the type of *that* argument is int (not slice), because
it is the length being read. This confuses the picture seen
by debuggers and now, worse, by the garbage collector.
Instead of attaching the type information to all uses,
emit an explicit list of TYPE instructions with the information.
The TYPE instructions are no-ops whose only role is to
provide an address to attach type information to.
For example, this function:
func f(x, y, z int) (a, b string) {
return
}
now compiles into:
--- prog list "f" ---
0000 (/Users/rsc/x.go:3) TEXT f+0(SB),$0-56
0001 (/Users/rsc/x.go:3) LOCALS ,
0002 (/Users/rsc/x.go:3) TYPE x+0(FP){int},$8
0003 (/Users/rsc/x.go:3) TYPE y+8(FP){int},$8
0004 (/Users/rsc/x.go:3) TYPE z+16(FP){int},$8
0005 (/Users/rsc/x.go:3) TYPE a+24(FP){string},$16
0006 (/Users/rsc/x.go:3) TYPE b+40(FP){string},$16
0007 (/Users/rsc/x.go:3) MOVQ $0,b+40(FP)
0008 (/Users/rsc/x.go:3) MOVQ $0,b+48(FP)
0009 (/Users/rsc/x.go:3) MOVQ $0,a+24(FP)
0010 (/Users/rsc/x.go:3) MOVQ $0,a+32(FP)
0011 (/Users/rsc/x.go:4) RET ,
The { } show the formerly hidden type information.
The { } syntax is used when printing from within the gc compiler.
It is not accepted by the assemblers.
The same type information is now included on global variables:
0055 (/Users/rsc/x.go:15) GLOBL slice+0(SB){[]string},$24(AL*0)
This more accurate type information fixes a bug in the
garbage collector's precise heap collection.
The linker only cares about globals right now, but having the
local information should make things a little nicer for Carl
in the future.
Fixes #4907.
R=ken2
CC=golang-dev
https://golang.org/cl/7395056
2013-02-25 10:13:47 -07:00
|
|
|
|
|
|
|
lp = &p->link;
|
2011-06-14 09:03:37 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-03-31 01:22:59 -06:00
|
|
|
void
|
|
|
|
clearfat(Node *nl)
|
|
|
|
{
|
2009-05-26 17:23:54 -06:00
|
|
|
uint32 w, c, q;
|
|
|
|
Node n1;
|
2014-04-01 13:51:02 -06:00
|
|
|
Prog *p;
|
2009-05-26 17:23:54 -06:00
|
|
|
|
|
|
|
/* clear a fat object */
|
|
|
|
if(debug['g'])
|
|
|
|
dump("\nclearfat", nl);
|
|
|
|
|
|
|
|
w = nl->type->width;
|
2012-11-01 07:36:08 -06:00
|
|
|
// Avoid taking the address for simple enough types.
|
|
|
|
if(componentgen(N, nl))
|
|
|
|
return;
|
2012-09-09 12:30:08 -06:00
|
|
|
|
2009-05-26 17:23:54 -06:00
|
|
|
c = w % 4; // bytes
|
|
|
|
q = w / 4; // quads
|
|
|
|
|
|
|
|
nodreg(&n1, types[tptr], D_DI);
|
|
|
|
agen(nl, &n1);
|
2013-07-17 03:04:34 -06:00
|
|
|
gconreg(AMOVL, 0, D_AX);
|
2009-05-26 17:23:54 -06:00
|
|
|
|
2014-04-01 13:51:02 -06:00
|
|
|
if(q > 128) {
|
2009-05-26 17:23:54 -06:00
|
|
|
gconreg(AMOVL, q, D_CX);
|
|
|
|
gins(AREP, N, N); // repeat
|
|
|
|
gins(ASTOSL, N, N); // STOL AL,*(DI)+
|
2014-04-01 13:51:02 -06:00
|
|
|
} else if(q >= 4) {
|
|
|
|
p = gins(ADUFFZERO, N, N);
|
|
|
|
p->to.type = D_ADDR;
|
|
|
|
p->to.sym = linksym(pkglookup("duffzero", runtimepkg));
|
|
|
|
// 1 and 128 = magic constants: see ../../pkg/runtime/asm_386.s
|
|
|
|
p->to.offset = 1*(128-q);
|
2009-05-26 17:23:54 -06:00
|
|
|
} else
|
|
|
|
while(q > 0) {
|
|
|
|
gins(ASTOSL, N, N); // STOL AL,*(DI)+
|
|
|
|
q--;
|
|
|
|
}
|
|
|
|
|
|
|
|
while(c > 0) {
|
|
|
|
gins(ASTOSB, N, N); // STOB AL,*(DI)+
|
|
|
|
c--;
|
|
|
|
}
|
2009-03-31 01:22:59 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* generate:
|
|
|
|
* call f
|
2013-02-21 15:01:13 -07:00
|
|
|
* proc=-1 normal call but no return
|
2009-03-31 01:22:59 -06:00
|
|
|
* proc=0 normal call
|
|
|
|
* proc=1 goroutine run in new proc
|
|
|
|
* proc=2 defer call save away stack
|
2013-02-21 15:01:13 -07:00
|
|
|
* proc=3 normal call to C pointer (not Go func value)
|
2009-03-31 01:22:59 -06:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
ginscall(Node *f, int proc)
|
|
|
|
{
|
2013-07-16 14:25:10 -06:00
|
|
|
int32 arg;
|
2009-03-31 01:22:59 -06:00
|
|
|
Prog *p;
|
2013-02-21 15:01:13 -07:00
|
|
|
Node reg, r1, con;
|
2009-03-31 01:22:59 -06:00
|
|
|
|
2013-07-16 14:25:10 -06:00
|
|
|
if(f->type != T)
|
|
|
|
setmaxarg(f->type);
|
|
|
|
|
|
|
|
arg = -1;
|
2013-07-31 10:00:33 -06:00
|
|
|
// Most functions have a fixed-size argument block, so traceback uses that during unwind.
|
|
|
|
// Not all, though: there are some variadic functions in package runtime,
|
|
|
|
// and for those we emit call-specific metadata recorded by caller.
|
|
|
|
// Reflect generates functions with variable argsize (see reflect.methodValueCall/makeFuncStub),
|
|
|
|
// so we do this for all indirect calls as well.
|
|
|
|
if(f->type != T && (f->sym == S || (f->sym != S && f->sym->pkg == runtimepkg) || proc == 1 || proc == 2)) {
|
2013-07-16 14:25:10 -06:00
|
|
|
arg = f->type->argwid;
|
|
|
|
if(proc == 1 || proc == 2)
|
|
|
|
arg += 2*widthptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(arg != -1)
|
|
|
|
gargsize(arg);
|
|
|
|
|
2009-03-31 01:22:59 -06:00
|
|
|
switch(proc) {
|
|
|
|
default:
|
|
|
|
fatal("ginscall: bad proc %d", proc);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0: // normal call
|
2012-05-30 16:07:39 -06:00
|
|
|
case -1: // normal call but no return
|
2013-02-21 15:01:13 -07:00
|
|
|
if(f->op == ONAME && f->class == PFUNC) {
|
cmd/5g, cmd/6g, cmd/8g: fix line number of caller of deferred func
Deferred functions are not run by a call instruction. They are run by
the runtime editing registers to make the call start with a caller PC
returning to a
CALL deferreturn
instruction.
That instruction has always had the line number of the function's
closing brace, but that instruction's line number is irrelevant.
Stack traces show the line number of the instruction before the
return PC, because normally that's what started the call. Not so here.
The instruction before the CALL deferreturn could be almost anywhere
in the function; it's unrelated and its line number is incorrect to show.
Fix the line number by inserting a true hardware no-op with the right
line number before the returned-to CALL instruction. That is, the deferred
calls now appear to start with a caller PC returning to the second instruction
in this sequence:
NOP
CALL deferreturn
The traceback will show the line number of the NOP, which we've set
to be the line number of the function's closing brace.
The NOP here is not the usual pseudo-instruction, which would be
elided by the linker. Instead it is the real hardware instruction:
XCHG AX, AX on 386 and amd64, and AND.EQ R0, R0, R0 on ARM.
Fixes #5856.
R=ken2, ken
CC=golang-dev
https://golang.org/cl/11223043
2013-07-12 11:47:55 -06:00
|
|
|
if(f == deferreturn) {
|
|
|
|
// Deferred calls will appear to be returning to
|
|
|
|
// the CALL deferreturn(SB) that we are about to emit.
|
|
|
|
// However, the stack trace code will show the line
|
|
|
|
// of the instruction byte before the return PC.
|
|
|
|
// To avoid that being an unrelated instruction,
|
|
|
|
// insert an x86 NOP that we will have the right line number.
|
|
|
|
// x86 NOP 0x90 is really XCHG AX, AX; use that description
|
|
|
|
// because the NOP pseudo-instruction will be removed by
|
|
|
|
// the linker.
|
|
|
|
nodreg(®, types[TINT], D_AX);
|
|
|
|
gins(AXCHGL, ®, ®);
|
|
|
|
}
|
2013-02-21 15:01:13 -07:00
|
|
|
p = gins(ACALL, N, f);
|
|
|
|
afunclit(&p->to, f);
|
|
|
|
if(proc == -1 || noreturn(p))
|
|
|
|
gins(AUNDEF, N, N);
|
|
|
|
break;
|
|
|
|
}
|
2013-02-22 08:47:54 -07:00
|
|
|
nodreg(®, types[tptr], D_DX);
|
2013-02-21 15:01:13 -07:00
|
|
|
nodreg(&r1, types[tptr], D_BX);
|
|
|
|
gmove(f, ®);
|
|
|
|
reg.op = OINDREG;
|
|
|
|
gmove(®, &r1);
|
2013-02-22 12:25:50 -07:00
|
|
|
reg.op = OREGISTER;
|
|
|
|
gins(ACALL, ®, &r1);
|
2013-02-21 15:01:13 -07:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 3: // normal call of c function pointer
|
|
|
|
gins(ACALL, N, f);
|
2009-03-31 01:22:59 -06:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 1: // call in new proc (go)
|
2010-03-31 12:46:01 -06:00
|
|
|
case 2: // deferred call (defer)
|
|
|
|
nodreg(®, types[TINT32], D_CX);
|
2009-03-31 01:22:59 -06:00
|
|
|
gins(APUSHL, f, N);
|
|
|
|
nodconst(&con, types[TINT32], argsize(f->type));
|
|
|
|
gins(APUSHL, &con, N);
|
|
|
|
if(proc == 1)
|
|
|
|
ginscall(newproc, 0);
|
|
|
|
else
|
|
|
|
ginscall(deferproc, 0);
|
|
|
|
gins(APOPL, N, ®);
|
|
|
|
gins(APOPL, N, ®);
|
2010-03-31 12:46:01 -06:00
|
|
|
if(proc == 2) {
|
|
|
|
nodreg(®, types[TINT64], D_AX);
|
|
|
|
gins(ATESTL, ®, ®);
|
2012-05-30 16:07:39 -06:00
|
|
|
patch(gbranch(AJNE, T, -1), retpc);
|
2010-03-31 12:46:01 -06:00
|
|
|
}
|
2009-03-31 01:22:59 -06:00
|
|
|
break;
|
|
|
|
}
|
2013-07-16 14:25:10 -06:00
|
|
|
|
|
|
|
if(arg != -1)
|
|
|
|
gargsize(-1);
|
2009-03-31 01:22:59 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* n is call to interface method.
|
|
|
|
* generate res = n.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
cgen_callinter(Node *n, Node *res, int proc)
|
|
|
|
{
|
2009-05-26 22:07:26 -06:00
|
|
|
Node *i, *f;
|
2012-09-11 00:45:23 -06:00
|
|
|
Node tmpi, nodi, nodo, nodr, nodsp;
|
2009-05-26 22:07:26 -06:00
|
|
|
|
|
|
|
i = n->left;
|
|
|
|
if(i->op != ODOTINTER)
|
|
|
|
fatal("cgen_callinter: not ODOTINTER %O", i->op);
|
|
|
|
|
|
|
|
f = i->right; // field
|
|
|
|
if(f->op != ONAME)
|
|
|
|
fatal("cgen_callinter: not ONAME %O", f->op);
|
|
|
|
|
|
|
|
i = i->left; // interface
|
|
|
|
|
|
|
|
if(!i->addable) {
|
2009-12-02 19:31:29 -07:00
|
|
|
tempname(&tmpi, i->type);
|
2009-05-26 22:07:26 -06:00
|
|
|
cgen(i, &tmpi);
|
|
|
|
i = &tmpi;
|
|
|
|
}
|
|
|
|
|
2009-07-17 02:00:44 -06:00
|
|
|
genlist(n->list); // assign the args
|
2009-05-26 22:07:26 -06:00
|
|
|
|
2012-09-11 00:45:23 -06:00
|
|
|
// i is now addable, prepare an indirected
|
|
|
|
// register to hold its address.
|
|
|
|
igen(i, &nodi, res); // REG = &inter
|
2009-05-26 22:07:26 -06:00
|
|
|
|
|
|
|
nodindreg(&nodsp, types[tptr], D_SP);
|
2012-09-11 00:45:23 -06:00
|
|
|
nodi.type = types[tptr];
|
|
|
|
nodi.xoffset += widthptr;
|
|
|
|
cgen(&nodi, &nodsp); // 0(SP) = 4(REG) -- i.data
|
2009-05-26 22:07:26 -06:00
|
|
|
|
2012-09-11 00:45:23 -06:00
|
|
|
regalloc(&nodo, types[tptr], res);
|
|
|
|
nodi.type = types[tptr];
|
|
|
|
nodi.xoffset -= widthptr;
|
|
|
|
cgen(&nodi, &nodo); // REG = 0(REG) -- i.tab
|
|
|
|
regfree(&nodi);
|
2009-05-26 22:07:26 -06:00
|
|
|
|
2012-09-11 00:45:23 -06:00
|
|
|
regalloc(&nodr, types[tptr], &nodo);
|
2010-02-18 19:31:13 -07:00
|
|
|
if(n->left->xoffset == BADWIDTH)
|
|
|
|
fatal("cgen_callinter: badwidth");
|
2013-08-15 12:38:32 -06:00
|
|
|
cgen_checknil(&nodo);
|
2012-09-11 00:45:23 -06:00
|
|
|
nodo.op = OINDREG;
|
2009-06-04 16:24:01 -06:00
|
|
|
nodo.xoffset = n->left->xoffset + 3*widthptr + 8;
|
2013-02-21 15:01:13 -07:00
|
|
|
|
|
|
|
if(proc == 0) {
|
|
|
|
// plain call: use direct c function pointer - more efficient
|
|
|
|
cgen(&nodo, &nodr); // REG = 20+offset(REG) -- i.tab->fun[f]
|
|
|
|
proc = 3;
|
|
|
|
} else {
|
|
|
|
// go/defer. generate go func value.
|
|
|
|
gins(ALEAL, &nodo, &nodr); // REG = &(20+offset(REG)) -- i.tab->fun[f]
|
|
|
|
}
|
2009-05-26 22:07:26 -06:00
|
|
|
|
|
|
|
nodr.type = n->left->type;
|
|
|
|
ginscall(&nodr, proc);
|
|
|
|
|
|
|
|
regfree(&nodr);
|
|
|
|
regfree(&nodo);
|
2009-03-31 01:22:59 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* generate function call;
|
|
|
|
* proc=0 normal call
|
|
|
|
* proc=1 goroutine run in new proc
|
|
|
|
* proc=2 defer call save away stack
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
cgen_call(Node *n, int proc)
|
|
|
|
{
|
2009-04-02 17:48:06 -06:00
|
|
|
Type *t;
|
|
|
|
Node nod, afun;
|
|
|
|
|
|
|
|
if(n == N)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(n->left->ullman >= UINF) {
|
|
|
|
// if name involves a fn call
|
|
|
|
// precompute the address of the fn
|
2009-12-02 19:31:29 -07:00
|
|
|
tempname(&afun, types[tptr]);
|
2009-04-02 17:48:06 -06:00
|
|
|
cgen(n->left, &afun);
|
|
|
|
}
|
|
|
|
|
2009-07-17 02:00:44 -06:00
|
|
|
genlist(n->list); // assign the args
|
2009-04-02 17:48:06 -06:00
|
|
|
t = n->left->type;
|
|
|
|
|
|
|
|
// call tempname pointer
|
|
|
|
if(n->left->ullman >= UINF) {
|
|
|
|
regalloc(&nod, types[tptr], N);
|
|
|
|
cgen_as(&nod, &afun);
|
|
|
|
nod.type = t;
|
|
|
|
ginscall(&nod, proc);
|
|
|
|
regfree(&nod);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// call pointer
|
|
|
|
if(n->left->op != ONAME || n->left->class != PFUNC) {
|
|
|
|
regalloc(&nod, types[tptr], N);
|
|
|
|
cgen_as(&nod, n->left);
|
|
|
|
nod.type = t;
|
|
|
|
ginscall(&nod, proc);
|
|
|
|
regfree(&nod);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// call direct
|
|
|
|
n->left->method = 1;
|
|
|
|
ginscall(n->left, proc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* call to n has already been generated.
|
|
|
|
* generate:
|
|
|
|
* res = return value from call.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
cgen_callret(Node *n, Node *res)
|
|
|
|
{
|
|
|
|
Node nod;
|
|
|
|
Type *fp, *t;
|
|
|
|
Iter flist;
|
|
|
|
|
|
|
|
t = n->left->type;
|
|
|
|
if(t->etype == TPTR32 || t->etype == TPTR64)
|
|
|
|
t = t->type;
|
|
|
|
|
|
|
|
fp = structfirst(&flist, getoutarg(t));
|
|
|
|
if(fp == T)
|
|
|
|
fatal("cgen_callret: nil");
|
|
|
|
|
|
|
|
memset(&nod, 0, sizeof(nod));
|
|
|
|
nod.op = OINDREG;
|
|
|
|
nod.val.u.reg = D_SP;
|
|
|
|
nod.addable = 1;
|
|
|
|
|
|
|
|
nod.xoffset = fp->width;
|
|
|
|
nod.type = fp->type;
|
|
|
|
cgen_as(res, &nod);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* call to n has already been generated.
|
|
|
|
* generate:
|
|
|
|
* res = &return value from call.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
cgen_aret(Node *n, Node *res)
|
|
|
|
{
|
|
|
|
Node nod1, nod2;
|
|
|
|
Type *fp, *t;
|
|
|
|
Iter flist;
|
|
|
|
|
|
|
|
t = n->left->type;
|
|
|
|
if(isptr[t->etype])
|
|
|
|
t = t->type;
|
|
|
|
|
|
|
|
fp = structfirst(&flist, getoutarg(t));
|
|
|
|
if(fp == T)
|
|
|
|
fatal("cgen_aret: nil");
|
|
|
|
|
|
|
|
memset(&nod1, 0, sizeof(nod1));
|
|
|
|
nod1.op = OINDREG;
|
|
|
|
nod1.val.u.reg = D_SP;
|
|
|
|
nod1.addable = 1;
|
|
|
|
|
|
|
|
nod1.xoffset = fp->width;
|
|
|
|
nod1.type = fp->type;
|
|
|
|
|
|
|
|
if(res->op != OREGISTER) {
|
|
|
|
regalloc(&nod2, types[tptr], res);
|
|
|
|
gins(ALEAL, &nod1, &nod2);
|
|
|
|
gins(AMOVL, &nod2, res);
|
|
|
|
regfree(&nod2);
|
|
|
|
} else
|
|
|
|
gins(ALEAL, &nod1, res);
|
2009-03-31 01:22:59 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* generate return.
|
|
|
|
* n->left is assignments to return values.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
cgen_ret(Node *n)
|
|
|
|
{
|
2013-06-11 07:41:49 -06:00
|
|
|
Prog *p;
|
|
|
|
|
2009-07-17 02:00:44 -06:00
|
|
|
genlist(n->list); // copy out args
|
2013-06-11 07:41:49 -06:00
|
|
|
if(retpc) {
|
2011-06-02 10:48:17 -06:00
|
|
|
gjmp(retpc);
|
2013-06-11 07:41:49 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
p = gins(ARET, N, N);
|
|
|
|
if(n->op == ORETJMP) {
|
|
|
|
p->to.type = D_EXTERN;
|
2013-12-08 20:51:55 -07:00
|
|
|
p->to.sym = linksym(n->left->sym);
|
2013-06-11 07:41:49 -06:00
|
|
|
}
|
2009-03-31 01:22:59 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* generate += *= etc.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
cgen_asop(Node *n)
|
|
|
|
{
|
2009-05-26 17:23:54 -06:00
|
|
|
Node n1, n2, n3, n4;
|
|
|
|
Node *nl, *nr;
|
|
|
|
Prog *p1;
|
|
|
|
Addr addr;
|
|
|
|
int a;
|
|
|
|
|
|
|
|
nl = n->left;
|
|
|
|
nr = n->right;
|
|
|
|
|
|
|
|
if(nr->ullman >= UINF && nl->ullman >= UINF) {
|
2009-12-02 19:31:29 -07:00
|
|
|
tempname(&n1, nr->type);
|
2009-05-26 17:23:54 -06:00
|
|
|
cgen(nr, &n1);
|
|
|
|
n2 = *n;
|
|
|
|
n2.right = &n1;
|
|
|
|
cgen_asop(&n2);
|
|
|
|
goto ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!isint[nl->type->etype])
|
|
|
|
goto hard;
|
|
|
|
if(!isint[nr->type->etype])
|
|
|
|
goto hard;
|
2009-05-26 22:07:26 -06:00
|
|
|
if(is64(nl->type) || is64(nr->type))
|
|
|
|
goto hard;
|
2009-05-26 17:23:54 -06:00
|
|
|
|
|
|
|
switch(n->etype) {
|
|
|
|
case OADD:
|
|
|
|
if(smallintconst(nr))
|
|
|
|
if(mpgetfix(nr->val.u.xval) == 1) {
|
|
|
|
a = optoas(OINC, nl->type);
|
|
|
|
if(nl->addable) {
|
|
|
|
gins(a, N, nl);
|
|
|
|
goto ret;
|
|
|
|
}
|
|
|
|
if(sudoaddable(a, nl, &addr)) {
|
|
|
|
p1 = gins(a, N, N);
|
|
|
|
p1->to = addr;
|
|
|
|
sudoclean();
|
|
|
|
goto ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OSUB:
|
|
|
|
if(smallintconst(nr))
|
|
|
|
if(mpgetfix(nr->val.u.xval) == 1) {
|
|
|
|
a = optoas(ODEC, nl->type);
|
|
|
|
if(nl->addable) {
|
|
|
|
gins(a, N, nl);
|
|
|
|
goto ret;
|
|
|
|
}
|
|
|
|
if(sudoaddable(a, nl, &addr)) {
|
|
|
|
p1 = gins(a, N, N);
|
|
|
|
p1->to = addr;
|
|
|
|
sudoclean();
|
|
|
|
goto ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(n->etype) {
|
|
|
|
case OADD:
|
|
|
|
case OSUB:
|
|
|
|
case OXOR:
|
|
|
|
case OAND:
|
|
|
|
case OOR:
|
|
|
|
a = optoas(n->etype, nl->type);
|
|
|
|
if(nl->addable) {
|
|
|
|
if(smallintconst(nr)) {
|
|
|
|
gins(a, nr, nl);
|
|
|
|
goto ret;
|
|
|
|
}
|
|
|
|
regalloc(&n2, nr->type, N);
|
|
|
|
cgen(nr, &n2);
|
|
|
|
gins(a, &n2, nl);
|
|
|
|
regfree(&n2);
|
|
|
|
goto ret;
|
|
|
|
}
|
|
|
|
if(nr->ullman < UINF)
|
|
|
|
if(sudoaddable(a, nl, &addr)) {
|
|
|
|
if(smallintconst(nr)) {
|
|
|
|
p1 = gins(a, nr, N);
|
|
|
|
p1->to = addr;
|
|
|
|
sudoclean();
|
|
|
|
goto ret;
|
|
|
|
}
|
|
|
|
regalloc(&n2, nr->type, N);
|
|
|
|
cgen(nr, &n2);
|
|
|
|
p1 = gins(a, &n2, N);
|
|
|
|
p1->to = addr;
|
|
|
|
regfree(&n2);
|
|
|
|
sudoclean();
|
|
|
|
goto ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hard:
|
2010-03-05 16:35:09 -07:00
|
|
|
n2.op = 0;
|
|
|
|
n1.op = 0;
|
|
|
|
if(nr->ullman >= nl->ullman || nl->addable) {
|
|
|
|
mgen(nr, &n2, N);
|
|
|
|
nr = &n2;
|
|
|
|
} else {
|
|
|
|
tempname(&n2, nr->type);
|
|
|
|
cgen(nr, &n2);
|
|
|
|
nr = &n2;
|
|
|
|
}
|
|
|
|
if(!nl->addable) {
|
|
|
|
igen(nl, &n1, N);
|
|
|
|
nl = &n1;
|
|
|
|
}
|
2009-05-26 17:23:54 -06:00
|
|
|
|
|
|
|
n3 = *n;
|
2010-03-05 16:35:09 -07:00
|
|
|
n3.left = nl;
|
|
|
|
n3.right = nr;
|
2009-05-26 17:23:54 -06:00
|
|
|
n3.op = n->etype;
|
|
|
|
|
2010-03-05 16:35:09 -07:00
|
|
|
mgen(&n3, &n4, N);
|
|
|
|
gmove(&n4, nl);
|
2009-05-26 17:23:54 -06:00
|
|
|
|
2010-03-05 16:35:09 -07:00
|
|
|
if(n1.op)
|
|
|
|
regfree(&n1);
|
|
|
|
mfree(&n2);
|
|
|
|
mfree(&n4);
|
2009-05-26 17:23:54 -06:00
|
|
|
|
|
|
|
ret:
|
|
|
|
;
|
2009-03-31 01:22:59 -06:00
|
|
|
}
|
|
|
|
|
2009-08-07 13:57:44 -06:00
|
|
|
int
|
|
|
|
samereg(Node *a, Node *b)
|
|
|
|
{
|
|
|
|
if(a->op != OREGISTER)
|
|
|
|
return 0;
|
|
|
|
if(b->op != OREGISTER)
|
|
|
|
return 0;
|
|
|
|
if(a->val.u.reg != b->val.u.reg)
|
|
|
|
return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2009-05-26 22:07:26 -06:00
|
|
|
/*
|
|
|
|
* generate division.
|
|
|
|
* caller must set:
|
|
|
|
* ax = allocated AX register
|
|
|
|
* dx = allocated DX register
|
|
|
|
* generates one of:
|
|
|
|
* res = nl / nr
|
|
|
|
* res = nl % nr
|
|
|
|
* according to op.
|
|
|
|
*/
|
|
|
|
void
|
2011-07-28 12:18:22 -06:00
|
|
|
dodiv(int op, Node *nl, Node *nr, Node *res, Node *ax, Node *dx)
|
2009-05-26 22:07:26 -06:00
|
|
|
{
|
2011-07-28 12:18:22 -06:00
|
|
|
int check;
|
2011-08-30 06:47:28 -06:00
|
|
|
Node n1, t1, t2, t3, t4, n4, nz;
|
|
|
|
Type *t, *t0;
|
2012-12-12 00:35:08 -07:00
|
|
|
Prog *p1, *p2;
|
2011-07-28 12:18:22 -06:00
|
|
|
|
|
|
|
// Have to be careful about handling
|
|
|
|
// most negative int divided by -1 correctly.
|
|
|
|
// The hardware will trap.
|
|
|
|
// Also the byte divide instruction needs AH,
|
|
|
|
// which we otherwise don't have to deal with.
|
|
|
|
// Easiest way to avoid for int8, int16: use int32.
|
|
|
|
// For int32 and int64, use explicit test.
|
|
|
|
// Could use int64 hw for int32.
|
|
|
|
t = nl->type;
|
2011-08-30 06:47:28 -06:00
|
|
|
t0 = t;
|
2011-07-28 12:18:22 -06:00
|
|
|
check = 0;
|
|
|
|
if(issigned[t->etype]) {
|
|
|
|
check = 1;
|
|
|
|
if(isconst(nl, CTINT) && mpgetfix(nl->val.u.xval) != -1LL<<(t->width*8-1))
|
|
|
|
check = 0;
|
|
|
|
else if(isconst(nr, CTINT) && mpgetfix(nr->val.u.xval) != -1)
|
|
|
|
check = 0;
|
|
|
|
}
|
|
|
|
if(t->width < 4) {
|
|
|
|
if(issigned[t->etype])
|
|
|
|
t = types[TINT32];
|
|
|
|
else
|
|
|
|
t = types[TUINT32];
|
|
|
|
check = 0;
|
|
|
|
}
|
2009-05-26 22:07:26 -06:00
|
|
|
|
2011-07-28 12:18:22 -06:00
|
|
|
tempname(&t1, t);
|
|
|
|
tempname(&t2, t);
|
2011-08-30 06:47:28 -06:00
|
|
|
if(t0 != t) {
|
|
|
|
tempname(&t3, t0);
|
|
|
|
tempname(&t4, t0);
|
|
|
|
cgen(nl, &t3);
|
|
|
|
cgen(nr, &t4);
|
|
|
|
// Convert.
|
|
|
|
gmove(&t3, &t1);
|
|
|
|
gmove(&t4, &t2);
|
|
|
|
} else {
|
|
|
|
cgen(nl, &t1);
|
|
|
|
cgen(nr, &t2);
|
|
|
|
}
|
2009-05-26 22:07:26 -06:00
|
|
|
|
2009-08-12 14:18:27 -06:00
|
|
|
if(!samereg(ax, res) && !samereg(dx, res))
|
|
|
|
regalloc(&n1, t, res);
|
|
|
|
else
|
|
|
|
regalloc(&n1, t, N);
|
|
|
|
gmove(&t2, &n1);
|
|
|
|
gmove(&t1, ax);
|
2012-12-12 00:35:08 -07:00
|
|
|
p2 = P;
|
all: merge NaCl branch (part 1)
See golang.org/s/go13nacl for design overview.
This CL is the mostly mechanical changes from rsc's Go 1.2 based NaCl branch, specifically 39cb35750369 to 500771b477cf from https://code.google.com/r/rsc-go13nacl. This CL does not include working NaCl support, there are probably two or three more large merges to come.
CL 15750044 is not included as it involves more invasive changes to the linker which will need to be merged separately.
The exact change lists included are
15050047: syscall: support for Native Client
15360044: syscall: unzip implementation for Native Client
15370044: syscall: Native Client SRPC implementation
15400047: cmd/dist, cmd/go, go/build, test: support for Native Client
15410048: runtime: support for Native Client
15410049: syscall: file descriptor table for Native Client
15410050: syscall: in-memory file system for Native Client
15440048: all: update +build lines for Native Client port
15540045: cmd/6g, cmd/8g, cmd/gc: support for Native Client
15570045: os: support for Native Client
15680044: crypto/..., hash/crc32, reflect, sync/atomic: support for amd64p32
15690044: net: support for Native Client
15690048: runtime: support for fake time like on Go Playground
15690051: build: disable various tests on Native Client
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/68150047
2014-02-25 07:47:42 -07:00
|
|
|
if(nacl) {
|
|
|
|
// Native Client does not relay the divide-by-zero trap
|
|
|
|
// to the executing program, so we must insert a check
|
|
|
|
// for ourselves.
|
|
|
|
nodconst(&n4, t, 0);
|
|
|
|
gins(optoas(OCMP, t), &n1, &n4);
|
|
|
|
p1 = gbranch(optoas(ONE, t), T, +1);
|
|
|
|
if(panicdiv == N)
|
|
|
|
panicdiv = sysfunc("panicdivide");
|
|
|
|
ginscall(panicdiv, -1);
|
|
|
|
patch(p1, pc);
|
|
|
|
}
|
2011-07-28 12:18:22 -06:00
|
|
|
if(check) {
|
|
|
|
nodconst(&n4, t, -1);
|
|
|
|
gins(optoas(OCMP, t), &n1, &n4);
|
2012-05-30 16:07:39 -06:00
|
|
|
p1 = gbranch(optoas(ONE, t), T, +1);
|
2012-12-12 00:35:08 -07:00
|
|
|
if(op == ODIV) {
|
|
|
|
// a / (-1) is -a.
|
|
|
|
gins(optoas(OMINUS, t), N, ax);
|
|
|
|
gmove(ax, res);
|
|
|
|
} else {
|
|
|
|
// a % (-1) is 0.
|
2011-07-28 12:18:22 -06:00
|
|
|
nodconst(&n4, t, 0);
|
|
|
|
gmove(&n4, res);
|
|
|
|
}
|
2012-12-12 00:35:08 -07:00
|
|
|
p2 = gbranch(AJMP, T, 0);
|
2011-07-28 12:18:22 -06:00
|
|
|
patch(p1, pc);
|
|
|
|
}
|
2009-06-04 16:24:01 -06:00
|
|
|
if(!issigned[t->etype]) {
|
2009-08-12 14:18:27 -06:00
|
|
|
nodconst(&nz, t, 0);
|
|
|
|
gmove(&nz, dx);
|
2009-06-04 16:24:01 -06:00
|
|
|
} else
|
|
|
|
gins(optoas(OEXTEND, t), N, N);
|
2009-08-12 14:18:27 -06:00
|
|
|
gins(optoas(op, t), &n1, N);
|
|
|
|
regfree(&n1);
|
2009-05-26 22:07:26 -06:00
|
|
|
|
|
|
|
if(op == ODIV)
|
|
|
|
gmove(ax, res);
|
|
|
|
else
|
|
|
|
gmove(dx, res);
|
2011-07-28 12:18:22 -06:00
|
|
|
if(check)
|
2012-12-12 00:35:08 -07:00
|
|
|
patch(p2, pc);
|
2009-05-26 22:07:26 -06:00
|
|
|
}
|
|
|
|
|
2009-08-12 14:18:27 -06:00
|
|
|
static void
|
|
|
|
savex(int dr, Node *x, Node *oldx, Node *res, Type *t)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
|
|
|
|
r = reg[dr];
|
|
|
|
nodreg(x, types[TINT32], dr);
|
|
|
|
|
|
|
|
// save current ax and dx if they are live
|
|
|
|
// and not the destination
|
|
|
|
memset(oldx, 0, sizeof *oldx);
|
|
|
|
if(r > 0 && !samereg(x, res)) {
|
2009-12-02 19:31:29 -07:00
|
|
|
tempname(oldx, types[TINT32]);
|
2009-08-12 14:18:27 -06:00
|
|
|
gmove(x, oldx);
|
|
|
|
}
|
|
|
|
|
|
|
|
regalloc(x, t, x);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
restx(Node *x, Node *oldx)
|
|
|
|
{
|
|
|
|
regfree(x);
|
|
|
|
|
|
|
|
if(oldx->op != 0) {
|
|
|
|
x->type = types[TINT32];
|
|
|
|
gmove(oldx, x);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-04-02 17:48:06 -06:00
|
|
|
/*
|
|
|
|
* generate division according to op, one of:
|
|
|
|
* res = nl / nr
|
|
|
|
* res = nl % nr
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
cgen_div(int op, Node *nl, Node *nr, Node *res)
|
|
|
|
{
|
2009-08-12 14:18:27 -06:00
|
|
|
Node ax, dx, oldax, olddx;
|
2009-06-04 16:24:01 -06:00
|
|
|
Type *t;
|
2009-05-26 22:07:26 -06:00
|
|
|
|
|
|
|
if(is64(nl->type))
|
|
|
|
fatal("cgen_div %T", nl->type);
|
|
|
|
|
2011-07-28 12:18:22 -06:00
|
|
|
if(issigned[nl->type->etype])
|
|
|
|
t = types[TINT32];
|
|
|
|
else
|
|
|
|
t = types[TUINT32];
|
2009-08-12 14:18:27 -06:00
|
|
|
savex(D_AX, &ax, &oldax, res, t);
|
|
|
|
savex(D_DX, &dx, &olddx, res, t);
|
2011-07-28 12:18:22 -06:00
|
|
|
dodiv(op, nl, nr, res, &ax, &dx);
|
2009-08-12 14:18:27 -06:00
|
|
|
restx(&dx, &olddx);
|
|
|
|
restx(&ax, &oldax);
|
2009-04-02 17:48:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* generate shift according to op, one of:
|
|
|
|
* res = nl << nr
|
|
|
|
* res = nl >> nr
|
|
|
|
*/
|
|
|
|
void
|
2012-05-24 15:20:07 -06:00
|
|
|
cgen_shift(int op, int bounded, Node *nl, Node *nr, Node *res)
|
2009-04-02 17:48:06 -06:00
|
|
|
{
|
2011-07-28 16:22:12 -06:00
|
|
|
Node n1, n2, nt, cx, oldcx, hi, lo;
|
2009-06-04 16:24:01 -06:00
|
|
|
int a, w;
|
2011-07-28 16:22:12 -06:00
|
|
|
Prog *p1, *p2;
|
2009-06-04 16:24:01 -06:00
|
|
|
uvlong sc;
|
|
|
|
|
|
|
|
if(nl->type->width > 4)
|
2009-10-27 23:38:45 -06:00
|
|
|
fatal("cgen_shift %T", nl->type);
|
2009-06-04 16:24:01 -06:00
|
|
|
|
|
|
|
w = nl->type->width * 8;
|
|
|
|
|
|
|
|
a = optoas(op, nl->type);
|
|
|
|
|
|
|
|
if(nr->op == OLITERAL) {
|
2010-06-30 21:45:50 -06:00
|
|
|
tempname(&n2, nl->type);
|
|
|
|
cgen(nl, &n2);
|
2009-06-04 16:24:01 -06:00
|
|
|
regalloc(&n1, nl->type, res);
|
2010-06-30 21:45:50 -06:00
|
|
|
gmove(&n2, &n1);
|
2009-06-04 16:24:01 -06:00
|
|
|
sc = mpgetfix(nr->val.u.xval);
|
|
|
|
if(sc >= nl->type->width*8) {
|
2012-05-24 15:20:07 -06:00
|
|
|
// large shift gets 2 shifts by width-1
|
2009-06-04 16:24:01 -06:00
|
|
|
gins(a, ncon(w-1), &n1);
|
|
|
|
gins(a, ncon(w-1), &n1);
|
|
|
|
} else
|
|
|
|
gins(a, nr, &n1);
|
|
|
|
gmove(&n1, res);
|
|
|
|
regfree(&n1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-08-07 13:57:44 -06:00
|
|
|
memset(&oldcx, 0, sizeof oldcx);
|
|
|
|
nodreg(&cx, types[TUINT32], D_CX);
|
2009-09-30 09:56:01 -06:00
|
|
|
if(reg[D_CX] > 1 && !samereg(&cx, res)) {
|
2009-12-02 19:31:29 -07:00
|
|
|
tempname(&oldcx, types[TUINT32]);
|
2009-08-07 13:57:44 -06:00
|
|
|
gmove(&cx, &oldcx);
|
|
|
|
}
|
|
|
|
|
2011-07-28 16:22:12 -06:00
|
|
|
if(nr->type->width > 4) {
|
|
|
|
tempname(&nt, nr->type);
|
|
|
|
n1 = nt;
|
|
|
|
} else {
|
|
|
|
nodreg(&n1, types[TUINT32], D_CX);
|
|
|
|
regalloc(&n1, nr->type, &n1); // to hold the shift type in CX
|
|
|
|
}
|
2009-06-04 16:24:01 -06:00
|
|
|
|
2009-08-07 13:57:44 -06:00
|
|
|
if(samereg(&cx, res))
|
|
|
|
regalloc(&n2, nl->type, N);
|
|
|
|
else
|
|
|
|
regalloc(&n2, nl->type, res);
|
2009-06-04 16:24:01 -06:00
|
|
|
if(nl->ullman >= nr->ullman) {
|
|
|
|
cgen(nl, &n2);
|
|
|
|
cgen(nr, &n1);
|
|
|
|
} else {
|
|
|
|
cgen(nr, &n1);
|
|
|
|
cgen(nl, &n2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// test and fix up large shifts
|
2012-05-24 15:20:07 -06:00
|
|
|
if(bounded) {
|
|
|
|
if(nr->type->width > 4) {
|
|
|
|
// delayed reg alloc
|
|
|
|
nodreg(&n1, types[TUINT32], D_CX);
|
|
|
|
regalloc(&n1, types[TUINT32], &n1); // to hold the shift type in CX
|
|
|
|
split64(&nt, &lo, &hi);
|
|
|
|
gmove(&lo, &n1);
|
2013-02-07 15:55:25 -07:00
|
|
|
splitclean();
|
2012-05-24 15:20:07 -06:00
|
|
|
}
|
2009-06-04 16:24:01 -06:00
|
|
|
} else {
|
2012-05-24 15:20:07 -06:00
|
|
|
if(nr->type->width > 4) {
|
|
|
|
// delayed reg alloc
|
|
|
|
nodreg(&n1, types[TUINT32], D_CX);
|
|
|
|
regalloc(&n1, types[TUINT32], &n1); // to hold the shift type in CX
|
|
|
|
split64(&nt, &lo, &hi);
|
|
|
|
gmove(&lo, &n1);
|
|
|
|
gins(optoas(OCMP, types[TUINT32]), &hi, ncon(0));
|
2012-05-30 16:07:39 -06:00
|
|
|
p2 = gbranch(optoas(ONE, types[TUINT32]), T, +1);
|
2012-05-24 15:20:07 -06:00
|
|
|
gins(optoas(OCMP, types[TUINT32]), &n1, ncon(w));
|
2012-05-30 16:07:39 -06:00
|
|
|
p1 = gbranch(optoas(OLT, types[TUINT32]), T, +1);
|
2013-02-07 15:55:25 -07:00
|
|
|
splitclean();
|
2012-05-24 15:20:07 -06:00
|
|
|
patch(p2, pc);
|
|
|
|
} else {
|
|
|
|
gins(optoas(OCMP, nr->type), &n1, ncon(w));
|
2012-05-30 16:07:39 -06:00
|
|
|
p1 = gbranch(optoas(OLT, types[TUINT32]), T, +1);
|
2012-05-24 15:20:07 -06:00
|
|
|
}
|
|
|
|
if(op == ORSH && issigned[nl->type->etype]) {
|
|
|
|
gins(a, ncon(w-1), &n2);
|
|
|
|
} else {
|
|
|
|
gmove(ncon(0), &n2);
|
|
|
|
}
|
|
|
|
patch(p1, pc);
|
2009-06-04 16:24:01 -06:00
|
|
|
}
|
|
|
|
gins(a, &n1, &n2);
|
2009-08-12 14:18:27 -06:00
|
|
|
|
2009-12-02 19:31:29 -07:00
|
|
|
if(oldcx.op != 0)
|
2009-08-07 13:57:44 -06:00
|
|
|
gmove(&oldcx, &cx);
|
2009-06-04 16:24:01 -06:00
|
|
|
|
|
|
|
gmove(&n2, res);
|
|
|
|
|
|
|
|
regfree(&n1);
|
|
|
|
regfree(&n2);
|
2009-04-02 17:48:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* generate byte multiply:
|
|
|
|
* res = nl * nr
|
2012-09-26 13:17:11 -06:00
|
|
|
* there is no 2-operand byte multiply instruction so
|
|
|
|
* we do a full-width multiplication and truncate afterwards.
|
2009-04-02 17:48:06 -06:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
cgen_bmul(int op, Node *nl, Node *nr, Node *res)
|
|
|
|
{
|
2012-12-21 15:46:16 -07:00
|
|
|
Node n1, n2, nt, *tmp;
|
2009-06-06 20:28:16 -06:00
|
|
|
Type *t;
|
|
|
|
int a;
|
|
|
|
|
2012-09-26 13:17:11 -06:00
|
|
|
// copy from byte to full registers
|
|
|
|
t = types[TUINT32];
|
2009-06-06 20:28:16 -06:00
|
|
|
if(issigned[nl->type->etype])
|
2012-09-26 13:17:11 -06:00
|
|
|
t = types[TINT32];
|
2009-06-06 20:28:16 -06:00
|
|
|
|
2012-09-26 13:17:11 -06:00
|
|
|
// largest ullman on left.
|
|
|
|
if(nl->ullman < nr->ullman) {
|
|
|
|
tmp = nl;
|
|
|
|
nl = nr;
|
|
|
|
nr = tmp;
|
|
|
|
}
|
2009-06-06 20:28:16 -06:00
|
|
|
|
2012-12-21 15:46:16 -07:00
|
|
|
tempname(&nt, nl->type);
|
|
|
|
cgen(nl, &nt);
|
2012-09-26 13:17:11 -06:00
|
|
|
regalloc(&n1, t, res);
|
2012-12-21 15:46:16 -07:00
|
|
|
cgen(nr, &n1);
|
2012-09-26 13:17:11 -06:00
|
|
|
regalloc(&n2, t, N);
|
2012-12-21 15:46:16 -07:00
|
|
|
gmove(&nt, &n2);
|
2009-06-06 20:28:16 -06:00
|
|
|
a = optoas(op, t);
|
2012-09-26 13:17:11 -06:00
|
|
|
gins(a, &n2, &n1);
|
|
|
|
regfree(&n2);
|
|
|
|
gmove(&n1, res);
|
|
|
|
regfree(&n1);
|
2009-04-02 17:48:06 -06:00
|
|
|
}
|
2012-09-26 13:17:11 -06:00
|
|
|
|
2012-11-26 15:45:22 -07:00
|
|
|
/*
|
|
|
|
* generate high multiply:
|
|
|
|
* res = (nl*nr) >> width
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
cgen_hmul(Node *nl, Node *nr, Node *res)
|
|
|
|
{
|
|
|
|
Type *t;
|
|
|
|
int a;
|
|
|
|
Node n1, n2, ax, dx;
|
|
|
|
|
|
|
|
t = nl->type;
|
|
|
|
a = optoas(OHMUL, t);
|
|
|
|
// gen nl in n1.
|
|
|
|
tempname(&n1, t);
|
|
|
|
cgen(nl, &n1);
|
|
|
|
// gen nr in n2.
|
|
|
|
regalloc(&n2, t, res);
|
|
|
|
cgen(nr, &n2);
|
|
|
|
|
|
|
|
// multiply.
|
|
|
|
nodreg(&ax, t, D_AX);
|
|
|
|
gmove(&n2, &ax);
|
|
|
|
gins(a, &n1, N);
|
|
|
|
regfree(&n2);
|
|
|
|
|
|
|
|
if(t->width == 1) {
|
|
|
|
// byte multiply behaves differently.
|
|
|
|
nodreg(&ax, t, D_AH);
|
|
|
|
nodreg(&dx, t, D_DL);
|
|
|
|
gmove(&ax, &dx);
|
|
|
|
}
|
|
|
|
nodreg(&dx, t, D_DX);
|
|
|
|
gmove(&dx, res);
|
|
|
|
}
|
|
|
|
|
2013-01-02 14:55:23 -07:00
|
|
|
static void cgen_float387(Node *n, Node *res);
|
|
|
|
static void cgen_floatsse(Node *n, Node *res);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* generate floating-point operation.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
cgen_float(Node *n, Node *res)
|
|
|
|
{
|
|
|
|
Node *nl;
|
|
|
|
Node n1, n2;
|
|
|
|
Prog *p1, *p2, *p3;
|
|
|
|
|
|
|
|
nl = n->left;
|
|
|
|
switch(n->op) {
|
|
|
|
case OEQ:
|
|
|
|
case ONE:
|
|
|
|
case OLT:
|
|
|
|
case OLE:
|
|
|
|
case OGE:
|
|
|
|
p1 = gbranch(AJMP, T, 0);
|
|
|
|
p2 = pc;
|
|
|
|
gmove(nodbool(1), res);
|
|
|
|
p3 = gbranch(AJMP, T, 0);
|
|
|
|
patch(p1, pc);
|
|
|
|
bgen(n, 1, 0, p2);
|
|
|
|
gmove(nodbool(0), res);
|
|
|
|
patch(p3, pc);
|
|
|
|
return;
|
|
|
|
|
|
|
|
case OPLUS:
|
|
|
|
cgen(nl, res);
|
|
|
|
return;
|
|
|
|
|
|
|
|
case OCONV:
|
|
|
|
if(eqtype(n->type, nl->type) || noconv(n->type, nl->type)) {
|
|
|
|
cgen(nl, res);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tempname(&n2, n->type);
|
|
|
|
mgen(nl, &n1, res);
|
|
|
|
gmove(&n1, &n2);
|
|
|
|
gmove(&n2, res);
|
|
|
|
mfree(&n1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(use_sse)
|
|
|
|
cgen_floatsse(n, res);
|
|
|
|
else
|
|
|
|
cgen_float387(n, res);
|
|
|
|
}
|
|
|
|
|
|
|
|
// floating-point. 387 (not SSE2)
|
|
|
|
static void
|
|
|
|
cgen_float387(Node *n, Node *res)
|
|
|
|
{
|
|
|
|
Node f0, f1;
|
|
|
|
Node *nl, *nr;
|
|
|
|
|
|
|
|
nl = n->left;
|
|
|
|
nr = n->right;
|
|
|
|
nodreg(&f0, nl->type, D_F0);
|
|
|
|
nodreg(&f1, n->type, D_F0+1);
|
|
|
|
if(nr != N)
|
|
|
|
goto flt2;
|
|
|
|
|
|
|
|
// unary
|
|
|
|
cgen(nl, &f0);
|
|
|
|
if(n->op != OCONV && n->op != OPLUS)
|
|
|
|
gins(foptoas(n->op, n->type, 0), N, N);
|
|
|
|
gmove(&f0, res);
|
|
|
|
return;
|
|
|
|
|
|
|
|
flt2: // binary
|
|
|
|
if(nl->ullman >= nr->ullman) {
|
|
|
|
cgen(nl, &f0);
|
|
|
|
if(nr->addable)
|
|
|
|
gins(foptoas(n->op, n->type, 0), nr, &f0);
|
|
|
|
else {
|
|
|
|
cgen(nr, &f0);
|
|
|
|
gins(foptoas(n->op, n->type, Fpop), &f0, &f1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
cgen(nr, &f0);
|
|
|
|
if(nl->addable)
|
|
|
|
gins(foptoas(n->op, n->type, Frev), nl, &f0);
|
|
|
|
else {
|
|
|
|
cgen(nl, &f0);
|
|
|
|
gins(foptoas(n->op, n->type, Frev|Fpop), &f0, &f1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
gmove(&f0, res);
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cgen_floatsse(Node *n, Node *res)
|
|
|
|
{
|
|
|
|
Node *nl, *nr, *r;
|
|
|
|
Node n1, n2, nt;
|
|
|
|
int a;
|
|
|
|
|
|
|
|
nl = n->left;
|
|
|
|
nr = n->right;
|
|
|
|
switch(n->op) {
|
|
|
|
default:
|
|
|
|
dump("cgen_floatsse", n);
|
|
|
|
fatal("cgen_floatsse %O", n->op);
|
|
|
|
return;
|
|
|
|
|
|
|
|
case OMINUS:
|
|
|
|
case OCOM:
|
|
|
|
nr = nodintconst(-1);
|
|
|
|
convlit(&nr, n->type);
|
|
|
|
a = foptoas(OMUL, nl->type, 0);
|
|
|
|
goto sbop;
|
|
|
|
|
|
|
|
// symmetric binary
|
|
|
|
case OADD:
|
|
|
|
case OMUL:
|
|
|
|
a = foptoas(n->op, nl->type, 0);
|
|
|
|
goto sbop;
|
|
|
|
|
|
|
|
// asymmetric binary
|
|
|
|
case OSUB:
|
|
|
|
case OMOD:
|
|
|
|
case ODIV:
|
|
|
|
a = foptoas(n->op, nl->type, 0);
|
|
|
|
goto abop;
|
|
|
|
}
|
|
|
|
|
|
|
|
sbop: // symmetric binary
|
|
|
|
if(nl->ullman < nr->ullman || nl->op == OLITERAL) {
|
|
|
|
r = nl;
|
|
|
|
nl = nr;
|
|
|
|
nr = r;
|
|
|
|
}
|
|
|
|
|
|
|
|
abop: // asymmetric binary
|
|
|
|
if(nl->ullman >= nr->ullman) {
|
|
|
|
tempname(&nt, nl->type);
|
|
|
|
cgen(nl, &nt);
|
|
|
|
mgen(nr, &n2, N);
|
|
|
|
regalloc(&n1, nl->type, res);
|
|
|
|
gmove(&nt, &n1);
|
|
|
|
gins(a, &n2, &n1);
|
|
|
|
gmove(&n1, res);
|
|
|
|
regfree(&n1);
|
|
|
|
mfree(&n2);
|
|
|
|
} else {
|
|
|
|
regalloc(&n2, nr->type, res);
|
|
|
|
cgen(nr, &n2);
|
|
|
|
regalloc(&n1, nl->type, N);
|
|
|
|
cgen(nl, &n1);
|
|
|
|
gins(a, &n2, &n1);
|
|
|
|
regfree(&n2);
|
|
|
|
gmove(&n1, res);
|
|
|
|
regfree(&n1);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
bgen_float(Node *n, int true, int likely, Prog *to)
|
|
|
|
{
|
|
|
|
int et, a;
|
|
|
|
Node *nl, *nr, *r;
|
|
|
|
Node n1, n2, n3, tmp, t1, t2, ax;
|
|
|
|
Prog *p1, *p2;
|
|
|
|
|
|
|
|
nl = n->left;
|
|
|
|
nr = n->right;
|
|
|
|
a = n->op;
|
|
|
|
if(!true) {
|
|
|
|
// brcom is not valid on floats when NaN is involved.
|
|
|
|
p1 = gbranch(AJMP, T, 0);
|
|
|
|
p2 = gbranch(AJMP, T, 0);
|
|
|
|
patch(p1, pc);
|
|
|
|
// No need to avoid re-genning ninit.
|
|
|
|
bgen_float(n, 1, -likely, p2);
|
|
|
|
patch(gbranch(AJMP, T, 0), to);
|
|
|
|
patch(p2, pc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(use_sse)
|
|
|
|
goto sse;
|
|
|
|
else
|
|
|
|
goto x87;
|
|
|
|
|
|
|
|
x87:
|
|
|
|
a = brrev(a); // because the args are stacked
|
|
|
|
if(a == OGE || a == OGT) {
|
|
|
|
// only < and <= work right with NaN; reverse if needed
|
|
|
|
r = nr;
|
|
|
|
nr = nl;
|
|
|
|
nl = r;
|
|
|
|
a = brrev(a);
|
|
|
|
}
|
|
|
|
|
|
|
|
nodreg(&tmp, nr->type, D_F0);
|
|
|
|
nodreg(&n2, nr->type, D_F0 + 1);
|
|
|
|
nodreg(&ax, types[TUINT16], D_AX);
|
|
|
|
et = simsimtype(nr->type);
|
|
|
|
if(et == TFLOAT64) {
|
|
|
|
if(nl->ullman > nr->ullman) {
|
|
|
|
cgen(nl, &tmp);
|
|
|
|
cgen(nr, &tmp);
|
|
|
|
gins(AFXCHD, &tmp, &n2);
|
|
|
|
} else {
|
|
|
|
cgen(nr, &tmp);
|
|
|
|
cgen(nl, &tmp);
|
|
|
|
}
|
|
|
|
gins(AFUCOMIP, &tmp, &n2);
|
|
|
|
gins(AFMOVDP, &tmp, &tmp); // annoying pop but still better than STSW+SAHF
|
|
|
|
} else {
|
|
|
|
// TODO(rsc): The moves back and forth to memory
|
|
|
|
// here are for truncating the value to 32 bits.
|
|
|
|
// This handles 32-bit comparison but presumably
|
|
|
|
// all the other ops have the same problem.
|
|
|
|
// We need to figure out what the right general
|
|
|
|
// solution is, besides telling people to use float64.
|
|
|
|
tempname(&t1, types[TFLOAT32]);
|
|
|
|
tempname(&t2, types[TFLOAT32]);
|
|
|
|
cgen(nr, &t1);
|
|
|
|
cgen(nl, &t2);
|
|
|
|
gmove(&t2, &tmp);
|
|
|
|
gins(AFCOMFP, &t1, &tmp);
|
|
|
|
gins(AFSTSW, N, &ax);
|
|
|
|
gins(ASAHF, N, N);
|
|
|
|
}
|
|
|
|
|
|
|
|
goto ret;
|
|
|
|
|
|
|
|
sse:
|
|
|
|
if(!nl->addable) {
|
|
|
|
tempname(&n1, nl->type);
|
|
|
|
cgen(nl, &n1);
|
|
|
|
nl = &n1;
|
|
|
|
}
|
|
|
|
if(!nr->addable) {
|
|
|
|
tempname(&tmp, nr->type);
|
|
|
|
cgen(nr, &tmp);
|
|
|
|
nr = &tmp;
|
|
|
|
}
|
|
|
|
regalloc(&n2, nr->type, N);
|
|
|
|
gmove(nr, &n2);
|
|
|
|
nr = &n2;
|
|
|
|
|
|
|
|
if(nl->op != OREGISTER) {
|
|
|
|
regalloc(&n3, nl->type, N);
|
|
|
|
gmove(nl, &n3);
|
|
|
|
nl = &n3;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(a == OGE || a == OGT) {
|
|
|
|
// only < and <= work right with NaN; reverse if needed
|
|
|
|
r = nr;
|
|
|
|
nr = nl;
|
|
|
|
nl = r;
|
|
|
|
a = brrev(a);
|
|
|
|
}
|
|
|
|
|
|
|
|
gins(foptoas(OCMP, nr->type, 0), nl, nr);
|
|
|
|
if(nl->op == OREGISTER)
|
|
|
|
regfree(nl);
|
|
|
|
regfree(nr);
|
|
|
|
|
|
|
|
ret:
|
|
|
|
if(a == OEQ) {
|
|
|
|
// neither NE nor P
|
|
|
|
p1 = gbranch(AJNE, T, -likely);
|
|
|
|
p2 = gbranch(AJPS, T, -likely);
|
|
|
|
patch(gbranch(AJMP, T, 0), to);
|
|
|
|
patch(p1, pc);
|
|
|
|
patch(p2, pc);
|
|
|
|
} else if(a == ONE) {
|
|
|
|
// either NE or P
|
|
|
|
patch(gbranch(AJNE, T, likely), to);
|
|
|
|
patch(gbranch(AJPS, T, likely), to);
|
|
|
|
} else
|
|
|
|
patch(gbranch(optoas(a, nr->type), T, likely), to);
|
|
|
|
|
|
|
|
}
|
2013-08-15 12:38:32 -06:00
|
|
|
|
|
|
|
// Called after regopt and peep have run.
|
|
|
|
// Expand CHECKNIL pseudo-op into actual nil pointer check.
|
|
|
|
void
|
|
|
|
expandchecks(Prog *firstp)
|
|
|
|
{
|
|
|
|
Prog *p, *p1, *p2;
|
|
|
|
|
|
|
|
for(p = firstp; p != P; p = p->link) {
|
|
|
|
if(p->as != ACHECKNIL)
|
|
|
|
continue;
|
|
|
|
if(debug_checknil && p->lineno > 1) // p->lineno==1 in generated wrappers
|
2013-09-17 14:54:22 -06:00
|
|
|
warnl(p->lineno, "generated nil check");
|
2013-08-15 12:38:32 -06:00
|
|
|
// check is
|
|
|
|
// CMP arg, $0
|
|
|
|
// JNE 2(PC) (likely)
|
|
|
|
// MOV AX, 0
|
|
|
|
p1 = mal(sizeof *p1);
|
|
|
|
p2 = mal(sizeof *p2);
|
|
|
|
clearp(p1);
|
|
|
|
clearp(p2);
|
|
|
|
p1->link = p2;
|
|
|
|
p2->link = p->link;
|
|
|
|
p->link = p1;
|
|
|
|
p1->lineno = p->lineno;
|
|
|
|
p2->lineno = p->lineno;
|
cmd/cc, cmd/gc, cmd/ld: consolidate print format routines
We now use the %A, %D, %P, and %R routines from liblink
across the board.
Fixes #7178.
Fixes #7055.
LGTM=iant
R=golang-codereviews, gobot, rsc, dave, iant, remyoudompheng
CC=golang-codereviews
https://golang.org/cl/49170043
2014-02-12 12:29:11 -07:00
|
|
|
p1->pc = 9999;
|
|
|
|
p2->pc = 9999;
|
2013-08-15 12:38:32 -06:00
|
|
|
p->as = ACMPL;
|
|
|
|
p->to.type = D_CONST;
|
|
|
|
p->to.offset = 0;
|
|
|
|
p1->as = AJNE;
|
|
|
|
p1->from.type = D_CONST;
|
|
|
|
p1->from.offset = 1; // likely
|
|
|
|
p1->to.type = D_BRANCH;
|
|
|
|
p1->to.u.branch = p2->link;
|
|
|
|
// crash by write to memory address 0.
|
|
|
|
// if possible, since we know arg is 0, use 0(arg),
|
|
|
|
// which will be shorter to encode than plain 0.
|
|
|
|
p2->as = AMOVL;
|
|
|
|
p2->from.type = D_AX;
|
|
|
|
if(regtyp(&p->from))
|
|
|
|
p2->to.type = p->from.type + D_INDIR;
|
|
|
|
else
|
|
|
|
p2->to.type = D_INDIR+D_NONE;
|
|
|
|
p2->to.offset = 0;
|
|
|
|
}
|
|
|
|
}
|