2024-08-30
Go & 后端
00

目录

1 使用特性
2 底层实现

对 select 的使用方法和底层机制进行记录,以下内容基于 go1.22.1 版本。

1 使用特性

select只能用于channel的读写,它的总体逻辑即为:

监控每个 case 对应的 channel,当某一个 case 的 channel 可用时,立即对 channel 进行操作,并且执行该 case 对应的代码块;当多个 case 都可用时,将在可用的 case 中随机一个 case 执行;当所有的 case 都不可用时,如果有 default 就执行 default,没有 default 就会进入阻塞,直到某个 case 的 channel 可用为止。

从它的总体逻辑可以得出一些结论:

  1. 一个 case 只能用于一个 channel 的读或者写。
  2. 空 select(即只有select{})会被永久阻塞。
  3. 因为一个 case 只能对应一个 channel 的读或者写,所以一个 case 最多只能赋值两个变量(指v, ok := <- channel这样的情况)。
  4. 如果 case 是对还未初始化的 channel 进行读写,那么该 case 会被直接忽略。

综合以上的使用说明,select 除了监控多个 channel,还有几个比较特殊的用法:

  1. 用空的 select 永久阻塞主 goroutine。
go
func main() { r := router.Router() go e := r.Run("127.0.0.1:8060") select {} }
  1. 将一个 channel 传入被调用的函数,如果有错误就传进来。用 select 快速检查该 channel 以确定是否有错误发生。
go
// 模拟某些业务代码 func SomeUsefulCode(errChan chan <- error) {} // 防报错的 func ErrHandler(e error) {} func ErrDetect() { errChannel := make(chan error, 1) SomeUsefulCode(errChannel) select { case e := <- errChannel: ErrHandler(e) default: } return }
  1. time.After函数返回的 channel 写进 case,做一个限时存在的 channel。
go
func GetTimeLimitedChan(messageChan <-chan struct{}, t time.Duration) chan struct{} { c := make(chan struct{}) go func() { select { case <- messageChan: // 手动取消该计时 channel case <- time.After(t): // 定时取消该计时 channel close(c) } }() return c }

2 底层实现

select 的实现分成两部分:select 的逻辑和 case 数据结构

这部分内容仅作为参考,可能是书过时了,内容也和源码不匹配了。

case 所对应的底层数据结构 hcase 是这样的:

go
type hcase struct { c *hchan kind uint16 elem unsafe.Pointer ... }

这三个变量的作用如下:

  • c:case具体要读写的channel,从这里也能看出来一个case仅能操作一个channel。
  • kind:case的行为类型。具体有caseNil,caseRecv,caseSend,caseDefault四种,分别对应传入channel。为空,读,写,default分支四种情况。其中caseNil类型的case会被直接忽略。
  • elem,数据存放的地址。根据读写不同,存放的是要读/要写的数据的地址。

select的逻辑对应底层的selectgo函数:func selectgo(cas0 *scase,order0 *uint16,ncases,int)(int,bool){}

该函数的三个参数作用如下:

  • cas0:因为编译器会将所有case放进一个数组中,所以传第一个case就相当于传了所有的case。
  • order0:和cas0一样,它也是一个数组,长度为cas0的两倍,是实现随机性的一个重要数据结构。
  • ncases:指示一共有多少case。

两个返回值含义如下:

  • int:指示命中的case索引。
  • bool:对于类型为caseRecv的case,它用于指示是否成功读取数据,对于其他类型的case无作用。

先说 case 数据结构。

go
// Select case descriptor. // Known to compiler. // Changes here must also be made in src/cmd/compile/internal/walk/select.go's scasetype. type scase struct { c *hchan // chan elem unsafe.Pointer // data element }
  • c:case具体要读写的channel,从这里也能看出来一个case仅能操作一个channel。
  • elem,数据存放的地址。根据读写不同,存放的是要读/要写的数据的地址。

之后是 select 的主要逻辑:selectgo函数。

go
// 该代码位于 src/runtime/select.go // selectgo implements the select statement. // // 以下为一些参数和该函数的说明 // // cas0 points to an array of type [ncases]scase, and order0 points to // an array of type [2*ncases]uint16 where ncases must be <= 65536. // Both reside on the goroutine's stack (regardless of any escaping in // selectgo). // // cas0 是一个指向了存储着所有 case 的数组的指针,order0 则是一个指向长度两倍于前一个数组的数组 // 这两个都在栈上 不用担心内存逃逸 // // For race detector builds, pc0 points to an array of type // [ncases]uintptr (also on the stack); for other builds, it's set to // nil. // // pc0 只在竟态检测器构建时有效 不在这里的讨论范围内 // // selectgo returns the index of the chosen scase, which matches the // ordinal position of its respective select{recv,send,default} call. // Also, if the chosen scase was a receive operation, it reports whether // a value was received. // // selectgo 返回满足条件的被选中的 case 的索引 如果被选中的 case 是个读操作 也会返回读到值与否 // // nsends 和 nrecvs 两个参数分别指示 case 中读 case 和写 case 的数量 // // block 用于指示是否阻塞 如果不阻塞 就说明存在 default 分支 // func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, block bool) (int, bool) { // 跟 pc0 racennabled debug 有关的全部不在讨论范围内 if debugSelect { print("select: cas0=", cas0, "\n") } // NOTE: In order to maintain a lean stack size, the number of scases // is capped at 65536. cas1 := (*[1 << 16]scase)(unsafe.Pointer(cas0)) order1 := (*[1 << 17]uint16)(unsafe.Pointer(order0)) ncases := nsends + nrecvs scases := cas1[:ncases:ncases] pollorder := order1[:ncases:ncases] // 存放被打乱顺序的 case lockorder := order1[ncases:][:ncases:ncases] // 存放所有 case 对应的 channel 以去重防止重复加锁 // NOTE: pollorder/lockorder's underlying array was not zero-initialized by compiler. // Even when raceenabled is true, there might be select // statements in packages compiled without -race (e.g., // ensureSigM in runtime/signal_unix.go). var pcs []uintptr if raceenabled && pc0 != nil { pc1 := (*[1 << 16]uintptr)(unsafe.Pointer(pc0)) pcs = pc1[:ncases:ncases] } casePC := func(casi int) uintptr { if pcs == nil { return 0 } return pcs[casi] } var t0 int64 if blockprofilerate > 0 { t0 = cputicks() } // The compiler rewrites selects that statically have // only 0 or 1 cases plus default into simpler constructs. // The only way we can end up with such small sel.ncase // values here is for a larger select in which most channels // have been nilled out. The general code handles those // cases correctly, and they are rare enough not to bother // optimizing (and needing to test). // 开始打乱顺序写入 pollorder // generate permuted order norder := 0 for i := range scases { cas := &scases[i] // Omit cases without channels from the poll and lock orders. // 查 channel 是空 if cas.c == nil { cas.elem = nil // allow GC continue } j := cheaprandn(uint32(norder + 1)) // 取随机数 pollorder[norder] = pollorder[j] pollorder[j] = uint16(i) norder++ } pollorder = pollorder[:norder] lockorder = lockorder[:norder] // sort the cases by Hchan address to get the locking order. // simple heap sort, to guarantee n log n time and constant stack footprint. // 根据 channel 的地址来排序 确定获取锁的顺序 for i := range lockorder { j := i // Start with the pollorder to permute cases on the same channel. c := scases[pollorder[i]].c for j > 0 && scases[lockorder[(j-1)/2]].c.sortkey() < c.sortkey() { k := (j - 1) / 2 lockorder[j] = lockorder[k] j = k } lockorder[j] = pollorder[i] } for i := len(lockorder) - 1; i >= 0; i-- { o := lockorder[i] c := scases[o].c lockorder[i] = lockorder[0] j := 0 for { k := j*2 + 1 if k >= i { break } if k+1 < i && scases[lockorder[k]].c.sortkey() < scases[lockorder[k+1]].c.sortkey() { k++ } if c.sortkey() < scases[lockorder[k]].c.sortkey() { lockorder[j] = lockorder[k] j = k continue } break } lockorder[j] = o } if debugSelect { for i := 0; i+1 < len(lockorder); i++ { if scases[lockorder[i]].c.sortkey() > scases[lockorder[i+1]].c.sortkey() { print("i=", i, " x=", lockorder[i], " y=", lockorder[i+1], "\n") throw("select: broken sort") } } } // lock all the channels involved in the select // 给所有有效的 channel 上锁 sellock(scases, lockorder) // 开始寻找准备好的 case var ( gp *g sg *sudog c *hchan k *scase sglist *sudog sgnext *sudog qp unsafe.Pointer nextp **sudog ) // pass 1 - look for something already waiting // 寻找已经准备好的 case var casi int var cas *scase var caseSuccess bool var caseReleaseTime int64 = -1 var recvOK bool // 注意此时 case 已经是随机的了 for _, casei := range pollorder { casi = int(casei) cas = &scases[casi] c = cas.c if casi >= nsends { // case 是从 channel 中读取数据的 // 查 channel 的等待发送 goroutine 队列 sg = c.sendq.dequeue() if sg != nil { // 有 goroutine 等待发送数据 goto recv } if c.qcount > 0 { // 缓冲区有数据 goto bufrecv } if c.closed != 0 { // channel 已关闭且已经确定缓冲区没有数据了 goto rclose } } else { // case 是写数据到 channel 的 if raceenabled { racereadpc(c.raceaddr(), casePC(casi), chansendpc) } if c.closed != 0 { // channel已关闭 goto sclose } // 查 channel 的等待接收 goroutine 队列 sg = c.recvq.dequeue() if sg != nil { // 有 goroutine 在队列中 goto send } if c.qcount < c.dataqsiz { // 缓冲区有空间 goto bufsend } } // 这些 goto 对应的位置都在最下面 } if !block { // 如果不阻塞 就意味着有 default selunlock(scases, lockorder) // 全部解锁 default 也不需要操作这些 channel 了 casi = -1 goto retc } // pass 2 - enqueue on all chans // 所有 channel 入队 等待处理 // 拿到当前 goroutine 的地址 gp = getg() if gp.waiting != nil { throw("gp.waiting != nil") } nextp = &gp.waiting for _, casei := range lockorder { casi = int(casei) cas = &scases[casi] // 拿 case c = cas.c // 拿 channel sg := acquireSudog() // 拿到 sudog 这个东西以后会说 反正就是标志一个goroutine 处于等待队列中 sg.g = gp sg.isSelect = true // No stack splits between assigning elem and enqueuing // sg on gp.waiting where copystack can find it. sg.elem = cas.elem sg.releasetime = 0 if t0 != 0 { sg.releasetime = -1 } sg.c = c // Construct waiting list in lock order. *nextp = sg nextp = &sg.waitlink if casi < nsends { c.sendq.enqueue(sg) } else { c.recvq.enqueue(sg) } } // wait for someone to wake us up gp.param = nil // Signal to anyone trying to shrink our stack that we're about // to park on a channel. The window between when this G's status // changes and when we set gp.activeStackChans is not safe for // stack shrinking. gp.parkingOnChan.Store(true) // gopark 挂起当前 goroutine 并且传进去了原因 waitReasonSelect gopark(selparkcommit, nil, waitReasonSelect, traceBlockSelect, 1) // 设置当前 goroutine 的状态 当前不再有任何其他 channel 与其交互 gp.activeStackChans = false sellock(scases, lockorder) gp.selectDone.Store(0) sg = (*sudog)(gp.param) gp.param = nil // pass 3 - dequeue from unsuccessful chans // otherwise they stack up on quiet channels // record the successful case, if any. // We singly-linked up the SudoGs in lock order. // 现在当前 goroutine 被唤醒 继续 select casi = -1 cas = nil caseSuccess = false sglist = gp.waiting // 拿到等待的 sudog // Clear all elem before unlinking from gp.waiting. for sg1 := gp.waiting; sg1 != nil; sg1 = sg1.waitlink { // 清理 gp.waiting sg1.isSelect = false sg1.elem = nil sg1.c = nil } gp.waiting = nil // 当前 goroutine 不再等待其他数据 for _, casei := range lockorder { // 遍历 case k = &scases[casei] if sg == sglist { // sg has already been dequeued by the G that woke us up. // 一一匹配 直到匹配成功 确定唤醒 goroutine 的 channel casi = int(casei) // 匹配成功的 case 的索引 cas = k caseSuccess = sglist.success if sglist.releasetime > 0 { caseReleaseTime = sglist.releasetime } } else { c = k.c if int(casei) < nsends { c.sendq.dequeueSudoG(sglist) } else { c.recvq.dequeueSudoG(sglist) } } sgnext = sglist.waitlink sglist.waitlink = nil releaseSudog(sglist) sglist = sgnext } if cas == nil { throw("selectgo: bad wakeup") // 一轮循环下来没匹配成功 } c = cas.c // 拿到 channel if debugSelect { print("wait-return: cas0=", cas0, " c=", c, " cas=", cas, " send=", casi < nsends, "\n") } if casi < nsends { if !caseSuccess { goto sclose } } else { recvOK = caseSuccess } if raceenabled { if casi < nsends { raceReadObjectPC(c.elemtype, cas.elem, casePC(casi), chansendpc) } else if cas.elem != nil { raceWriteObjectPC(c.elemtype, cas.elem, casePC(casi), chanrecvpc) } } if msanenabled { if casi < nsends { msanread(cas.elem, c.elemtype.Size_) } else if cas.elem != nil { msanwrite(cas.elem, c.elemtype.Size_) } } if asanenabled { if casi < nsends { asanread(cas.elem, c.elemtype.Size_) } else if cas.elem != nil { asanwrite(cas.elem, c.elemtype.Size_) } } selunlock(scases, lockorder) goto retc bufrecv: // can receive from buffer // 可以从缓冲区内拿数据的 if raceenabled { if cas.elem != nil { raceWriteObjectPC(c.elemtype, cas.elem, casePC(casi), chanrecvpc) } racenotify(c, c.recvx, nil) } if msanenabled && cas.elem != nil { msanwrite(cas.elem, c.elemtype.Size_) } if asanenabled && cas.elem != nil { asanwrite(cas.elem, c.elemtype.Size_) } recvOK = true qp = chanbuf(c, c.recvx) if cas.elem != nil { typedmemmove(c.elemtype, cas.elem, qp) } typedmemclr(c.elemtype, qp) c.recvx++ if c.recvx == c.dataqsiz { c.recvx = 0 } c.qcount-- selunlock(scases, lockorder) goto retc bufsend: // can send to buffer // 可以写数据到缓冲区的 if raceenabled { racenotify(c, c.sendx, nil) raceReadObjectPC(c.elemtype, cas.elem, casePC(casi), chansendpc) } if msanenabled { msanread(cas.elem, c.elemtype.Size_) } if asanenabled { asanread(cas.elem, c.elemtype.Size_) } typedmemmove(c.elemtype, chanbuf(c, c.sendx), cas.elem) c.sendx++ if c.sendx == c.dataqsiz { c.sendx = 0 } c.qcount++ selunlock(scases, lockorder) goto retc recv: // can receive from sleeping sender (sg) // 可以从一个休眠的 goroutine 那里拿数据的 recv(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2) if debugSelect { print("syncrecv: cas0=", cas0, " c=", c, "\n") } recvOK = true goto retc rclose: // read at end of closed channel // 可以从一个已经关闭的 channel 拿零值的 selunlock(scases, lockorder) recvOK = false if cas.elem != nil { typedmemclr(c.elemtype, cas.elem) // 这是清内存的函数 } if raceenabled { raceacquire(c.raceaddr()) } goto retc send: // can send to a sleeping receiver (sg) // 可以写数据到一个休眠的 goroutine 的 if raceenabled { raceReadObjectPC(c.elemtype, cas.elem, casePC(casi), chansendpc) } if msanenabled { msanread(cas.elem, c.elemtype.Size_) } if asanenabled { asanread(cas.elem, c.elemtype.Size_) } send(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2) if debugSelect { print("syncsend: cas0=", cas0, " c=", c, "\n") } goto retc retc: // 结束select if caseReleaseTime > 0 { blockevent(caseReleaseTime-t0, 1) } return casi, recvOK sclose: // send on closed channel // 向一个已经关闭的 channel 写数据的 直接引发 panic selunlock(scases, lockorder) panic(plainError("send on closed channel")) }

以上就是 select 的全部逻辑,当然还有一些没有在这里面,大致说一下:

  • 进 select 的时候会进行优化,比如一个 case 的,空的,俩 case 其中有一个是 default 的,都不会再进入 select 的逻辑。这些优化都在src/cmd/compile/walk/select.go里的walkSelectCases函数中。下面会附源码。
  • default 分支已经不在selectgo函数中了,它也在walkSelectCases函数中。
  • 空 channel 分支一开始就会被忽略掉。

除了这些,剩下的在selectgo函数中的内容,大体总结一下:

  1. 它会先打乱 case 顺序,并且排除掉空 channel 的 case。
  2. 之后,先把 case 全部过一遍,查看是否满足条件。如果满足直接进分支,不满足再走下一步。
  3. 如果所有的 case 都不满足条件,再根据是否阻塞决定是否进 default。
  4. 如果没有 default,那么就休眠当前 goroutine,等待某一个符合条件的 channel 唤醒自己。
  5. 等到自己被唤醒,找到唤醒自己的 channel,select 结束。
  6. 如果没有唤醒自己的 channel,throw 一个错误出去。
go
func walkSelectCases(cases []*ir.CommClause) []ir.Node { ncas := len(cases) sellineno := base.Pos // optimization: zero-case select // 空 select 直接阻塞 if ncas == 0 { return []ir.Node{mkcallstmt("block")} } // optimization: one-case select: single op. // 一个 case 的 select if ncas == 1 { cas := cases[0] ir.SetPos(cas) l := cas.Init() if cas.Comm != nil { // not default: n := cas.Comm l = append(l, ir.TakeInit(n)...) switch n.Op() { default: base.Fatalf("select %v", n.Op()) case ir.OSEND: // already ok case ir.OSELRECV2: r := n.(*ir.AssignListStmt) if ir.IsBlank(r.Lhs[0]) && ir.IsBlank(r.Lhs[1]) { n = r.Rhs[0] break } r.SetOp(ir.OAS2RECV) } l = append(l, n) } l = append(l, cas.Body...) l = append(l, ir.NewBranchStmt(base.Pos, ir.OBREAK, nil)) return l } // convert case value arguments to addresses. // this rewrite is used by both the general code and the next optimization. var dflt *ir.CommClause for _, cas := range cases { ir.SetPos(cas) n := cas.Comm if n == nil { dflt = cas continue } switch n.Op() { case ir.OSEND: n := n.(*ir.SendStmt) n.Value = typecheck.NodAddr(n.Value) n.Value = typecheck.Expr(n.Value) case ir.OSELRECV2: n := n.(*ir.AssignListStmt) if !ir.IsBlank(n.Lhs[0]) { n.Lhs[0] = typecheck.NodAddr(n.Lhs[0]) n.Lhs[0] = typecheck.Expr(n.Lhs[0]) } } } // optimization: two-case select but one is default: single non-blocking op. // 两个 case 但是其中有一个是 default if ncas == 2 && dflt != nil { cas := cases[0] if cas == dflt { cas = cases[1] } n := cas.Comm ir.SetPos(n) r := ir.NewIfStmt(base.Pos, nil, nil, nil) r.SetInit(cas.Init()) var cond ir.Node switch n.Op() { default: base.Fatalf("select %v", n.Op()) case ir.OSEND: // if selectnbsend(c, v) { body } else { default body } n := n.(*ir.SendStmt) ch := n.Chan cond = mkcall1(chanfn("selectnbsend", 2, ch.Type()), types.Types[types.TBOOL], r.PtrInit(), ch, n.Value) case ir.OSELRECV2: n := n.(*ir.AssignListStmt) recv := n.Rhs[0].(*ir.UnaryExpr) ch := recv.X elem := n.Lhs[0] if ir.IsBlank(elem) { elem = typecheck.NodNil() } cond = typecheck.TempAt(base.Pos, ir.CurFunc, types.Types[types.TBOOL]) fn := chanfn("selectnbrecv", 2, ch.Type()) call := mkcall1(fn, fn.Type().ResultsTuple(), r.PtrInit(), elem, ch) as := ir.NewAssignListStmt(r.Pos(), ir.OAS2, []ir.Node{cond, n.Lhs[1]}, []ir.Node{call}) r.PtrInit().Append(typecheck.Stmt(as)) } r.Cond = typecheck.Expr(cond) r.Body = cas.Body r.Else = append(dflt.Init(), dflt.Body...) return []ir.Node{r, ir.NewBranchStmt(base.Pos, ir.OBREAK, nil)} } if dflt != nil { ncas-- } casorder := make([]*ir.CommClause, ncas) nsends, nrecvs := 0, 0 var init []ir.Node // generate sel-struct base.Pos = sellineno selv := typecheck.TempAt(base.Pos, ir.CurFunc, types.NewArray(scasetype(), int64(ncas))) init = append(init, typecheck.Stmt(ir.NewAssignStmt(base.Pos, selv, nil))) // No initialization for order; runtime.selectgo is responsible for that. order := typecheck.TempAt(base.Pos, ir.CurFunc, types.NewArray(types.Types[types.TUINT16], 2*int64(ncas))) var pc0, pcs ir.Node if base.Flag.Race { pcs = typecheck.TempAt(base.Pos, ir.CurFunc, types.NewArray(types.Types[types.TUINTPTR], int64(ncas))) pc0 = typecheck.Expr(typecheck.NodAddr(ir.NewIndexExpr(base.Pos, pcs, ir.NewInt(base.Pos, 0)))) } else { pc0 = typecheck.NodNil() } // register cases // 转换 case for _, cas := range cases { ir.SetPos(cas) init = append(init, ir.TakeInit(cas)...) n := cas.Comm if n == nil { // default: continue } var i int var c, elem ir.Node switch n.Op() { default: base.Fatalf("select %v", n.Op()) case ir.OSEND: n := n.(*ir.SendStmt) i = nsends nsends++ c = n.Chan elem = n.Value case ir.OSELRECV2: n := n.(*ir.AssignListStmt) nrecvs++ i = ncas - nrecvs recv := n.Rhs[0].(*ir.UnaryExpr) c = recv.X elem = n.Lhs[0] } casorder[i] = cas setField := func(f string, val ir.Node) { r := ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, ir.NewIndexExpr(base.Pos, selv, ir.NewInt(base.Pos, int64(i))), typecheck.Lookup(f)), val) init = append(init, typecheck.Stmt(r)) } c = typecheck.ConvNop(c, types.Types[types.TUNSAFEPTR]) setField("c", c) if !ir.IsBlank(elem) { elem = typecheck.ConvNop(elem, types.Types[types.TUNSAFEPTR]) setField("elem", elem) } // TODO(mdempsky): There should be a cleaner way to // handle this. if base.Flag.Race { r := mkcallstmt("selectsetpc", typecheck.NodAddr(ir.NewIndexExpr(base.Pos, pcs, ir.NewInt(base.Pos, int64(i))))) init = append(init, r) } } if nsends+nrecvs != ncas { base.Fatalf("walkSelectCases: miscount: %v + %v != %v", nsends, nrecvs, ncas) } // run the select base.Pos = sellineno chosen := typecheck.TempAt(base.Pos, ir.CurFunc, types.Types[types.TINT]) recvOK := typecheck.TempAt(base.Pos, ir.CurFunc, types.Types[types.TBOOL]) r := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, nil) r.Lhs = []ir.Node{chosen, recvOK} fn := typecheck.LookupRuntime("selectgo") // 在 runtime 里寻找 selectgo var fnInit ir.Nodes r.Rhs = []ir.Node{mkcall1(fn, fn.Type().ResultsTuple(), &fnInit, bytePtrToIndex(selv, 0), bytePtrToIndex(order, 0), pc0, ir.NewInt(base.Pos, int64(nsends)), ir.NewInt(base.Pos, int64(nrecvs)), ir.NewBool(base.Pos, dflt == nil))} init = append(init, fnInit...) init = append(init, typecheck.Stmt(r)) // selv, order, and pcs (if race) are no longer alive after selectgo. // dispatch cases dispatch := func(cond ir.Node, cas *ir.CommClause) { var list ir.Nodes if n := cas.Comm; n != nil && n.Op() == ir.OSELRECV2 { n := n.(*ir.AssignListStmt) if !ir.IsBlank(n.Lhs[1]) { x := ir.NewAssignStmt(base.Pos, n.Lhs[1], recvOK) list.Append(typecheck.Stmt(x)) } } list.Append(cas.Body.Take()...) list.Append(ir.NewBranchStmt(base.Pos, ir.OBREAK, nil)) var r ir.Node if cond != nil { cond = typecheck.Expr(cond) cond = typecheck.DefaultLit(cond, nil) r = ir.NewIfStmt(base.Pos, cond, list, nil) } else { r = ir.NewBlockStmt(base.Pos, list) } init = append(init, r) } if dflt != nil { ir.SetPos(dflt) dispatch(ir.NewBinaryExpr(base.Pos, ir.OLT, chosen, ir.NewInt(base.Pos, 0)), dflt) } for i, cas := range casorder { ir.SetPos(cas) if i == len(casorder)-1 { dispatch(nil, cas) break } dispatch(ir.NewBinaryExpr(base.Pos, ir.OEQ, chosen, ir.NewInt(base.Pos, int64(i))), cas) } return init }

本文作者:御坂19327号

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!