Commit cfc2aa56 authored by Keith Randall's avatar Keith Randall

[dev.ssa] cmd/internal/ssa: Handle more instructions + some cleanup

Add & as an input op.  Add several output ops (loads & stores, TESTB,
LEAQglobal, branches, memcopy)

Some other small things:
- Add exprAddr to builder to generate addresses of expressions.  Use it in
  various places that had ad-hoc code.
- Separate out nil & bounds check generation to separate functions.
- Add explicit FP and SP ops so we dont need specialized *FP and *SP opcodes.
- Fix fallthrough at end of functions with no return values.
- rematerialization of more opcodes.

Change-Id: I781decfcef9770fb15f0cd6b061547f7824a2d5e
Reviewed-on: https://go-review.googlesource.com/10213Reviewed-by: default avatarAlan Donovan <adonovan@google.com>
parent 083a646f
This diff is collapsed.
......@@ -110,23 +110,23 @@ const (
AINTO
AIRETL
AIRETW
AJCC
AJCS
AJCC // >= unsigned
AJCS // < unsigned
AJCXZL
AJEQ
AJGE
AJGT
AJHI
AJLE
AJLS
AJLT
AJMI
AJNE
AJOC
AJOS
AJPC
AJPL
AJPS
AJEQ // == (zero)
AJGE // >= signed
AJGT // > signed
AJHI // > unsigned
AJLE // <= signed
AJLS // <= unsigned
AJLT // < signed
AJMI // sign bit set (negative)
AJNE // != (nonzero)
AJOC // overflow clear
AJOS // overflow set
AJPC // parity clear
AJPL // sign bit clear (positive)
AJPS // parity set
ALAHF
ALARL
ALARW
......
......@@ -58,7 +58,7 @@ func checkFunc(f *Func) {
if b.Control == nil {
log.Panicf("exit block %s has no control value", b)
}
if b.Control.Type != TypeMem {
if !b.Control.Type.IsMemory() {
log.Panicf("exit block %s has non-memory control value %s", b, b.Control.LongString())
}
case BlockPlain:
......@@ -75,7 +75,7 @@ func checkFunc(f *Func) {
if b.Control == nil {
log.Panicf("if block %s has no control value", b)
}
if b.Control.Type != TypeBool {
if !b.Control.Type.IsBoolean() {
log.Panicf("if block %s has non-bool control value %s", b, b.Control.LongString())
}
case BlockCall:
......@@ -85,7 +85,7 @@ func checkFunc(f *Func) {
if b.Control == nil {
log.Panicf("call block %s has no control value", b)
}
if b.Control.Type != TypeMem {
if !b.Control.Type.IsMemory() {
log.Panicf("call block %s has non-memory control value %s", b, b.Control.LongString())
}
if b.Succs[1].Kind != BlockExit {
......
......@@ -9,7 +9,7 @@ import "log"
type Config struct {
arch string // "amd64", etc.
ptrSize int64 // 4 or 8
UIntPtr Type // pointer arithmetic type
Uintptr Type // pointer arithmetic type
lower func(*Value) bool // lowering function
// TODO: more stuff. Compiler flags of interest, ...
......@@ -30,9 +30,9 @@ func NewConfig(arch string) *Config {
}
// cache the intptr type in the config
c.UIntPtr = TypeUInt32
c.Uintptr = TypeUInt32
if c.ptrSize == 8 {
c.UIntPtr = TypeUInt64
c.Uintptr = TypeUInt64
}
return c
......
......@@ -6,20 +6,20 @@ func genericRules(v *Value) bool {
switch v.Op {
case OpAdd:
// match: (Add <t> (Const [c]) (Const [d]))
// cond: is64BitInt(t) && isSigned(t)
// cond: is64BitInt(t)
// result: (Const [{c.(int64)+d.(int64)}])
{
t := v.Type
if v.Args[0].Op != OpConst {
goto endc86f5c160a87f6f5ec90b6551ec099d9
goto end8d047ed0ae9537b840adc79ea82c6e05
}
c := v.Args[0].Aux
if v.Args[1].Op != OpConst {
goto endc86f5c160a87f6f5ec90b6551ec099d9
goto end8d047ed0ae9537b840adc79ea82c6e05
}
d := v.Args[1].Aux
if !(is64BitInt(t) && isSigned(t)) {
goto endc86f5c160a87f6f5ec90b6551ec099d9
if !(is64BitInt(t)) {
goto end8d047ed0ae9537b840adc79ea82c6e05
}
v.Op = OpConst
v.Aux = nil
......@@ -27,100 +27,141 @@ func genericRules(v *Value) bool {
v.Aux = c.(int64) + d.(int64)
return true
}
goto endc86f5c160a87f6f5ec90b6551ec099d9
endc86f5c160a87f6f5ec90b6551ec099d9:
goto end8d047ed0ae9537b840adc79ea82c6e05
end8d047ed0ae9537b840adc79ea82c6e05:
;
// match: (Add <t> (Const [c]) (Const [d]))
// cond: is64BitInt(t) && !isSigned(t)
// result: (Const [{c.(uint64)+d.(uint64)}])
case OpArrayIndex:
// match: (ArrayIndex (Load ptr mem) idx)
// cond:
// result: (Load (PtrIndex <ptr.Type.Elem().Elem().PtrTo()> ptr idx) mem)
{
if v.Args[0].Op != OpLoad {
goto end3809f4c52270a76313e4ea26e6f0b753
}
ptr := v.Args[0].Args[0]
mem := v.Args[0].Args[1]
idx := v.Args[1]
v.Op = OpLoad
v.Aux = nil
v.resetArgs()
v0 := v.Block.NewValue(OpPtrIndex, TypeInvalid, nil)
v0.Type = ptr.Type.Elem().Elem().PtrTo()
v0.AddArg(ptr)
v0.AddArg(idx)
v.AddArg(v0)
v.AddArg(mem)
return true
}
goto end3809f4c52270a76313e4ea26e6f0b753
end3809f4c52270a76313e4ea26e6f0b753:
;
case OpIsInBounds:
// match: (IsInBounds (Const [c]) (Const [d]))
// cond:
// result: (Const [inBounds(c.(int64),d.(int64))])
{
if v.Args[0].Op != OpConst {
goto enddbd1a394d9b71ee64335361b8384865c
}
c := v.Args[0].Aux
if v.Args[1].Op != OpConst {
goto enddbd1a394d9b71ee64335361b8384865c
}
d := v.Args[1].Aux
v.Op = OpConst
v.Aux = nil
v.resetArgs()
v.Aux = inBounds(c.(int64), d.(int64))
return true
}
goto enddbd1a394d9b71ee64335361b8384865c
enddbd1a394d9b71ee64335361b8384865c:
;
case OpMul:
// match: (Mul <t> (Const [c]) (Const [d]))
// cond: is64BitInt(t)
// result: (Const [{c.(int64)*d.(int64)}])
{
t := v.Type
if v.Args[0].Op != OpConst {
goto end8941c2a515c1bd38530b7fd96862bac4
goto end776610f88cf04f438242d76ed2b14f1c
}
c := v.Args[0].Aux
if v.Args[1].Op != OpConst {
goto end8941c2a515c1bd38530b7fd96862bac4
goto end776610f88cf04f438242d76ed2b14f1c
}
d := v.Args[1].Aux
if !(is64BitInt(t) && !isSigned(t)) {
goto end8941c2a515c1bd38530b7fd96862bac4
if !(is64BitInt(t)) {
goto end776610f88cf04f438242d76ed2b14f1c
}
v.Op = OpConst
v.Aux = nil
v.resetArgs()
v.Aux = c.(uint64) + d.(uint64)
v.Aux = c.(int64) * d.(int64)
return true
}
goto end8941c2a515c1bd38530b7fd96862bac4
end8941c2a515c1bd38530b7fd96862bac4:
goto end776610f88cf04f438242d76ed2b14f1c
end776610f88cf04f438242d76ed2b14f1c:
;
case OpSliceCap:
// match: (SliceCap (Load ptr mem))
case OpPtrIndex:
// match: (PtrIndex <t> ptr idx)
// cond:
// result: (Load (Add <ptr.Type> ptr (Const <v.Block.Func.Config.UIntPtr> [int64(v.Block.Func.Config.ptrSize*2)])) mem)
// result: (Add ptr (Mul <v.Block.Func.Config.Uintptr> idx (Const <v.Block.Func.Config.Uintptr> [t.Elem().Size()])))
{
if v.Args[0].Op != OpLoad {
goto ende03f9b79848867df439b56889bb4e55d
}
ptr := v.Args[0].Args[0]
mem := v.Args[0].Args[1]
v.Op = OpLoad
t := v.Type
ptr := v.Args[0]
idx := v.Args[1]
v.Op = OpAdd
v.Aux = nil
v.resetArgs()
v0 := v.Block.NewValue(OpAdd, TypeInvalid, nil)
v0.Type = ptr.Type
v0.AddArg(ptr)
v.AddArg(ptr)
v0 := v.Block.NewValue(OpMul, TypeInvalid, nil)
v0.Type = v.Block.Func.Config.Uintptr
v0.AddArg(idx)
v1 := v.Block.NewValue(OpConst, TypeInvalid, nil)
v1.Type = v.Block.Func.Config.UIntPtr
v1.Aux = int64(v.Block.Func.Config.ptrSize * 2)
v1.Type = v.Block.Func.Config.Uintptr
v1.Aux = t.Elem().Size()
v0.AddArg(v1)
v.AddArg(v0)
v.AddArg(mem)
return true
}
goto ende03f9b79848867df439b56889bb4e55d
ende03f9b79848867df439b56889bb4e55d:
goto end383c68c41e72d22ef00c4b7b0fddcbb8
end383c68c41e72d22ef00c4b7b0fddcbb8:
;
case OpSliceIndex:
// match: (SliceIndex s i mem)
case OpSliceCap:
// match: (SliceCap (Load ptr mem))
// cond:
// result: (Load (Add <s.Type.Elem().PtrTo()> (SlicePtr <s.Type.Elem().PtrTo()> s) (Mul <v.Block.Func.Config.UIntPtr> i (Const <v.Block.Func.Config.UIntPtr> [s.Type.Elem().Size()]))) mem)
// result: (Load (Add <ptr.Type> ptr (Const <v.Block.Func.Config.Uintptr> [int64(v.Block.Func.Config.ptrSize*2)])) mem)
{
s := v.Args[0]
i := v.Args[1]
mem := v.Args[2]
if v.Args[0].Op != OpLoad {
goto endbf1d4db93c4664ed43be3f73afb4dfa3
}
ptr := v.Args[0].Args[0]
mem := v.Args[0].Args[1]
v.Op = OpLoad
v.Aux = nil
v.resetArgs()
v0 := v.Block.NewValue(OpAdd, TypeInvalid, nil)
v0.Type = s.Type.Elem().PtrTo()
v1 := v.Block.NewValue(OpSlicePtr, TypeInvalid, nil)
v1.Type = s.Type.Elem().PtrTo()
v1.AddArg(s)
v0.Type = ptr.Type
v0.AddArg(ptr)
v1 := v.Block.NewValue(OpConst, TypeInvalid, nil)
v1.Type = v.Block.Func.Config.Uintptr
v1.Aux = int64(v.Block.Func.Config.ptrSize * 2)
v0.AddArg(v1)
v2 := v.Block.NewValue(OpMul, TypeInvalid, nil)
v2.Type = v.Block.Func.Config.UIntPtr
v2.AddArg(i)
v3 := v.Block.NewValue(OpConst, TypeInvalid, nil)
v3.Type = v.Block.Func.Config.UIntPtr
v3.Aux = s.Type.Elem().Size()
v2.AddArg(v3)
v0.AddArg(v2)
v.AddArg(v0)
v.AddArg(mem)
return true
}
goto end733704831a61760840348f790b3ab045
end733704831a61760840348f790b3ab045:
goto endbf1d4db93c4664ed43be3f73afb4dfa3
endbf1d4db93c4664ed43be3f73afb4dfa3:
;
case OpSliceLen:
// match: (SliceLen (Load ptr mem))
// cond:
// result: (Load (Add <ptr.Type> ptr (Const <v.Block.Func.Config.UIntPtr> [int64(v.Block.Func.Config.ptrSize)])) mem)
// result: (Load (Add <ptr.Type> ptr (Const <v.Block.Func.Config.Uintptr> [int64(v.Block.Func.Config.ptrSize)])) mem)
{
if v.Args[0].Op != OpLoad {
goto ende94950a57eca1871c93afdeaadb90223
goto end9190b1ecbda4c5dd6d3e05d2495fb297
}
ptr := v.Args[0].Args[0]
mem := v.Args[0].Args[1]
......@@ -131,15 +172,15 @@ func genericRules(v *Value) bool {
v0.Type = ptr.Type
v0.AddArg(ptr)
v1 := v.Block.NewValue(OpConst, TypeInvalid, nil)
v1.Type = v.Block.Func.Config.UIntPtr
v1.Type = v.Block.Func.Config.Uintptr
v1.Aux = int64(v.Block.Func.Config.ptrSize)
v0.AddArg(v1)
v.AddArg(v0)
v.AddArg(mem)
return true
}
goto ende94950a57eca1871c93afdeaadb90223
ende94950a57eca1871c93afdeaadb90223:
goto end9190b1ecbda4c5dd6d3e05d2495fb297
end9190b1ecbda4c5dd6d3e05d2495fb297:
;
case OpSlicePtr:
// match: (SlicePtr (Load ptr mem))
......@@ -160,6 +201,36 @@ func genericRules(v *Value) bool {
}
goto end459613b83f95b65729d45c2ed663a153
end459613b83f95b65729d45c2ed663a153:
;
case OpStore:
// match: (Store dst (Load <t> src mem) mem)
// cond: t.Size() > 8
// result: (Move [t.Size()] dst src mem)
{
dst := v.Args[0]
if v.Args[1].Op != OpLoad {
goto end324ffb6d2771808da4267f62c854e9c8
}
t := v.Args[1].Type
src := v.Args[1].Args[0]
mem := v.Args[1].Args[1]
if v.Args[2] != v.Args[1].Args[1] {
goto end324ffb6d2771808da4267f62c854e9c8
}
if !(t.Size() > 8) {
goto end324ffb6d2771808da4267f62c854e9c8
}
v.Op = OpMove
v.Aux = nil
v.resetArgs()
v.Aux = t.Size()
v.AddArg(dst)
v.AddArg(src)
v.AddArg(mem)
return true
}
goto end324ffb6d2771808da4267f62c854e9c8
end324ffb6d2771808da4267f62c854e9c8:
}
return false
}
......@@ -16,41 +16,88 @@ func lower(f *Func) {
// additional pass for 386/amd64, link condition codes directly to blocks
// TODO: do generically somehow? Special "block" rewrite rules?
for _, b := range f.Blocks {
switch b.Kind {
case BlockIf:
switch b.Control.Op {
case OpSETL:
b.Kind = BlockLT
b.Control = b.Control.Args[0]
case OpSETNE:
b.Kind = BlockNE
b.Control = b.Control.Args[0]
case OpSETB:
b.Kind = BlockULT
b.Control = b.Control.Args[0]
// TODO: others
for {
switch b.Kind {
case BlockIf:
switch b.Control.Op {
case OpSETL:
b.Kind = BlockLT
b.Control = b.Control.Args[0]
continue
case OpSETNE:
b.Kind = BlockNE
b.Control = b.Control.Args[0]
continue
case OpSETB:
b.Kind = BlockULT
b.Control = b.Control.Args[0]
continue
case OpMOVBload:
b.Kind = BlockNE
b.Control = b.NewValue2(OpTESTB, TypeFlags, nil, b.Control, b.Control)
continue
// TODO: others
}
case BlockLT:
if b.Control.Op == OpInvertFlags {
b.Kind = BlockGT
b.Control = b.Control.Args[0]
continue
}
case BlockGT:
if b.Control.Op == OpInvertFlags {
b.Kind = BlockLT
b.Control = b.Control.Args[0]
continue
}
case BlockLE:
if b.Control.Op == OpInvertFlags {
b.Kind = BlockGE
b.Control = b.Control.Args[0]
continue
}
case BlockGE:
if b.Control.Op == OpInvertFlags {
b.Kind = BlockLE
b.Control = b.Control.Args[0]
continue
}
case BlockULT:
if b.Control.Op == OpInvertFlags {
b.Kind = BlockUGT
b.Control = b.Control.Args[0]
continue
}
case BlockUGT:
if b.Control.Op == OpInvertFlags {
b.Kind = BlockULT
b.Control = b.Control.Args[0]
continue
}
case BlockULE:
if b.Control.Op == OpInvertFlags {
b.Kind = BlockUGE
b.Control = b.Control.Args[0]
continue
}
case BlockUGE:
if b.Control.Op == OpInvertFlags {
b.Kind = BlockULE
b.Control = b.Control.Args[0]
continue
}
case BlockEQ:
if b.Control.Op == OpInvertFlags {
b.Control = b.Control.Args[0]
continue
}
case BlockNE:
if b.Control.Op == OpInvertFlags {
b.Control = b.Control.Args[0]
continue
}
}
case BlockLT:
if b.Control.Op == OpInvertFlags {
b.Kind = BlockGE
b.Control = b.Control.Args[0]
}
case BlockULT:
if b.Control.Op == OpInvertFlags {
b.Kind = BlockUGE
b.Control = b.Control.Args[0]
}
case BlockEQ:
if b.Control.Op == OpInvertFlags {
b.Kind = BlockNE
b.Control = b.Control.Args[0]
}
case BlockNE:
if b.Control.Op == OpInvertFlags {
b.Kind = BlockEQ
b.Control = b.Control.Args[0]
}
// TODO: others
break
}
}
}
This diff is collapsed.
......@@ -4,6 +4,8 @@
package ssa
import "fmt"
// An Op encodes the specific operation that a Value performs.
// Opcodes' semantics can be modified by the type and aux fields of the Value.
// For instance, OpAdd can be 32 or 64 bit, signed or unsigned, float or complex, depending on Value.Type.
......@@ -47,8 +49,11 @@ const (
OpArg // address of a function parameter/result. Memory input is an arg called ".mem". aux is a string (TODO: make it something other than a string?)
OpGlobal // the address of a global variable aux.(*gc.Sym)
OpFunc // entry address of a function
OpFP // frame pointer
OpSP // stack pointer
OpCopy // output = arg0
OpMove // arg0=destptr, arg1=srcptr, arg2=mem, aux.(int64)=size. Returns memory.
OpPhi // select an argument based on which predecessor block we came from
OpSliceMake // arg0=ptr, arg1=len, arg2=cap
......@@ -62,7 +67,8 @@ const (
OpLoad // Load from arg0+aux.(int64). arg1=memory
OpStore // Store arg1 to arg0+aux.(int64). arg2=memory. Returns memory.
OpSliceIndex // arg0=slice, arg1=index, arg2=memory
OpArrayIndex // arg0=array, arg1=index. Returns a[i]
OpPtrIndex // arg0=ptr, arg1=index. Computes ptr+sizeof(*v.type)*index, where index is extended to ptrwidth type
OpIsNonNil // arg0 != nil
OpIsInBounds // 0 <= arg0 < arg1
......@@ -75,6 +81,8 @@ const (
OpConvert // convert arg0 to another type
OpConvNop // interpret arg0 as another type
OpOffPtr // arg0 + aux.(int64) (arg0 and result are pointers)
// These ops return a pointer to a location on the stack.
OpFPAddr // FP + aux.(int64) (+ == args from caller, - == locals)
OpSPAddr // SP + aux.(int64)
......@@ -96,6 +104,15 @@ type GlobalOffset struct {
Offset int64
}
// offset adds x to the location specified by g and returns it.
func (g GlobalOffset) offset(x int64) GlobalOffset {
return GlobalOffset{g.Global, g.Offset + x}
}
func (g GlobalOffset) String() string {
return fmt.Sprintf("%v+%d", g.Global, g.Offset)
}
//go:generate stringer -type=Op
type opInfo struct {
......
......@@ -6,16 +6,16 @@ import "fmt"
const (
_Op_name_0 = "opInvalid"
_Op_name_1 = "opGenericBaseOpAddOpSubOpMulOpLessOpConstOpArgOpGlobalOpFuncOpCopyOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpLoadOpStoreOpSliceIndexOpIsNonNilOpIsInBoundsOpCallOpStaticCallOpConvertOpConvNopOpFPAddrOpSPAddrOpStoreReg8OpLoadReg8OpFwdRef"
_Op_name_2 = "opAMD64BaseOpADDQOpSUBQOpADDCQOpSUBCQOpMULQOpMULCQOpSHLQOpSHLCQOpNEGQOpADDLOpCMPQOpCMPCQOpTESTQOpSETEQOpSETNEOpSETLOpSETGEOpSETBOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpMOVQloadOpMOVQstoreOpMOVQloadidx8OpMOVQstoreidx8OpMOVQloadglobalOpMOVQstoreglobalOpMOVQloadFPOpMOVQloadSPOpMOVQstoreFPOpMOVQstoreSPOpMOVQconst"
_Op_name_1 = "opGenericBaseOpAddOpSubOpMulOpLessOpConstOpArgOpGlobalOpFuncOpFPOpSPOpCopyOpMoveOpPhiOpSliceMakeOpSlicePtrOpSliceLenOpSliceCapOpStringMakeOpStringPtrOpStringLenOpLoadOpStoreOpArrayIndexOpPtrIndexOpIsNonNilOpIsInBoundsOpCallOpStaticCallOpConvertOpConvNopOpOffPtrOpFPAddrOpSPAddrOpStoreReg8OpLoadReg8OpFwdRef"
_Op_name_2 = "opAMD64BaseOpADDQOpSUBQOpADDCQOpSUBCQOpMULQOpMULCQOpSHLQOpSHLCQOpNEGQOpADDLOpCMPQOpCMPCQOpTESTQOpTESTBOpSETEQOpSETNEOpSETLOpSETGEOpSETBOpInvertFlagsOpLEAQOpLEAQ2OpLEAQ4OpLEAQ8OpLEAQglobalOpMOVBloadOpMOVBQZXloadOpMOVBQSXloadOpMOVQloadOpMOVQstoreOpMOVQloadidx8OpMOVQstoreidx8OpMOVQloadglobalOpMOVQstoreglobalOpMOVQconstOpREPMOVSB"
_Op_name_3 = "op386Base"
_Op_name_4 = "opMax"
)
var (
_Op_index_0 = [...]uint8{0, 9}
_Op_index_1 = [...]uint16{0, 13, 18, 23, 28, 34, 41, 46, 54, 60, 66, 71, 82, 92, 102, 112, 124, 135, 146, 152, 159, 171, 181, 193, 199, 211, 220, 229, 237, 245, 256, 266, 274}
_Op_index_2 = [...]uint16{0, 11, 17, 23, 30, 37, 43, 50, 56, 63, 69, 75, 81, 88, 95, 102, 109, 115, 122, 128, 141, 147, 154, 161, 168, 178, 189, 203, 218, 234, 251, 263, 275, 288, 301, 312}
_Op_index_1 = [...]uint16{0, 13, 18, 23, 28, 34, 41, 46, 54, 60, 64, 68, 74, 80, 85, 96, 106, 116, 126, 138, 149, 160, 166, 173, 185, 195, 205, 217, 223, 235, 244, 253, 261, 269, 277, 288, 298, 306}
_Op_index_2 = [...]uint16{0, 11, 17, 23, 30, 37, 43, 50, 56, 63, 69, 75, 81, 88, 95, 102, 109, 116, 122, 129, 135, 148, 154, 161, 168, 175, 187, 197, 210, 223, 233, 244, 258, 273, 289, 306, 317, 327}
_Op_index_3 = [...]uint8{0, 9}
_Op_index_4 = [...]uint8{0, 5}
)
......@@ -24,10 +24,10 @@ func (i Op) String() string {
switch {
case i == 0:
return _Op_name_0
case 1001 <= i && i <= 1032:
case 1001 <= i && i <= 1037:
i -= 1001
return _Op_name_1[_Op_index_1[i]:_Op_index_1[i+1]]
case 2001 <= i && i <= 2035:
case 2001 <= i && i <= 2037:
i -= 2001
return _Op_name_2[_Op_index_2[i]:_Op_index_2[i+1]]
case i == 3001:
......
......@@ -30,6 +30,7 @@ const (
OpCMPQ // arg0 compare to arg1
OpCMPCQ // arg0 compare to aux.(int64)
OpTESTQ // (arg0 & arg1) compare to 0
OpTESTB // (arg0 & arg1) compare to 0
// These opcodes extract a particular boolean condition from a flags value.
OpSETEQ // extract == condition from arg0
......@@ -43,29 +44,30 @@ const (
// This is a pseudo-op which can't appear in assembly output.
OpInvertFlags // reverse direction of arg0
OpLEAQ // arg0 + arg1 + aux.(int64)
OpLEAQ2 // arg0 + 2*arg1 + aux.(int64)
OpLEAQ4 // arg0 + 4*arg1 + aux.(int64)
OpLEAQ8 // arg0 + 8*arg1 + aux.(int64)
OpLEAQ // arg0 + arg1 + aux.(int64)
OpLEAQ2 // arg0 + 2*arg1 + aux.(int64)
OpLEAQ4 // arg0 + 4*arg1 + aux.(int64)
OpLEAQ8 // arg0 + 8*arg1 + aux.(int64)
OpLEAQglobal // no args. address of aux.(GlobalOffset)
// Load/store from general address
OpMOVQload // Load from arg0+aux.(int64). arg1=memory
OpMOVBload // Load from arg0+aux.(int64). arg1=memory
OpMOVBQZXload
OpMOVBQSXload
OpMOVQload
OpMOVQstore // Store arg1 to arg0+aux.(int64). arg2=memory, returns memory.
OpMOVQloadidx8 // Load from arg0+arg1*8+aux.(int64). arg2=memory
OpMOVQstoreidx8 // Store arg2 to arg0+arg1*8+aux.(int64). arg3=memory, returns memory.
// Load/store from global. aux.(GlobalOffset) encodes the global location.
// Load/store from global. Same as the above loads, but arg0 is missing and aux is a GlobalOffset instead of an int64.
OpMOVQloadglobal // arg0 = memory
OpMOVQstoreglobal // store arg0. arg1=memory, returns memory.
// Load/store from stack slot.
OpMOVQloadFP // load from FP+aux.(int64). arg0=memory
OpMOVQloadSP // load from SP+aux.(int64). arg0=memory
OpMOVQstoreFP // store arg0 to FP+aux.(int64). arg1=memory, returns memory.
OpMOVQstoreSP // store arg0 to SP+aux.(int64). arg1=memory, returns memory.
// materialize a constant into a register
OpMOVQconst // (takes no arguments)
// move memory
OpREPMOVSB // arg0=destptr, arg1=srcptr, arg2=len, arg3=mem
)
type regMask uint64
......@@ -89,13 +91,16 @@ var regsAMD64 = [...]string{
"R15",
// pseudo registers
"FP",
"FLAGS",
"OVERWRITE0", // the same register as the first input
}
var gp regMask = 0xef // all integer registers except SP
var cx regMask = 0x2
var flags regMask = 1 << 16
var gp regMask = 0x1ffff // all integer registers (including SP&FP)
var cx regMask = 1 << 1
var si regMask = 1 << 6
var di regMask = 1 << 7
var flags regMask = 1 << 17
var (
// gp = general purpose (integer) registers
......@@ -129,13 +134,16 @@ var amd64Table = map[Op]opInfo{
OpCMPQ: {asm: "CMPQ\t%I0,%I1", reg: gp2_flags}, // compute arg[0]-arg[1] and produce flags
OpCMPCQ: {asm: "CMPQ\t$%A,%I0", reg: gp1_flags},
OpTESTQ: {asm: "TESTQ\t%I0,%I1", reg: gp2_flags},
OpTESTB: {asm: "TESTB\t%I0,%I1", reg: gp2_flags},
OpLEAQ: {flags: OpFlagCommutative, asm: "LEAQ\t%A(%I0)(%I1*1),%O0", reg: gp21}, // aux = int64 constant to add
OpLEAQ2: {asm: "LEAQ\t%A(%I0)(%I1*2),%O0"},
OpLEAQ4: {asm: "LEAQ\t%A(%I0)(%I1*4),%O0"},
OpLEAQ8: {asm: "LEAQ\t%A(%I0)(%I1*8),%O0"},
OpLEAQ: {flags: OpFlagCommutative, asm: "LEAQ\t%A(%I0)(%I1*1),%O0", reg: gp21}, // aux = int64 constant to add
OpLEAQ2: {asm: "LEAQ\t%A(%I0)(%I1*2),%O0"},
OpLEAQ4: {asm: "LEAQ\t%A(%I0)(%I1*4),%O0"},
OpLEAQ8: {asm: "LEAQ\t%A(%I0)(%I1*8),%O0"},
OpLEAQglobal: {asm: "LEAQ\t%A(SB),%O0", reg: gp01},
// loads and stores
OpMOVBload: {asm: "MOVB\t%A(%I0),%O0", reg: gpload},
OpMOVQload: {asm: "MOVQ\t%A(%I0),%O0", reg: gpload},
OpMOVQstore: {asm: "MOVQ\t%I1,%A(%I0)", reg: gpstore},
OpMOVQloadidx8: {asm: "MOVQ\t%A(%I0)(%I1*8),%O0", reg: gploadidx},
......@@ -145,23 +153,20 @@ var amd64Table = map[Op]opInfo{
OpStaticCall: {asm: "CALL\t%A(SB)"},
OpCopy: {asm: "MOVQ\t%I0,%O0", reg: gp11},
OpCopy: {asm: "MOVQ\t%I0,%O0", reg: gp11}, // TODO: make arch-specific
OpConvNop: {asm: "MOVQ\t%I0,%O0", reg: gp11}, // TODO: make arch-specific. Or get rid of this altogether.
// convert from flags back to boolean
OpSETL: {},
// ops for load/store to stack
OpMOVQloadFP: {asm: "MOVQ\t%A(FP),%O0", reg: gpload_stack}, // mem -> value
OpMOVQloadSP: {asm: "MOVQ\t%A(SP),%O0", reg: gpload_stack}, // mem -> value
OpMOVQstoreFP: {asm: "MOVQ\t%I0,%A(FP)", reg: gpstore_stack}, // mem, value -> mem
OpMOVQstoreSP: {asm: "MOVQ\t%I0,%A(SP)", reg: gpstore_stack}, // mem, value -> mem
// ops for spilling of registers
// unlike regular loads & stores, these take no memory argument.
// They are just like OpCopy but we use them during register allocation.
// TODO: different widths, float
OpLoadReg8: {asm: "MOVQ\t%I0,%O0"},
OpStoreReg8: {asm: "MOVQ\t%I0,%O0"},
OpREPMOVSB: {asm: "REP MOVSB", reg: [2][]regMask{{di, si, cx, 0}, {0}}}, // TODO: record that si/di/cx are clobbered
}
func init() {
......
......@@ -39,8 +39,9 @@ var registers = [...]Register{
// TODO X0, ...
// TODO: make arch-dependent
Register{16, "FLAGS"},
Register{17, "OVERWRITE"},
Register{16, "FP"}, // pseudo-register, actually a constant offset from SP
Register{17, "FLAGS"},
Register{18, "OVERWRITE"},
}
// countRegs returns the number of set bits in the register mask.
......@@ -84,6 +85,19 @@ func regalloc(f *Func) {
var oldSched []*Value
// Hack to find fp, sp Values and assign them a register. (TODO: make not so hacky)
var fp, sp *Value
for _, v := range f.Entry.Values {
switch v.Op {
case OpSP:
sp = v
home = setloc(home, v, &registers[4]) // TODO: arch-dependent
case OpFP:
fp = v
home = setloc(home, v, &registers[16]) // TODO: arch-dependent
}
}
// Register allocate each block separately. All live values will live
// in home locations (stack slots) between blocks.
for _, b := range f.Blocks {
......@@ -115,6 +129,10 @@ func regalloc(f *Func) {
}
regs := make([]regInfo, numRegs)
// TODO: hack: initialize fixed registers
regs[4] = regInfo{sp, sp, false}
regs[16] = regInfo{fp, fp, false}
var used regMask // has a 1 for each non-nil entry in regs
var dirty regMask // has a 1 for each dirty entry in regs
......@@ -133,9 +151,6 @@ func regalloc(f *Func) {
// - definition of v. c will be identical to v but will live in
// a register. v will be modified into a spill of c.
regspec := opcodeTable[v.Op].reg
if v.Op == OpConvNop {
regspec = opcodeTable[v.Args[0].Op].reg
}
inputs := regspec[0]
outputs := regspec[1]
if len(inputs) == 0 && len(outputs) == 0 {
......@@ -154,6 +169,7 @@ func regalloc(f *Func) {
// nospill contains registers that we can't spill because
// we already set them up for use by the current instruction.
var nospill regMask
nospill |= 0x10010 // SP and FP can't be spilled (TODO: arch-specific)
// Move inputs into registers
for _, o := range order {
......@@ -215,10 +231,16 @@ func regalloc(f *Func) {
// Load w into this register
var c *Value
if w.Op == OpConst {
if len(w.Args) == 0 {
// Materialize w
// TODO: arch-specific MOV op
c = b.NewValue(OpMOVQconst, w.Type, w.Aux)
if w.Op == OpFP || w.Op == OpSP || w.Op == OpGlobal {
c = b.NewValue1(OpCopy, w.Type, nil, w)
} else {
c = b.NewValue(w.Op, w.Type, w.Aux)
}
} else if len(w.Args) == 1 && (w.Args[0].Op == OpFP || w.Args[0].Op == OpSP || w.Args[0].Op == OpGlobal) {
// Materialize offsets from SP/FP/Global
c = b.NewValue1(w.Op, w.Type, w.Aux, w.Args[0])
} else if wreg != 0 {
// Copy from another register.
// Typically just an optimization, but this is
......@@ -317,6 +339,10 @@ func regalloc(f *Func) {
v := regs[r].v
c := regs[r].c
if lastUse[v.ID] <= len(oldSched) {
if v == v.Block.Control {
// link control value to register version
v.Block.Control = c
}
continue // not live after block
}
......@@ -334,6 +360,7 @@ func regalloc(f *Func) {
}
}
f.RegAlloc = home
deadcode(f) // remove values that had all of their uses rematerialized. TODO: separate pass?
}
// addPhiCopies adds copies of phi inputs in the blocks
......
......@@ -4,14 +4,14 @@
package ssa
import "fmt"
import "log"
func applyRewrite(f *Func, r func(*Value) bool) {
// repeat rewrites until we find no more rewrites
var curv *Value
defer func() {
if curv != nil {
fmt.Printf("panic during rewrite of %s\n", curv.LongString())
log.Printf("panic during rewrite of %s\n", curv.LongString())
// TODO(khr): print source location also
}
}()
......@@ -19,6 +19,18 @@ func applyRewrite(f *Func, r func(*Value) bool) {
change := false
for _, b := range f.Blocks {
for _, v := range b.Values {
// elide any copies generated during rewriting
for i, a := range v.Args {
if a.Op != OpCopy {
continue
}
for a.Op == OpCopy {
a = a.Args[0]
}
v.Args[i] = a
}
// apply rewrite function
curv = v
if r(v) {
change = true
......@@ -26,6 +38,7 @@ func applyRewrite(f *Func, r func(*Value) bool) {
}
}
if !change {
curv = nil
return
}
}
......@@ -52,3 +65,19 @@ func isSigned(t Type) bool {
func typeSize(t Type) int64 {
return t.Size()
}
// addOff adds two offset aux values. Each should be an int64. Fails if wraparound happens.
func addOff(a, b interface{}) interface{} {
x := a.(int64)
y := b.(int64)
z := x + y
// x and y have same sign and z has a different sign => overflow
if x^y >= 0 && x^z < 0 {
log.Panicf("offset overflow %d %d\n", x, y)
}
return z
}
func inBounds(idx, len int64) bool {
return idx >= 0 && idx < len
}
......@@ -3,17 +3,22 @@
// license that can be found in the LICENSE file.
// constant folding
(Add <t> (Const [c]) (Const [d])) && is64BitInt(t) && isSigned(t) -> (Const [{c.(int64)+d.(int64)}])
(Add <t> (Const [c]) (Const [d])) && is64BitInt(t) && !isSigned(t) -> (Const [{c.(uint64)+d.(uint64)}])
(Add <t> (Const [c]) (Const [d])) && is64BitInt(t) -> (Const [{c.(int64)+d.(int64)}])
(Mul <t> (Const [c]) (Const [d])) && is64BitInt(t) -> (Const [{c.(int64)*d.(int64)}])
(IsInBounds (Const [c]) (Const [d])) -> (Const [inBounds(c.(int64),d.(int64))])
// tear apart slices
// TODO: anything that generates a slice needs to go in here.
(SlicePtr (Load ptr mem)) -> (Load ptr mem)
(SliceLen (Load ptr mem)) -> (Load (Add <ptr.Type> ptr (Const <v.Block.Func.Config.UIntPtr> [int64(v.Block.Func.Config.ptrSize)])) mem)
(SliceCap (Load ptr mem)) -> (Load (Add <ptr.Type> ptr (Const <v.Block.Func.Config.UIntPtr> [int64(v.Block.Func.Config.ptrSize*2)])) mem)
// expand array indexing
// others? Depends on what is already done by frontend
(SliceLen (Load ptr mem)) -> (Load (Add <ptr.Type> ptr (Const <v.Block.Func.Config.Uintptr> [int64(v.Block.Func.Config.ptrSize)])) mem)
(SliceCap (Load ptr mem)) -> (Load (Add <ptr.Type> ptr (Const <v.Block.Func.Config.Uintptr> [int64(v.Block.Func.Config.ptrSize*2)])) mem)
// indexing operations
// Note: bounds check has already been done
(SliceIndex s i mem) -> (Load (Add <s.Type.Elem().PtrTo()> (SlicePtr <s.Type.Elem().PtrTo()> s) (Mul <v.Block.Func.Config.UIntPtr> i (Const <v.Block.Func.Config.UIntPtr> [s.Type.Elem().Size()]))) mem)
(ArrayIndex (Load ptr mem) idx) -> (Load (PtrIndex <ptr.Type.Elem().Elem().PtrTo()> ptr idx) mem)
(PtrIndex <t> ptr idx) -> (Add ptr (Mul <v.Block.Func.Config.Uintptr> idx (Const <v.Block.Func.Config.Uintptr> [t.Elem().Size()])))
// TODO: hopefully this will get rid of all full-width array copies.
// big-object moves
// TODO: fix size
(Store dst (Load <t> src mem) mem) && t.Size() > 8 -> (Move [t.Size()] dst src mem)
......@@ -30,6 +30,7 @@
(Less x y) && is64BitInt(v.Args[0].Type) && isSigned(v.Args[0].Type) -> (SETL (CMPQ <TypeFlags> x y))
(Load <t> ptr mem) && t.IsBoolean() -> (MOVBload [int64(0)] ptr mem)
(Load <t> ptr mem) && (is64BitInt(t) || isPtr(t)) -> (MOVQload [int64(0)] ptr mem)
(Store ptr val mem) && (is64BitInt(val.Type) || isPtr(val.Type)) -> (MOVQstore [int64(0)] ptr val mem)
......@@ -37,28 +38,27 @@
(IsNonNil p) -> (SETNE (TESTQ <TypeFlags> p p))
(IsInBounds idx len) -> (SETB (CMPQ <TypeFlags> idx len))
(Move [size] dst src mem) -> (REPMOVSB dst src (Const <TypeUInt64> [size.(int64)]) mem)
(OffPtr [off] ptr) -> (ADDCQ [off] ptr)
(Const <t> [val]) && is64BitInt(t) -> (MOVQconst [val])
// Rules below here apply some simple optimizations after lowering.
// TODO: Should this be a separate pass?
// stack loads/stores
(MOVQload [off1] (FPAddr [off2]) mem) -> (MOVQloadFP [off1.(int64)+off2.(int64)] mem)
(MOVQload [off1] (SPAddr [off2]) mem) -> (MOVQloadSP [off1.(int64)+off2.(int64)] mem)
(MOVQstore [off1] (FPAddr [off2]) val mem) -> (MOVQstoreFP [off1.(int64)+off2.(int64)] val mem)
(MOVQstore [off1] (SPAddr [off2]) val mem) -> (MOVQstoreSP [off1.(int64)+off2.(int64)] val mem)
// global loads/stores
(MOVQload [off] (Global [sym]) mem) -> (MOVQloadglobal [GlobalOffset{sym,off.(int64)}] mem)
(MOVQstore [off] (Global [sym]) val mem) -> (MOVQstoreglobal [GlobalOffset{sym,off.(int64)}] val mem)
(Global [sym]) -> (LEAQglobal [GlobalOffset{sym,0}])
// fold constants into instructions
(ADDQ x (Const [c])) -> (ADDCQ [c] x) // TODO: restrict c to int32 range?
(ADDQ (Const [c]) x) -> (ADDCQ [c] x)
(SUBQ x (Const [c])) -> (SUBCQ x [c])
(SUBQ <t> (Const [c]) x) -> (NEGQ (SUBCQ <t> x [c]))
(MULQ x (Const [c])) -> (MULCQ [c] x)
(MULQ (Const [c]) x) -> (MULCQ [c] x)
(CMPQ x (Const [c])) -> (CMPCQ x [c])
(CMPQ (Const [c]) x) -> (InvertFlags (CMPCQ <TypeFlags> x [c]))
(ADDQ x (MOVQconst [c])) -> (ADDCQ [c] x) // TODO: restrict c to int32 range?
(ADDQ (MOVQconst [c]) x) -> (ADDCQ [c] x)
(SUBQ x (MOVQconst [c])) -> (SUBCQ x [c])
(SUBQ <t> (MOVQconst [c]) x) -> (NEGQ (SUBCQ <t> x [c]))
(MULQ x (MOVQconst [c])) -> (MULCQ [c] x)
(MULQ (MOVQconst [c]) x) -> (MULCQ [c] x)
(CMPQ x (MOVQconst [c])) -> (CMPCQ x [c])
(CMPQ (MOVQconst [c]) x) -> (InvertFlags (CMPCQ <TypeFlags> x [c]))
// strength reduction
// TODO: do this a lot more generically
......@@ -66,7 +66,7 @@
// fold add/shift into leaq
(ADDQ x (SHLCQ [shift] y)) && shift.(int64) == 3 -> (LEAQ8 [int64(0)] x y)
(ADDCQ [c] (LEAQ8 [d] x y)) -> (LEAQ8 [c.(int64)+d.(int64)] x y)
(ADDCQ [c] (LEAQ8 [d] x y)) -> (LEAQ8 [addOff(c, d)] x y)
// reverse ordering of compare instruction
(SETL (InvertFlags x)) -> (SETGE x)
......@@ -76,13 +76,14 @@
// the ADDCQ get eliminated, we still have to compute the ADDCQ and we now
// have potentially two live values (ptr and (ADDCQ [off] ptr)) instead of one.
// Nevertheless, let's do it!
(MOVQload [off1] (ADDCQ [off2] ptr) mem) -> (MOVQload [off1.(int64)+off2.(int64)] ptr mem)
(MOVQstore [off1] (ADDCQ [off2] ptr) val mem) -> (MOVQstore [off1.(int64)+off2.(int64)] ptr val mem)
(MOVQload [off1] (ADDCQ [off2] ptr) mem) -> (MOVQload [addOff(off1, off2)] ptr mem)
(MOVQstore [off1] (ADDCQ [off2] ptr) val mem) -> (MOVQstore [addOff(off1, off2)] ptr val mem)
// indexed loads and stores
(MOVQload [off1] (LEAQ8 [off2] ptr idx) mem) -> (MOVQloadidx8 [off1.(int64)+off2.(int64)] ptr idx mem)
(MOVQstore [off1] (LEAQ8 [off2] ptr idx) val mem) -> (MOVQstoreidx8 [off1.(int64)+off2.(int64)] ptr idx val mem)
(MOVQload [off1] (LEAQ8 [off2] ptr idx) mem) -> (MOVQloadidx8 [addOff(off1, off2)] ptr idx mem)
(MOVQstore [off1] (LEAQ8 [off2] ptr idx) val mem) -> (MOVQstoreidx8 [addOff(off1, off2)] ptr idx val mem)
(MOVQloadidx8 [off1] (ADDCQ [off2] ptr) idx mem) -> (MOVQloadidx8 [addOff(off1, off2)] ptr idx mem)
(MOVQstoreidx8 [off1] (ADDCQ [off2] ptr) idx val mem) -> (MOVQstoreidx8 [addOff(off1, off2)] ptr idx val mem)
// Combine the offset of a stack object with the offset within a stack object
(ADDCQ [off1] (FPAddr [off2])) -> (FPAddr [off1.(int64)+off2.(int64)])
(ADDCQ [off1] (SPAddr [off2])) -> (SPAddr [off1.(int64)+off2.(int64)])
(ADDCQ [off] x) && off.(int64) == 0 -> (Copy x)
......@@ -245,6 +245,12 @@ func genResult(w io.Writer, result string) {
func genResult0(w io.Writer, result string, alloc *int, top bool) string {
if result[0] != '(' {
// variable
if top {
fmt.Fprintf(w, "v.Op = %s.Op\n", result)
fmt.Fprintf(w, "v.Aux = %s.Aux\n", result)
fmt.Fprintf(w, "v.resetArgs()\n")
fmt.Fprintf(w, "v.AddArgs(%s.Args...)\n", result)
}
return result
}
......@@ -297,20 +303,33 @@ func split(s string) []string {
outer:
for s != "" {
d := 0 // depth of ({[<
nonsp := false // found a non-space char so far
d := 0 // depth of ({[<
var open, close byte // opening and closing markers ({[< or )}]>
nonsp := false // found a non-space char so far
for i := 0; i < len(s); i++ {
switch s[i] {
case '(', '{', '[', '<':
switch {
case d == 0 && s[i] == '(':
open, close = '(', ')'
d++
case ')', '}', ']', '>':
d--
case ' ', '\t':
if d == 0 && nonsp {
case d == 0 && s[i] == '<':
open, close = '<', '>'
d++
case d == 0 && s[i] == '[':
open, close = '[', ']'
d++
case d == 0 && s[i] == '{':
open, close = '{', '}'
d++
case d == 0 && (s[i] == ' ' || s[i] == '\t'):
if nonsp {
r = append(r, strings.TrimSpace(s[:i]))
s = s[i:]
continue outer
}
case d > 0 && s[i] == open:
d++
case d > 0 && s[i] == close:
d--
default:
nonsp = true
}
......
......@@ -15,6 +15,9 @@ func stackalloc(f *Func) {
if v.Op != OpPhi {
continue
}
if v.Type.IsMemory() { // TODO: only "regallocable" types
continue
}
n += v.Type.Size()
// a := v.Type.Align()
// n = (n + a - 1) / a * a TODO
......@@ -35,10 +38,11 @@ func stackalloc(f *Func) {
if v.Type.IsMemory() { // TODO: only "regallocable" types
continue
}
if v.Op == OpConst {
// don't allocate space for OpConsts. They should
// have been rematerialized everywhere.
// TODO: is this the right thing to do?
if len(v.Args) == 0 {
// v will have been materialized wherever it is needed.
continue
}
if len(v.Args) == 1 && (v.Args[0].Op == OpFP || v.Args[0].Op == OpSP || v.Args[0].Op == OpGlobal) {
continue
}
// a := v.Type.Align()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment