mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
cmd/compile: add support for GOARCH=mips{,le}
Change-Id: Ib489dc847787aaab7ba1be96792f885469e346ae Reviewed-on: https://go-review.googlesource.com/31479 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Cherry Zhang <cherryyz@google.com>
This commit is contained in:
parent
247fc4a98e
commit
f4c997578a
4 changed files with 1191 additions and 0 deletions
26
src/cmd/compile/internal/mips/galign.go
Normal file
26
src/cmd/compile/internal/mips/galign.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package mips
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmd/compile/internal/gc"
|
||||||
|
"cmd/compile/internal/ssa"
|
||||||
|
"cmd/internal/obj"
|
||||||
|
"cmd/internal/obj/mips"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
gc.Thearch.LinkArch = &mips.Linkmips
|
||||||
|
if obj.GOARCH == "mipsle" {
|
||||||
|
gc.Thearch.LinkArch = &mips.Linkmipsle
|
||||||
|
}
|
||||||
|
gc.Thearch.REGSP = mips.REGSP
|
||||||
|
gc.Thearch.MAXWIDTH = (1 << 31) - 1
|
||||||
|
gc.Thearch.Defframe = defframe
|
||||||
|
gc.Thearch.Proginfo = proginfo
|
||||||
|
gc.Thearch.SSAMarkMoves = func(s *gc.SSAGenState, b *ssa.Block) {}
|
||||||
|
gc.Thearch.SSAGenValue = ssaGenValue
|
||||||
|
gc.Thearch.SSAGenBlock = ssaGenBlock
|
||||||
|
}
|
||||||
101
src/cmd/compile/internal/mips/ggen.go
Normal file
101
src/cmd/compile/internal/mips/ggen.go
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package mips
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmd/compile/internal/gc"
|
||||||
|
"cmd/internal/obj"
|
||||||
|
"cmd/internal/obj/mips"
|
||||||
|
)
|
||||||
|
|
||||||
|
func defframe(ptxt *obj.Prog) {
|
||||||
|
// fill in argument size, stack size
|
||||||
|
ptxt.To.Type = obj.TYPE_TEXTSIZE
|
||||||
|
|
||||||
|
ptxt.To.Val = int32(gc.Rnd(gc.Curfn.Type.ArgWidth(), int64(gc.Widthptr)))
|
||||||
|
frame := uint32(gc.Rnd(gc.Stksize+gc.Maxarg, int64(gc.Widthreg)))
|
||||||
|
ptxt.To.Offset = int64(frame)
|
||||||
|
|
||||||
|
// insert code to zero ambiguously live variables
|
||||||
|
// so that the garbage collector only sees initialized values
|
||||||
|
// when it looks for pointers.
|
||||||
|
p := ptxt
|
||||||
|
|
||||||
|
hi := int64(0)
|
||||||
|
lo := hi
|
||||||
|
|
||||||
|
// iterate through declarations - they are sorted in decreasing xoffset order.
|
||||||
|
for _, n := range gc.Curfn.Func.Dcl {
|
||||||
|
if !n.Name.Needzero {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n.Class != gc.PAUTO {
|
||||||
|
gc.Fatalf("needzero class %d", n.Class)
|
||||||
|
}
|
||||||
|
if n.Type.Width%int64(gc.Widthptr) != 0 || n.Xoffset%int64(gc.Widthptr) != 0 || n.Type.Width == 0 {
|
||||||
|
gc.Fatalf("var %L has size %d offset %d", n, int(n.Type.Width), int(n.Xoffset))
|
||||||
|
}
|
||||||
|
|
||||||
|
if lo != hi && n.Xoffset+n.Type.Width >= lo-int64(2*gc.Widthreg) {
|
||||||
|
// merge with range we already have
|
||||||
|
lo = n.Xoffset
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// zero old range
|
||||||
|
p = zerorange(p, int64(frame), lo, hi)
|
||||||
|
|
||||||
|
// set new range
|
||||||
|
hi = n.Xoffset + n.Type.Width
|
||||||
|
|
||||||
|
lo = n.Xoffset
|
||||||
|
}
|
||||||
|
|
||||||
|
// zero final range
|
||||||
|
zerorange(p, int64(frame), lo, hi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(mips): implement DUFFZERO
|
||||||
|
func zerorange(p *obj.Prog, frame int64, lo int64, hi int64) *obj.Prog {
|
||||||
|
|
||||||
|
cnt := hi - lo
|
||||||
|
if cnt == 0 {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
if cnt < int64(4*gc.Widthptr) {
|
||||||
|
for i := int64(0); i < cnt; i += int64(gc.Widthptr) {
|
||||||
|
p = gc.Appendpp(p, mips.AMOVW, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGSP, gc.Ctxt.FixedFrameSize()+frame+lo+i)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//fmt.Printf("zerorange frame:%v, lo: %v, hi:%v \n", frame ,lo, hi)
|
||||||
|
// ADD $(FIXED_FRAME+frame+lo-4), SP, r1
|
||||||
|
// ADD $cnt, r1, r2
|
||||||
|
// loop:
|
||||||
|
// MOVW R0, (Widthptr)r1
|
||||||
|
// ADD $Widthptr, r1
|
||||||
|
// BNE r1, r2, loop
|
||||||
|
p = gc.Appendpp(p, mips.AADD, obj.TYPE_CONST, 0, gc.Ctxt.FixedFrameSize()+frame+lo-4, obj.TYPE_REG, mips.REGRT1, 0)
|
||||||
|
p.Reg = mips.REGSP
|
||||||
|
p = gc.Appendpp(p, mips.AADD, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, mips.REGRT2, 0)
|
||||||
|
p.Reg = mips.REGRT1
|
||||||
|
p = gc.Appendpp(p, mips.AMOVW, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGRT1, int64(gc.Widthptr))
|
||||||
|
p1 := p
|
||||||
|
p = gc.Appendpp(p, mips.AADD, obj.TYPE_CONST, 0, int64(gc.Widthptr), obj.TYPE_REG, mips.REGRT1, 0)
|
||||||
|
p = gc.Appendpp(p, mips.ABNE, obj.TYPE_REG, mips.REGRT1, 0, obj.TYPE_BRANCH, 0, 0)
|
||||||
|
p.Reg = mips.REGRT2
|
||||||
|
gc.Patch(p, p1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func ginsnop() {
|
||||||
|
p := gc.Prog(mips.ANOR)
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = mips.REG_R0
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = mips.REG_R0
|
||||||
|
}
|
||||||
157
src/cmd/compile/internal/mips/prog.go
Normal file
157
src/cmd/compile/internal/mips/prog.go
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package mips
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmd/compile/internal/gc"
|
||||||
|
"cmd/internal/obj"
|
||||||
|
"cmd/internal/obj/mips"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LeftRdwr uint32 = gc.LeftRead | gc.LeftWrite
|
||||||
|
RightRdwr uint32 = gc.RightRead | gc.RightWrite
|
||||||
|
)
|
||||||
|
|
||||||
|
// This table gives the basic information about instruction
|
||||||
|
// generated by the compiler and processed in the optimizer.
|
||||||
|
// See opt.h for bit definitions.
|
||||||
|
//
|
||||||
|
// Instructions not generated need not be listed.
|
||||||
|
// As an exception to that rule, we typically write down all the
|
||||||
|
// size variants of an operation even if we just use a subset.
|
||||||
|
//
|
||||||
|
// The table is formatted for 8-space tabs.
|
||||||
|
var progtable = [mips.ALAST & obj.AMask]gc.ProgInfo{
|
||||||
|
obj.ATYPE: {Flags: gc.Pseudo | gc.Skip},
|
||||||
|
obj.ATEXT: {Flags: gc.Pseudo},
|
||||||
|
obj.AFUNCDATA: {Flags: gc.Pseudo},
|
||||||
|
obj.APCDATA: {Flags: gc.Pseudo},
|
||||||
|
obj.AUNDEF: {Flags: gc.Break},
|
||||||
|
obj.AUSEFIELD: {Flags: gc.OK},
|
||||||
|
obj.AVARDEF: {Flags: gc.Pseudo | gc.RightWrite},
|
||||||
|
obj.AVARKILL: {Flags: gc.Pseudo | gc.RightWrite},
|
||||||
|
obj.AVARLIVE: {Flags: gc.Pseudo | gc.LeftRead},
|
||||||
|
|
||||||
|
// NOP is an internal no-op that also stands
|
||||||
|
// for USED and SET annotations, not the MIPS opcode.
|
||||||
|
obj.ANOP: {Flags: gc.LeftRead | gc.RightWrite},
|
||||||
|
|
||||||
|
// Integer
|
||||||
|
mips.AADD & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.AADDU & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.ASUB & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.ASUBU & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.AAND & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.AOR & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.AXOR & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.ANOR & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.AMUL & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead},
|
||||||
|
mips.AMULU & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead},
|
||||||
|
mips.ADIV & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead},
|
||||||
|
mips.ADIVU & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead},
|
||||||
|
mips.ASLL & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.ASRA & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.ASRL & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.ASGT & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.ASGTU & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
|
||||||
|
mips.ACLZ & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightRead},
|
||||||
|
mips.ACLO & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightRead},
|
||||||
|
|
||||||
|
// Floating point.
|
||||||
|
mips.AADDF & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.AADDD & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.ASUBF & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.ASUBD & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.AMULF & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.AMULD & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.ADIVF & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.ADIVD & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RegRead | gc.RightWrite},
|
||||||
|
mips.AABSF & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RightWrite},
|
||||||
|
mips.AABSD & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightWrite},
|
||||||
|
mips.ANEGF & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RightWrite},
|
||||||
|
mips.ANEGD & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightWrite},
|
||||||
|
mips.ACMPEQF & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RegRead},
|
||||||
|
mips.ACMPEQD & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RegRead},
|
||||||
|
mips.ACMPGTF & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RegRead},
|
||||||
|
mips.ACMPGTD & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RegRead},
|
||||||
|
mips.ACMPGEF & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RegRead},
|
||||||
|
mips.ACMPGED & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RegRead},
|
||||||
|
mips.AMOVFD & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightWrite | gc.Conv},
|
||||||
|
mips.AMOVDF & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RightWrite | gc.Conv},
|
||||||
|
mips.AMOVFW & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightWrite | gc.Conv},
|
||||||
|
mips.AMOVWF & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RightWrite | gc.Conv},
|
||||||
|
mips.AMOVDW & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightWrite | gc.Conv},
|
||||||
|
mips.AMOVWD & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightWrite | gc.Conv},
|
||||||
|
mips.ATRUNCFW & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightWrite | gc.Conv},
|
||||||
|
mips.ATRUNCDW & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightWrite | gc.Conv},
|
||||||
|
|
||||||
|
mips.ASQRTF & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RightWrite},
|
||||||
|
mips.ASQRTD & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightWrite},
|
||||||
|
|
||||||
|
// Moves
|
||||||
|
mips.AMOVB & obj.AMask: {Flags: gc.SizeB | gc.LeftRead | gc.RightWrite | gc.Move | gc.Conv},
|
||||||
|
mips.AMOVBU & obj.AMask: {Flags: gc.SizeB | gc.LeftRead | gc.RightWrite | gc.Move | gc.Conv},
|
||||||
|
mips.AMOVH & obj.AMask: {Flags: gc.SizeW | gc.LeftRead | gc.RightWrite | gc.Move | gc.Conv},
|
||||||
|
mips.AMOVHU & obj.AMask: {Flags: gc.SizeW | gc.LeftRead | gc.RightWrite | gc.Move | gc.Conv},
|
||||||
|
mips.AMOVW & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightWrite | gc.Move | gc.Conv},
|
||||||
|
mips.AMOVF & obj.AMask: {Flags: gc.SizeF | gc.LeftRead | gc.RightWrite | gc.Move | gc.Conv},
|
||||||
|
mips.AMOVD & obj.AMask: {Flags: gc.SizeD | gc.LeftRead | gc.RightWrite | gc.Move | gc.Conv},
|
||||||
|
|
||||||
|
// Conditional moves
|
||||||
|
mips.ACMOVN & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | RightRdwr},
|
||||||
|
mips.ACMOVZ & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RegRead | RightRdwr},
|
||||||
|
mips.ACMOVT & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | RightRdwr},
|
||||||
|
mips.ACMOVF & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | RightRdwr},
|
||||||
|
|
||||||
|
// Conditional trap
|
||||||
|
mips.ATEQ & obj.AMask: {Flags: gc.SizeL | gc.RegRead | gc.RightRead},
|
||||||
|
mips.ATNE & obj.AMask: {Flags: gc.SizeL | gc.RegRead | gc.RightRead},
|
||||||
|
|
||||||
|
// Atomic
|
||||||
|
mips.ASYNC & obj.AMask: {Flags: gc.OK},
|
||||||
|
mips.ALL & obj.AMask: {Flags: gc.SizeL | gc.LeftRead | gc.RightWrite},
|
||||||
|
mips.ASC & obj.AMask: {Flags: gc.SizeL | LeftRdwr | gc.RightRead},
|
||||||
|
|
||||||
|
// Jumps
|
||||||
|
mips.AJMP & obj.AMask: {Flags: gc.Jump | gc.Break},
|
||||||
|
mips.AJAL & obj.AMask: {Flags: gc.Call},
|
||||||
|
mips.ABEQ & obj.AMask: {Flags: gc.Cjmp},
|
||||||
|
mips.ABNE & obj.AMask: {Flags: gc.Cjmp},
|
||||||
|
mips.ABGEZ & obj.AMask: {Flags: gc.Cjmp},
|
||||||
|
mips.ABLTZ & obj.AMask: {Flags: gc.Cjmp},
|
||||||
|
mips.ABGTZ & obj.AMask: {Flags: gc.Cjmp},
|
||||||
|
mips.ABLEZ & obj.AMask: {Flags: gc.Cjmp},
|
||||||
|
mips.ABFPF & obj.AMask: {Flags: gc.Cjmp},
|
||||||
|
mips.ABFPT & obj.AMask: {Flags: gc.Cjmp},
|
||||||
|
mips.ARET & obj.AMask: {Flags: gc.Break},
|
||||||
|
obj.ADUFFZERO: {Flags: gc.Call},
|
||||||
|
obj.ADUFFCOPY: {Flags: gc.Call},
|
||||||
|
}
|
||||||
|
|
||||||
|
func proginfo(p *obj.Prog) gc.ProgInfo {
|
||||||
|
info := progtable[p.As&obj.AMask]
|
||||||
|
|
||||||
|
if info.Flags == 0 {
|
||||||
|
gc.Fatalf("proginfo: unknown instruction %v", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.Flags&gc.RegRead != 0) && p.Reg == 0 {
|
||||||
|
info.Flags &^= gc.RegRead
|
||||||
|
info.Flags |= gc.RightRead
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.From.Type == obj.TYPE_ADDR && p.From.Sym != nil && (info.Flags&gc.LeftRead != 0) {
|
||||||
|
info.Flags &^= gc.LeftRead
|
||||||
|
info.Flags |= gc.LeftAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.As == mips.AMUL && p.To.Reg != 0 {
|
||||||
|
info.Flags |= gc.RightWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
907
src/cmd/compile/internal/mips/ssa.go
Normal file
907
src/cmd/compile/internal/mips/ssa.go
Normal file
|
|
@ -0,0 +1,907 @@
|
||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
package mips
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"cmd/compile/internal/gc"
|
||||||
|
"cmd/compile/internal/ssa"
|
||||||
|
"cmd/internal/obj"
|
||||||
|
"cmd/internal/obj/mips"
|
||||||
|
)
|
||||||
|
|
||||||
|
// isFPreg returns whether r is an FP register
|
||||||
|
func isFPreg(r int16) bool {
|
||||||
|
return mips.REG_F0 <= r && r <= mips.REG_F31
|
||||||
|
}
|
||||||
|
|
||||||
|
// isHILO returns whether r is HI or LO register
|
||||||
|
func isHILO(r int16) bool {
|
||||||
|
return r == mips.REG_HI || r == mips.REG_LO
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadByType returns the load instruction of the given type.
|
||||||
|
func loadByType(t ssa.Type, r int16) obj.As {
|
||||||
|
if isFPreg(r) {
|
||||||
|
if t.Size() == 4 { // float32 or int32
|
||||||
|
return mips.AMOVF
|
||||||
|
} else { // float64 or int64
|
||||||
|
return mips.AMOVD
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch t.Size() {
|
||||||
|
case 1:
|
||||||
|
if t.IsSigned() {
|
||||||
|
return mips.AMOVB
|
||||||
|
} else {
|
||||||
|
return mips.AMOVBU
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
if t.IsSigned() {
|
||||||
|
return mips.AMOVH
|
||||||
|
} else {
|
||||||
|
return mips.AMOVHU
|
||||||
|
}
|
||||||
|
case 4:
|
||||||
|
return mips.AMOVW
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("bad load type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeByType returns the store instruction of the given type.
|
||||||
|
func storeByType(t ssa.Type, r int16) obj.As {
|
||||||
|
if isFPreg(r) {
|
||||||
|
if t.Size() == 4 { // float32 or int32
|
||||||
|
return mips.AMOVF
|
||||||
|
} else { // float64 or int64
|
||||||
|
return mips.AMOVD
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch t.Size() {
|
||||||
|
case 1:
|
||||||
|
return mips.AMOVB
|
||||||
|
case 2:
|
||||||
|
return mips.AMOVH
|
||||||
|
case 4:
|
||||||
|
return mips.AMOVW
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("bad store type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
|
||||||
|
s.SetLineno(v.Line)
|
||||||
|
switch v.Op {
|
||||||
|
case ssa.OpInitMem:
|
||||||
|
// memory arg needs no code
|
||||||
|
case ssa.OpArg:
|
||||||
|
// input args need no code
|
||||||
|
case ssa.OpSP, ssa.OpSB, ssa.OpGetG:
|
||||||
|
// nothing to do
|
||||||
|
case ssa.OpSelect0, ssa.OpSelect1:
|
||||||
|
// nothing to do
|
||||||
|
case ssa.OpCopy, ssa.OpMIPSMOVWconvert, ssa.OpMIPSMOVWreg:
|
||||||
|
t := v.Type
|
||||||
|
if t.IsMemory() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
x := v.Args[0].Reg()
|
||||||
|
y := v.Reg()
|
||||||
|
if x == y {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
as := mips.AMOVW
|
||||||
|
if isFPreg(x) && isFPreg(y) {
|
||||||
|
as = mips.AMOVF
|
||||||
|
if t.Size() == 8 {
|
||||||
|
as = mips.AMOVD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p := gc.Prog(as)
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = x
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = y
|
||||||
|
if isHILO(x) && isHILO(y) || isHILO(x) && isFPreg(y) || isFPreg(x) && isHILO(y) {
|
||||||
|
// cannot move between special registers, use TMP as intermediate
|
||||||
|
p.To.Reg = mips.REGTMP
|
||||||
|
p = gc.Prog(mips.AMOVW)
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = mips.REGTMP
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = y
|
||||||
|
}
|
||||||
|
case ssa.OpMIPSMOVWnop:
|
||||||
|
if v.Reg() != v.Args[0].Reg() {
|
||||||
|
v.Fatalf("input[0] and output not in same register %s", v.LongString())
|
||||||
|
}
|
||||||
|
// nothing to do
|
||||||
|
case ssa.OpLoadReg:
|
||||||
|
if v.Type.IsFlags() {
|
||||||
|
v.Fatalf("load flags not implemented: %v", v.LongString())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r := v.Reg()
|
||||||
|
p := gc.Prog(loadByType(v.Type, r))
|
||||||
|
gc.AddrAuto(&p.From, v.Args[0])
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = r
|
||||||
|
if isHILO(r) {
|
||||||
|
// cannot directly load, load to TMP and move
|
||||||
|
p.To.Reg = mips.REGTMP
|
||||||
|
p = gc.Prog(mips.AMOVW)
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = mips.REGTMP
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = r
|
||||||
|
}
|
||||||
|
case ssa.OpStoreReg:
|
||||||
|
if v.Type.IsFlags() {
|
||||||
|
v.Fatalf("store flags not implemented: %v", v.LongString())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r := v.Args[0].Reg()
|
||||||
|
if isHILO(r) {
|
||||||
|
// cannot directly store, move to TMP and store
|
||||||
|
p := gc.Prog(mips.AMOVW)
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = r
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = mips.REGTMP
|
||||||
|
r = mips.REGTMP
|
||||||
|
}
|
||||||
|
p := gc.Prog(storeByType(v.Type, r))
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = r
|
||||||
|
gc.AddrAuto(&p.To, v)
|
||||||
|
case ssa.OpMIPSADD,
|
||||||
|
ssa.OpMIPSSUB,
|
||||||
|
ssa.OpMIPSAND,
|
||||||
|
ssa.OpMIPSOR,
|
||||||
|
ssa.OpMIPSXOR,
|
||||||
|
ssa.OpMIPSNOR,
|
||||||
|
ssa.OpMIPSSLL,
|
||||||
|
ssa.OpMIPSSRL,
|
||||||
|
ssa.OpMIPSSRA,
|
||||||
|
ssa.OpMIPSADDF,
|
||||||
|
ssa.OpMIPSADDD,
|
||||||
|
ssa.OpMIPSSUBF,
|
||||||
|
ssa.OpMIPSSUBD,
|
||||||
|
ssa.OpMIPSMULF,
|
||||||
|
ssa.OpMIPSMULD,
|
||||||
|
ssa.OpMIPSDIVF,
|
||||||
|
ssa.OpMIPSDIVD,
|
||||||
|
ssa.OpMIPSMUL:
|
||||||
|
p := gc.Prog(v.Op.Asm())
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = v.Args[1].Reg()
|
||||||
|
p.Reg = v.Args[0].Reg()
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg()
|
||||||
|
case ssa.OpMIPSSGT,
|
||||||
|
ssa.OpMIPSSGTU:
|
||||||
|
p := gc.Prog(v.Op.Asm())
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = v.Args[0].Reg()
|
||||||
|
p.Reg = v.Args[1].Reg()
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg()
|
||||||
|
case ssa.OpMIPSSGTzero,
|
||||||
|
ssa.OpMIPSSGTUzero:
|
||||||
|
p := gc.Prog(v.Op.Asm())
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = v.Args[0].Reg()
|
||||||
|
p.Reg = mips.REGZERO
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg()
|
||||||
|
case ssa.OpMIPSADDconst,
|
||||||
|
ssa.OpMIPSSUBconst,
|
||||||
|
ssa.OpMIPSANDconst,
|
||||||
|
ssa.OpMIPSORconst,
|
||||||
|
ssa.OpMIPSXORconst,
|
||||||
|
ssa.OpMIPSNORconst,
|
||||||
|
ssa.OpMIPSSLLconst,
|
||||||
|
ssa.OpMIPSSRLconst,
|
||||||
|
ssa.OpMIPSSRAconst,
|
||||||
|
ssa.OpMIPSSGTconst,
|
||||||
|
ssa.OpMIPSSGTUconst:
|
||||||
|
p := gc.Prog(v.Op.Asm())
|
||||||
|
p.From.Type = obj.TYPE_CONST
|
||||||
|
p.From.Offset = v.AuxInt
|
||||||
|
p.Reg = v.Args[0].Reg()
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg()
|
||||||
|
case ssa.OpMIPSMULT,
|
||||||
|
ssa.OpMIPSMULTU,
|
||||||
|
ssa.OpMIPSDIV,
|
||||||
|
ssa.OpMIPSDIVU:
|
||||||
|
// result in hi,lo
|
||||||
|
p := gc.Prog(v.Op.Asm())
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = v.Args[1].Reg()
|
||||||
|
p.Reg = v.Args[0].Reg()
|
||||||
|
case ssa.OpMIPSMOVWconst:
|
||||||
|
r := v.Reg()
|
||||||
|
p := gc.Prog(v.Op.Asm())
|
||||||
|
p.From.Type = obj.TYPE_CONST
|
||||||
|
p.From.Offset = v.AuxInt
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = r
|
||||||
|
if isFPreg(r) || isHILO(r) {
|
||||||
|
// cannot move into FP or special registers, use TMP as intermediate
|
||||||
|
p.To.Reg = mips.REGTMP
|
||||||
|
p = gc.Prog(mips.AMOVW)
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = mips.REGTMP
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = r
|
||||||
|
}
|
||||||
|
case ssa.OpMIPSMOVFconst,
|
||||||
|
ssa.OpMIPSMOVDconst:
|
||||||
|
p := gc.Prog(v.Op.Asm())
|
||||||
|
p.From.Type = obj.TYPE_FCONST
|
||||||
|
p.From.Val = math.Float64frombits(uint64(v.AuxInt))
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg()
|
||||||
|
case ssa.OpMIPSCMOVZ:
|
||||||
|
if v.Reg() != v.Args[0].Reg() {
|
||||||
|
v.Fatalf("input[0] and output not in same register %s", v.LongString())
|
||||||
|
}
|
||||||
|
p := gc.Prog(v.Op.Asm())
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = v.Args[2].Reg()
|
||||||
|
p.Reg = v.Args[1].Reg()
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg()
|
||||||
|
case ssa.OpMIPSCMOVZzero:
|
||||||
|
if v.Reg() != v.Args[0].Reg() {
|
||||||
|
v.Fatalf("input[0] and output not in same register %s", v.LongString())
|
||||||
|
}
|
||||||
|
p := gc.Prog(v.Op.Asm())
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = v.Args[1].Reg()
|
||||||
|
p.Reg = mips.REGZERO
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg()
|
||||||
|
case ssa.OpMIPSCMPEQF,
|
||||||
|
ssa.OpMIPSCMPEQD,
|
||||||
|
ssa.OpMIPSCMPGEF,
|
||||||
|
ssa.OpMIPSCMPGED,
|
||||||
|
ssa.OpMIPSCMPGTF,
|
||||||
|
ssa.OpMIPSCMPGTD:
|
||||||
|
p := gc.Prog(v.Op.Asm())
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = v.Args[0].Reg()
|
||||||
|
p.Reg = v.Args[1].Reg()
|
||||||
|
case ssa.OpMIPSMOVWaddr:
|
||||||
|
p := gc.Prog(mips.AMOVW)
|
||||||
|
p.From.Type = obj.TYPE_ADDR
|
||||||
|
var wantreg string
|
||||||
|
// MOVW $sym+off(base), R
|
||||||
|
// the assembler expands it as the following:
|
||||||
|
// - base is SP: add constant offset to SP (R29)
|
||||||
|
// when constant is large, tmp register (R23) may be used
|
||||||
|
// - base is SB: load external address with relocation
|
||||||
|
switch v.Aux.(type) {
|
||||||
|
default:
|
||||||
|
v.Fatalf("aux is of unknown type %T", v.Aux)
|
||||||
|
case *ssa.ExternSymbol:
|
||||||
|
wantreg = "SB"
|
||||||
|
gc.AddAux(&p.From, v)
|
||||||
|
case *ssa.ArgSymbol, *ssa.AutoSymbol:
|
||||||
|
wantreg = "SP"
|
||||||
|
gc.AddAux(&p.From, v)
|
||||||
|
case nil:
|
||||||
|
// No sym, just MOVW $off(SP), R
|
||||||
|
wantreg = "SP"
|
||||||
|
p.From.Reg = mips.REGSP
|
||||||
|
p.From.Offset = v.AuxInt
|
||||||
|
}
|
||||||
|
if reg := v.Args[0].RegName(); reg != wantreg {
|
||||||
|
v.Fatalf("bad reg %s for symbol type %T, want %s", reg, v.Aux, wantreg)
|
||||||
|
}
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg()
|
||||||
|
case ssa.OpMIPSMOVBload,
|
||||||
|
ssa.OpMIPSMOVBUload,
|
||||||
|
ssa.OpMIPSMOVHload,
|
||||||
|
ssa.OpMIPSMOVHUload,
|
||||||
|
ssa.OpMIPSMOVWload,
|
||||||
|
ssa.OpMIPSMOVFload,
|
||||||
|
ssa.OpMIPSMOVDload:
|
||||||
|
p := gc.Prog(v.Op.Asm())
|
||||||
|
p.From.Type = obj.TYPE_MEM
|
||||||
|
p.From.Reg = v.Args[0].Reg()
|
||||||
|
gc.AddAux(&p.From, v)
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg()
|
||||||
|
case ssa.OpMIPSMOVBstore,
|
||||||
|
ssa.OpMIPSMOVHstore,
|
||||||
|
ssa.OpMIPSMOVWstore,
|
||||||
|
ssa.OpMIPSMOVFstore,
|
||||||
|
ssa.OpMIPSMOVDstore:
|
||||||
|
p := gc.Prog(v.Op.Asm())
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = v.Args[1].Reg()
|
||||||
|
p.To.Type = obj.TYPE_MEM
|
||||||
|
p.To.Reg = v.Args[0].Reg()
|
||||||
|
gc.AddAux(&p.To, v)
|
||||||
|
case ssa.OpMIPSMOVBstorezero,
|
||||||
|
ssa.OpMIPSMOVHstorezero,
|
||||||
|
ssa.OpMIPSMOVWstorezero:
|
||||||
|
p := gc.Prog(v.Op.Asm())
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = mips.REGZERO
|
||||||
|
p.To.Type = obj.TYPE_MEM
|
||||||
|
p.To.Reg = v.Args[0].Reg()
|
||||||
|
gc.AddAux(&p.To, v)
|
||||||
|
case ssa.OpMIPSMOVBreg,
|
||||||
|
ssa.OpMIPSMOVBUreg,
|
||||||
|
ssa.OpMIPSMOVHreg,
|
||||||
|
ssa.OpMIPSMOVHUreg:
|
||||||
|
a := v.Args[0]
|
||||||
|
for a.Op == ssa.OpCopy || a.Op == ssa.OpMIPSMOVWreg || a.Op == ssa.OpMIPSMOVWnop {
|
||||||
|
a = a.Args[0]
|
||||||
|
}
|
||||||
|
if a.Op == ssa.OpLoadReg {
|
||||||
|
t := a.Type
|
||||||
|
switch {
|
||||||
|
case v.Op == ssa.OpMIPSMOVBreg && t.Size() == 1 && t.IsSigned(),
|
||||||
|
v.Op == ssa.OpMIPSMOVBUreg && t.Size() == 1 && !t.IsSigned(),
|
||||||
|
v.Op == ssa.OpMIPSMOVHreg && t.Size() == 2 && t.IsSigned(),
|
||||||
|
v.Op == ssa.OpMIPSMOVHUreg && t.Size() == 2 && !t.IsSigned():
|
||||||
|
// arg is a proper-typed load, already zero/sign-extended, don't extend again
|
||||||
|
if v.Reg() == v.Args[0].Reg() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p := gc.Prog(mips.AMOVW)
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = v.Args[0].Reg()
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg()
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case ssa.OpMIPSMOVWF,
|
||||||
|
ssa.OpMIPSMOVWD,
|
||||||
|
ssa.OpMIPSTRUNCFW,
|
||||||
|
ssa.OpMIPSTRUNCDW,
|
||||||
|
ssa.OpMIPSMOVFD,
|
||||||
|
ssa.OpMIPSMOVDF,
|
||||||
|
ssa.OpMIPSNEGF,
|
||||||
|
ssa.OpMIPSNEGD,
|
||||||
|
ssa.OpMIPSSQRTD,
|
||||||
|
ssa.OpMIPSCLZ:
|
||||||
|
p := gc.Prog(v.Op.Asm())
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = v.Args[0].Reg()
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg()
|
||||||
|
case ssa.OpMIPSNEG:
|
||||||
|
// SUB from REGZERO
|
||||||
|
p := gc.Prog(mips.ASUBU)
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = v.Args[0].Reg()
|
||||||
|
p.Reg = mips.REGZERO
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg()
|
||||||
|
case ssa.OpMIPSLoweredZero:
|
||||||
|
// SUBU $4, R1
|
||||||
|
// MOVW R0, 4(R1)
|
||||||
|
// ADDU $4, R1
|
||||||
|
// BNE Rarg1, R1, -2(PC)
|
||||||
|
// arg1 is the address of the last element to zero
|
||||||
|
var sz int64
|
||||||
|
var mov obj.As
|
||||||
|
switch {
|
||||||
|
case v.AuxInt%4 == 0:
|
||||||
|
sz = 4
|
||||||
|
mov = mips.AMOVW
|
||||||
|
case v.AuxInt%2 == 0:
|
||||||
|
sz = 2
|
||||||
|
mov = mips.AMOVH
|
||||||
|
default:
|
||||||
|
sz = 1
|
||||||
|
mov = mips.AMOVB
|
||||||
|
}
|
||||||
|
p := gc.Prog(mips.ASUBU)
|
||||||
|
p.From.Type = obj.TYPE_CONST
|
||||||
|
p.From.Offset = sz
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = mips.REG_R1
|
||||||
|
p2 := gc.Prog(mov)
|
||||||
|
p2.From.Type = obj.TYPE_REG
|
||||||
|
p2.From.Reg = mips.REGZERO
|
||||||
|
p2.To.Type = obj.TYPE_MEM
|
||||||
|
p2.To.Reg = mips.REG_R1
|
||||||
|
p2.To.Offset = sz
|
||||||
|
p3 := gc.Prog(mips.AADDU)
|
||||||
|
p3.From.Type = obj.TYPE_CONST
|
||||||
|
p3.From.Offset = sz
|
||||||
|
p3.To.Type = obj.TYPE_REG
|
||||||
|
p3.To.Reg = mips.REG_R1
|
||||||
|
p4 := gc.Prog(mips.ABNE)
|
||||||
|
p4.From.Type = obj.TYPE_REG
|
||||||
|
p4.From.Reg = v.Args[1].Reg()
|
||||||
|
p4.Reg = mips.REG_R1
|
||||||
|
p4.To.Type = obj.TYPE_BRANCH
|
||||||
|
gc.Patch(p4, p2)
|
||||||
|
case ssa.OpMIPSLoweredMove:
|
||||||
|
// SUBU $4, R1
|
||||||
|
// MOVW 4(R1), Rtmp
|
||||||
|
// MOVW Rtmp, (R2)
|
||||||
|
// ADDU $4, R1
|
||||||
|
// ADDU $4, R2
|
||||||
|
// BNE Rarg2, R1, -4(PC)
|
||||||
|
// arg2 is the address of the last element of src
|
||||||
|
var sz int64
|
||||||
|
var mov obj.As
|
||||||
|
switch {
|
||||||
|
case v.AuxInt%4 == 0:
|
||||||
|
sz = 4
|
||||||
|
mov = mips.AMOVW
|
||||||
|
case v.AuxInt%2 == 0:
|
||||||
|
sz = 2
|
||||||
|
mov = mips.AMOVH
|
||||||
|
default:
|
||||||
|
sz = 1
|
||||||
|
mov = mips.AMOVB
|
||||||
|
}
|
||||||
|
p := gc.Prog(mips.ASUBU)
|
||||||
|
p.From.Type = obj.TYPE_CONST
|
||||||
|
p.From.Offset = sz
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = mips.REG_R1
|
||||||
|
p2 := gc.Prog(mov)
|
||||||
|
p2.From.Type = obj.TYPE_MEM
|
||||||
|
p2.From.Reg = mips.REG_R1
|
||||||
|
p2.From.Offset = sz
|
||||||
|
p2.To.Type = obj.TYPE_REG
|
||||||
|
p2.To.Reg = mips.REGTMP
|
||||||
|
p3 := gc.Prog(mov)
|
||||||
|
p3.From.Type = obj.TYPE_REG
|
||||||
|
p3.From.Reg = mips.REGTMP
|
||||||
|
p3.To.Type = obj.TYPE_MEM
|
||||||
|
p3.To.Reg = mips.REG_R2
|
||||||
|
p4 := gc.Prog(mips.AADDU)
|
||||||
|
p4.From.Type = obj.TYPE_CONST
|
||||||
|
p4.From.Offset = sz
|
||||||
|
p4.To.Type = obj.TYPE_REG
|
||||||
|
p4.To.Reg = mips.REG_R1
|
||||||
|
p5 := gc.Prog(mips.AADDU)
|
||||||
|
p5.From.Type = obj.TYPE_CONST
|
||||||
|
p5.From.Offset = sz
|
||||||
|
p5.To.Type = obj.TYPE_REG
|
||||||
|
p5.To.Reg = mips.REG_R2
|
||||||
|
p6 := gc.Prog(mips.ABNE)
|
||||||
|
p6.From.Type = obj.TYPE_REG
|
||||||
|
p6.From.Reg = v.Args[2].Reg()
|
||||||
|
p6.Reg = mips.REG_R1
|
||||||
|
p6.To.Type = obj.TYPE_BRANCH
|
||||||
|
gc.Patch(p6, p2)
|
||||||
|
case ssa.OpMIPSCALLstatic:
|
||||||
|
if v.Aux.(*gc.Sym) == gc.Deferreturn.Sym {
|
||||||
|
// 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 actual hardware NOP that will have the right line number.
|
||||||
|
// This is different from obj.ANOP, which is a virtual no-op
|
||||||
|
// that doesn't make it into the instruction stream.
|
||||||
|
ginsnop()
|
||||||
|
}
|
||||||
|
p := gc.Prog(obj.ACALL)
|
||||||
|
p.To.Type = obj.TYPE_MEM
|
||||||
|
p.To.Name = obj.NAME_EXTERN
|
||||||
|
p.To.Sym = gc.Linksym(v.Aux.(*gc.Sym))
|
||||||
|
if gc.Maxarg < v.AuxInt {
|
||||||
|
gc.Maxarg = v.AuxInt
|
||||||
|
}
|
||||||
|
case ssa.OpMIPSCALLclosure:
|
||||||
|
p := gc.Prog(obj.ACALL)
|
||||||
|
p.To.Type = obj.TYPE_MEM
|
||||||
|
p.To.Offset = 0
|
||||||
|
p.To.Reg = v.Args[0].Reg()
|
||||||
|
if gc.Maxarg < v.AuxInt {
|
||||||
|
gc.Maxarg = v.AuxInt
|
||||||
|
}
|
||||||
|
case ssa.OpMIPSCALLdefer:
|
||||||
|
p := gc.Prog(obj.ACALL)
|
||||||
|
p.To.Type = obj.TYPE_MEM
|
||||||
|
p.To.Name = obj.NAME_EXTERN
|
||||||
|
p.To.Sym = gc.Linksym(gc.Deferproc.Sym)
|
||||||
|
if gc.Maxarg < v.AuxInt {
|
||||||
|
gc.Maxarg = v.AuxInt
|
||||||
|
}
|
||||||
|
case ssa.OpMIPSCALLgo:
|
||||||
|
p := gc.Prog(obj.ACALL)
|
||||||
|
p.To.Type = obj.TYPE_MEM
|
||||||
|
p.To.Name = obj.NAME_EXTERN
|
||||||
|
p.To.Sym = gc.Linksym(gc.Newproc.Sym)
|
||||||
|
if gc.Maxarg < v.AuxInt {
|
||||||
|
gc.Maxarg = v.AuxInt
|
||||||
|
}
|
||||||
|
case ssa.OpMIPSCALLinter:
|
||||||
|
p := gc.Prog(obj.ACALL)
|
||||||
|
p.To.Type = obj.TYPE_MEM
|
||||||
|
p.To.Offset = 0
|
||||||
|
p.To.Reg = v.Args[0].Reg()
|
||||||
|
if gc.Maxarg < v.AuxInt {
|
||||||
|
gc.Maxarg = v.AuxInt
|
||||||
|
}
|
||||||
|
case ssa.OpMIPSLoweredAtomicLoad:
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
|
||||||
|
p := gc.Prog(mips.AMOVW)
|
||||||
|
p.From.Type = obj.TYPE_MEM
|
||||||
|
p.From.Reg = v.Args[0].Reg()
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg0()
|
||||||
|
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
case ssa.OpMIPSLoweredAtomicStore:
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
|
||||||
|
p := gc.Prog(mips.AMOVW)
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = v.Args[1].Reg()
|
||||||
|
p.To.Type = obj.TYPE_MEM
|
||||||
|
p.To.Reg = v.Args[0].Reg()
|
||||||
|
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
case ssa.OpMIPSLoweredAtomicStorezero:
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
|
||||||
|
p := gc.Prog(mips.AMOVW)
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = mips.REGZERO
|
||||||
|
p.To.Type = obj.TYPE_MEM
|
||||||
|
p.To.Reg = v.Args[0].Reg()
|
||||||
|
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
case ssa.OpMIPSLoweredAtomicExchange:
|
||||||
|
// SYNC
|
||||||
|
// MOVW Rarg1, Rtmp
|
||||||
|
// LL (Rarg0), Rout
|
||||||
|
// SC Rtmp, (Rarg0)
|
||||||
|
// BEQ Rtmp, -3(PC)
|
||||||
|
// SYNC
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
|
||||||
|
p := gc.Prog(mips.AMOVW)
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = v.Args[1].Reg()
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = mips.REGTMP
|
||||||
|
|
||||||
|
p1 := gc.Prog(mips.ALL)
|
||||||
|
p1.From.Type = obj.TYPE_MEM
|
||||||
|
p1.From.Reg = v.Args[0].Reg()
|
||||||
|
p1.To.Type = obj.TYPE_REG
|
||||||
|
p1.To.Reg = v.Reg0()
|
||||||
|
|
||||||
|
p2 := gc.Prog(mips.ASC)
|
||||||
|
p2.From.Type = obj.TYPE_REG
|
||||||
|
p2.From.Reg = mips.REGTMP
|
||||||
|
p2.To.Type = obj.TYPE_MEM
|
||||||
|
p2.To.Reg = v.Args[0].Reg()
|
||||||
|
|
||||||
|
p3 := gc.Prog(mips.ABEQ)
|
||||||
|
p3.From.Type = obj.TYPE_REG
|
||||||
|
p3.From.Reg = mips.REGTMP
|
||||||
|
p3.To.Type = obj.TYPE_BRANCH
|
||||||
|
gc.Patch(p3, p)
|
||||||
|
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
case ssa.OpMIPSLoweredAtomicAdd:
|
||||||
|
// SYNC
|
||||||
|
// LL (Rarg0), Rout
|
||||||
|
// ADDU Rarg1, Rout, Rtmp
|
||||||
|
// SC Rtmp, (Rarg0)
|
||||||
|
// BEQ Rtmp, -3(PC)
|
||||||
|
// SYNC
|
||||||
|
// ADDU Rarg1, Rout
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
|
||||||
|
p := gc.Prog(mips.ALL)
|
||||||
|
p.From.Type = obj.TYPE_MEM
|
||||||
|
p.From.Reg = v.Args[0].Reg()
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg0()
|
||||||
|
|
||||||
|
p1 := gc.Prog(mips.AADDU)
|
||||||
|
p1.From.Type = obj.TYPE_REG
|
||||||
|
p1.From.Reg = v.Args[1].Reg()
|
||||||
|
p1.Reg = v.Reg0()
|
||||||
|
p1.To.Type = obj.TYPE_REG
|
||||||
|
p1.To.Reg = mips.REGTMP
|
||||||
|
|
||||||
|
p2 := gc.Prog(mips.ASC)
|
||||||
|
p2.From.Type = obj.TYPE_REG
|
||||||
|
p2.From.Reg = mips.REGTMP
|
||||||
|
p2.To.Type = obj.TYPE_MEM
|
||||||
|
p2.To.Reg = v.Args[0].Reg()
|
||||||
|
|
||||||
|
p3 := gc.Prog(mips.ABEQ)
|
||||||
|
p3.From.Type = obj.TYPE_REG
|
||||||
|
p3.From.Reg = mips.REGTMP
|
||||||
|
p3.To.Type = obj.TYPE_BRANCH
|
||||||
|
gc.Patch(p3, p)
|
||||||
|
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
|
||||||
|
p4 := gc.Prog(mips.AADDU)
|
||||||
|
p4.From.Type = obj.TYPE_REG
|
||||||
|
p4.From.Reg = v.Args[1].Reg()
|
||||||
|
p4.Reg = v.Reg0()
|
||||||
|
p4.To.Type = obj.TYPE_REG
|
||||||
|
p4.To.Reg = v.Reg0()
|
||||||
|
|
||||||
|
case ssa.OpMIPSLoweredAtomicAddconst:
|
||||||
|
// SYNC
|
||||||
|
// LL (Rarg0), Rout
|
||||||
|
// ADDU $auxInt, Rout, Rtmp
|
||||||
|
// SC Rtmp, (Rarg0)
|
||||||
|
// BEQ Rtmp, -3(PC)
|
||||||
|
// SYNC
|
||||||
|
// ADDU $auxInt, Rout
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
|
||||||
|
p := gc.Prog(mips.ALL)
|
||||||
|
p.From.Type = obj.TYPE_MEM
|
||||||
|
p.From.Reg = v.Args[0].Reg()
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg0()
|
||||||
|
|
||||||
|
p1 := gc.Prog(mips.AADDU)
|
||||||
|
p1.From.Type = obj.TYPE_CONST
|
||||||
|
p1.From.Offset = v.AuxInt
|
||||||
|
p1.Reg = v.Reg0()
|
||||||
|
p1.To.Type = obj.TYPE_REG
|
||||||
|
p1.To.Reg = mips.REGTMP
|
||||||
|
|
||||||
|
p2 := gc.Prog(mips.ASC)
|
||||||
|
p2.From.Type = obj.TYPE_REG
|
||||||
|
p2.From.Reg = mips.REGTMP
|
||||||
|
p2.To.Type = obj.TYPE_MEM
|
||||||
|
p2.To.Reg = v.Args[0].Reg()
|
||||||
|
|
||||||
|
p3 := gc.Prog(mips.ABEQ)
|
||||||
|
p3.From.Type = obj.TYPE_REG
|
||||||
|
p3.From.Reg = mips.REGTMP
|
||||||
|
p3.To.Type = obj.TYPE_BRANCH
|
||||||
|
gc.Patch(p3, p)
|
||||||
|
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
|
||||||
|
p4 := gc.Prog(mips.AADDU)
|
||||||
|
p4.From.Type = obj.TYPE_CONST
|
||||||
|
p4.From.Offset = v.AuxInt
|
||||||
|
p4.Reg = v.Reg0()
|
||||||
|
p4.To.Type = obj.TYPE_REG
|
||||||
|
p4.To.Reg = v.Reg0()
|
||||||
|
|
||||||
|
case ssa.OpMIPSLoweredAtomicAnd,
|
||||||
|
ssa.OpMIPSLoweredAtomicOr:
|
||||||
|
// SYNC
|
||||||
|
// LL (Rarg0), Rtmp
|
||||||
|
// AND/OR Rarg1, Rtmp
|
||||||
|
// SC Rtmp, (Rarg0)
|
||||||
|
// BEQ Rtmp, -3(PC)
|
||||||
|
// SYNC
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
|
||||||
|
p := gc.Prog(mips.ALL)
|
||||||
|
p.From.Type = obj.TYPE_MEM
|
||||||
|
p.From.Reg = v.Args[0].Reg()
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = mips.REGTMP
|
||||||
|
|
||||||
|
p1 := gc.Prog(v.Op.Asm())
|
||||||
|
p1.From.Type = obj.TYPE_REG
|
||||||
|
p1.From.Reg = v.Args[1].Reg()
|
||||||
|
p1.Reg = mips.REGTMP
|
||||||
|
p1.To.Type = obj.TYPE_REG
|
||||||
|
p1.To.Reg = mips.REGTMP
|
||||||
|
|
||||||
|
p2 := gc.Prog(mips.ASC)
|
||||||
|
p2.From.Type = obj.TYPE_REG
|
||||||
|
p2.From.Reg = mips.REGTMP
|
||||||
|
p2.To.Type = obj.TYPE_MEM
|
||||||
|
p2.To.Reg = v.Args[0].Reg()
|
||||||
|
|
||||||
|
p3 := gc.Prog(mips.ABEQ)
|
||||||
|
p3.From.Type = obj.TYPE_REG
|
||||||
|
p3.From.Reg = mips.REGTMP
|
||||||
|
p3.To.Type = obj.TYPE_BRANCH
|
||||||
|
gc.Patch(p3, p)
|
||||||
|
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
|
||||||
|
case ssa.OpMIPSLoweredAtomicCas:
|
||||||
|
// MOVW $0, Rout
|
||||||
|
// SYNC
|
||||||
|
// LL (Rarg0), Rtmp
|
||||||
|
// BNE Rtmp, Rarg1, 4(PC)
|
||||||
|
// MOVW Rarg2, Rout
|
||||||
|
// SC Rout, (Rarg0)
|
||||||
|
// BEQ Rout, -4(PC)
|
||||||
|
// SYNC
|
||||||
|
p := gc.Prog(mips.AMOVW)
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = mips.REGZERO
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg0()
|
||||||
|
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
|
||||||
|
p1 := gc.Prog(mips.ALL)
|
||||||
|
p1.From.Type = obj.TYPE_MEM
|
||||||
|
p1.From.Reg = v.Args[0].Reg()
|
||||||
|
p1.To.Type = obj.TYPE_REG
|
||||||
|
p1.To.Reg = mips.REGTMP
|
||||||
|
|
||||||
|
p2 := gc.Prog(mips.ABNE)
|
||||||
|
p2.From.Type = obj.TYPE_REG
|
||||||
|
p2.From.Reg = v.Args[1].Reg()
|
||||||
|
p2.Reg = mips.REGTMP
|
||||||
|
p2.To.Type = obj.TYPE_BRANCH
|
||||||
|
|
||||||
|
p3 := gc.Prog(mips.AMOVW)
|
||||||
|
p3.From.Type = obj.TYPE_REG
|
||||||
|
p3.From.Reg = v.Args[2].Reg()
|
||||||
|
p3.To.Type = obj.TYPE_REG
|
||||||
|
p3.To.Reg = v.Reg0()
|
||||||
|
|
||||||
|
p4 := gc.Prog(mips.ASC)
|
||||||
|
p4.From.Type = obj.TYPE_REG
|
||||||
|
p4.From.Reg = v.Reg0()
|
||||||
|
p4.To.Type = obj.TYPE_MEM
|
||||||
|
p4.To.Reg = v.Args[0].Reg()
|
||||||
|
|
||||||
|
p5 := gc.Prog(mips.ABEQ)
|
||||||
|
p5.From.Type = obj.TYPE_REG
|
||||||
|
p5.From.Reg = v.Reg0()
|
||||||
|
p5.To.Type = obj.TYPE_BRANCH
|
||||||
|
gc.Patch(p5, p1)
|
||||||
|
|
||||||
|
gc.Prog(mips.ASYNC)
|
||||||
|
|
||||||
|
p6 := gc.Prog(obj.ANOP)
|
||||||
|
gc.Patch(p2, p6)
|
||||||
|
|
||||||
|
case ssa.OpVarDef:
|
||||||
|
gc.Gvardef(v.Aux.(*gc.Node))
|
||||||
|
case ssa.OpVarKill:
|
||||||
|
gc.Gvarkill(v.Aux.(*gc.Node))
|
||||||
|
case ssa.OpVarLive:
|
||||||
|
gc.Gvarlive(v.Aux.(*gc.Node))
|
||||||
|
case ssa.OpKeepAlive:
|
||||||
|
gc.KeepAlive(v)
|
||||||
|
case ssa.OpPhi:
|
||||||
|
gc.CheckLoweredPhi(v)
|
||||||
|
case ssa.OpMIPSLoweredNilCheck:
|
||||||
|
// Issue a load which will fault if arg is nil.
|
||||||
|
p := gc.Prog(mips.AMOVB)
|
||||||
|
p.From.Type = obj.TYPE_MEM
|
||||||
|
p.From.Reg = v.Args[0].Reg()
|
||||||
|
gc.AddAux(&p.From, v)
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = mips.REGTMP
|
||||||
|
if gc.Debug_checknil != 0 && v.Line > 1 { // v.Line==1 in generated wrappers
|
||||||
|
gc.Warnl(v.Line, "generated nil check")
|
||||||
|
}
|
||||||
|
case ssa.OpMIPSFPFlagTrue,
|
||||||
|
ssa.OpMIPSFPFlagFalse:
|
||||||
|
// MOVW $1, r
|
||||||
|
// CMOVF R0, r
|
||||||
|
|
||||||
|
cmov := mips.ACMOVF
|
||||||
|
if v.Op == ssa.OpMIPSFPFlagFalse {
|
||||||
|
cmov = mips.ACMOVT
|
||||||
|
}
|
||||||
|
p := gc.Prog(mips.AMOVW)
|
||||||
|
p.From.Type = obj.TYPE_CONST
|
||||||
|
p.From.Offset = 1
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = v.Reg()
|
||||||
|
p1 := gc.Prog(cmov)
|
||||||
|
p1.From.Type = obj.TYPE_REG
|
||||||
|
p1.From.Reg = mips.REGZERO
|
||||||
|
p1.To.Type = obj.TYPE_REG
|
||||||
|
p1.To.Reg = v.Reg()
|
||||||
|
|
||||||
|
case ssa.OpMIPSLoweredGetClosurePtr:
|
||||||
|
// Closure pointer is R22 (mips.REGCTXT).
|
||||||
|
gc.CheckLoweredGetClosurePtr(v)
|
||||||
|
default:
|
||||||
|
v.Fatalf("genValue not implemented: %s", v.LongString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockJump = map[ssa.BlockKind]struct {
|
||||||
|
asm, invasm obj.As
|
||||||
|
}{
|
||||||
|
ssa.BlockMIPSEQ: {mips.ABEQ, mips.ABNE},
|
||||||
|
ssa.BlockMIPSNE: {mips.ABNE, mips.ABEQ},
|
||||||
|
ssa.BlockMIPSLTZ: {mips.ABLTZ, mips.ABGEZ},
|
||||||
|
ssa.BlockMIPSGEZ: {mips.ABGEZ, mips.ABLTZ},
|
||||||
|
ssa.BlockMIPSLEZ: {mips.ABLEZ, mips.ABGTZ},
|
||||||
|
ssa.BlockMIPSGTZ: {mips.ABGTZ, mips.ABLEZ},
|
||||||
|
ssa.BlockMIPSFPT: {mips.ABFPT, mips.ABFPF},
|
||||||
|
ssa.BlockMIPSFPF: {mips.ABFPF, mips.ABFPT},
|
||||||
|
}
|
||||||
|
|
||||||
|
func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) {
|
||||||
|
s.SetLineno(b.Line)
|
||||||
|
|
||||||
|
switch b.Kind {
|
||||||
|
case ssa.BlockPlain:
|
||||||
|
if b.Succs[0].Block() != next {
|
||||||
|
p := gc.Prog(obj.AJMP)
|
||||||
|
p.To.Type = obj.TYPE_BRANCH
|
||||||
|
s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()})
|
||||||
|
}
|
||||||
|
case ssa.BlockDefer:
|
||||||
|
// defer returns in R1:
|
||||||
|
// 0 if we should continue executing
|
||||||
|
// 1 if we should jump to deferreturn call
|
||||||
|
p := gc.Prog(mips.ABNE)
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = mips.REGZERO
|
||||||
|
p.Reg = mips.REG_R1
|
||||||
|
p.To.Type = obj.TYPE_BRANCH
|
||||||
|
s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[1].Block()})
|
||||||
|
if b.Succs[0].Block() != next {
|
||||||
|
p := gc.Prog(obj.AJMP)
|
||||||
|
p.To.Type = obj.TYPE_BRANCH
|
||||||
|
s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()})
|
||||||
|
}
|
||||||
|
case ssa.BlockExit:
|
||||||
|
gc.Prog(obj.AUNDEF) // tell plive.go that we never reach here
|
||||||
|
case ssa.BlockRet:
|
||||||
|
gc.Prog(obj.ARET)
|
||||||
|
case ssa.BlockRetJmp:
|
||||||
|
p := gc.Prog(obj.ARET)
|
||||||
|
p.To.Type = obj.TYPE_MEM
|
||||||
|
p.To.Name = obj.NAME_EXTERN
|
||||||
|
p.To.Sym = gc.Linksym(b.Aux.(*gc.Sym))
|
||||||
|
case ssa.BlockMIPSEQ, ssa.BlockMIPSNE,
|
||||||
|
ssa.BlockMIPSLTZ, ssa.BlockMIPSGEZ,
|
||||||
|
ssa.BlockMIPSLEZ, ssa.BlockMIPSGTZ,
|
||||||
|
ssa.BlockMIPSFPT, ssa.BlockMIPSFPF:
|
||||||
|
jmp := blockJump[b.Kind]
|
||||||
|
var p *obj.Prog
|
||||||
|
switch next {
|
||||||
|
case b.Succs[0].Block():
|
||||||
|
p = gc.Prog(jmp.invasm)
|
||||||
|
p.To.Type = obj.TYPE_BRANCH
|
||||||
|
s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[1].Block()})
|
||||||
|
case b.Succs[1].Block():
|
||||||
|
p = gc.Prog(jmp.asm)
|
||||||
|
p.To.Type = obj.TYPE_BRANCH
|
||||||
|
s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()})
|
||||||
|
default:
|
||||||
|
p = gc.Prog(jmp.asm)
|
||||||
|
p.To.Type = obj.TYPE_BRANCH
|
||||||
|
s.Branches = append(s.Branches, gc.Branch{P: p, B: b.Succs[0].Block()})
|
||||||
|
q := gc.Prog(obj.AJMP)
|
||||||
|
q.To.Type = obj.TYPE_BRANCH
|
||||||
|
s.Branches = append(s.Branches, gc.Branch{P: q, B: b.Succs[1].Block()})
|
||||||
|
}
|
||||||
|
if !b.Control.Type.IsFlags() {
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = b.Control.Reg()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
b.Fatalf("branch not implemented: %s. Control: %s", b.LongString(), b.Control.LongString())
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue