github.com/bir3/gocompiler@v0.9.2202/src/cmd/compile/internal/wasm/ssa.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package wasm
     6  
     7  import (
     8  	"github.com/bir3/gocompiler/src/cmd/compile/internal/base"
     9  	"github.com/bir3/gocompiler/src/cmd/compile/internal/ir"
    10  	"github.com/bir3/gocompiler/src/cmd/compile/internal/logopt"
    11  	"github.com/bir3/gocompiler/src/cmd/compile/internal/objw"
    12  	"github.com/bir3/gocompiler/src/cmd/compile/internal/ssa"
    13  	"github.com/bir3/gocompiler/src/cmd/compile/internal/ssagen"
    14  	"github.com/bir3/gocompiler/src/cmd/compile/internal/types"
    15  	"github.com/bir3/gocompiler/src/cmd/internal/obj"
    16  	"github.com/bir3/gocompiler/src/cmd/internal/obj/wasm"
    17  	"github.com/bir3/gocompiler/src/internal/buildcfg"
    18  )
    19  
    20  /*
    21  
    22     Wasm implementation
    23     -------------------
    24  
    25     Wasm is a strange Go port because the machine isn't
    26     a register-based machine, threads are different, code paths
    27     are different, etc. We outline those differences here.
    28  
    29     See the design doc for some additional info on this topic.
    30     https://docs.google.com/document/d/131vjr4DH6JFnb-blm_uRdaC0_Nv3OUwjEY5qVCxCup4/edit#heading=h.mjo1bish3xni
    31  
    32     PCs:
    33  
    34     Wasm doesn't have PCs in the normal sense that you can jump
    35     to or call to. Instead, we simulate these PCs using our own construct.
    36  
    37     A PC in the Wasm implementation is the combination of a function
    38     ID and a block ID within that function. The function ID is an index
    39     into a function table which transfers control to the start of the
    40     function in question, and the block ID is a sequential integer
    41     indicating where in the function we are.
    42  
    43     Every function starts with a branch table which transfers control
    44     to the place in the function indicated by the block ID. The block
    45     ID is provided to the function as the sole Wasm argument.
    46  
    47     Block IDs do not encode every possible PC. They only encode places
    48     in the function where it might be suspended. Typically these places
    49     are call sites.
    50  
    51     Sometimes we encode the function ID and block ID separately. When
    52     recorded together as a single integer, we use the value F<<16+B.
    53  
    54     Threads:
    55  
    56     Wasm doesn't (yet) have threads. We have to simulate threads by
    57     keeping goroutine stacks in linear memory and unwinding
    58     the Wasm stack each time we want to switch goroutines.
    59  
    60     To support unwinding a stack, each function call returns on the Wasm
    61     stack a boolean that tells the function whether it should return
    62     immediately or not. When returning immediately, a return address
    63     is left on the top of the Go stack indicating where the goroutine
    64     should be resumed.
    65  
    66     Stack pointer:
    67  
    68     There is a single global stack pointer which records the stack pointer
    69     used by the currently active goroutine. This is just an address in
    70     linear memory where the Go runtime is maintaining the stack for that
    71     goroutine.
    72  
    73     Functions cache the global stack pointer in a local variable for
    74     faster access, but any changes must be spilled to the global variable
    75     before any call and restored from the global variable after any call.
    76  
    77     Calling convention:
    78  
    79     All Go arguments and return values are passed on the Go stack, not
    80     the wasm stack. In addition, return addresses are pushed on the
    81     Go stack at every call point. Return addresses are not used during
    82     normal execution, they are used only when resuming goroutines.
    83     (So they are not really a "return address", they are a "resume address".)
    84  
    85     All Go functions have the Wasm type (i32)->i32. The argument
    86     is the block ID and the return value is the exit immediately flag.
    87  
    88     Callsite:
    89      - write arguments to the Go stack (starting at SP+0)
    90      - push return address to Go stack (8 bytes)
    91      - write local SP to global SP
    92      - push 0 (type i32) to Wasm stack
    93      - issue Call
    94      - restore local SP from global SP
    95      - pop int32 from top of Wasm stack. If nonzero, exit function immediately.
    96      - use results from Go stack (starting at SP+sizeof(args))
    97         - note that the callee will have popped the return address
    98  
    99     Prologue:
   100      - initialize local SP from global SP
   101      - jump to the location indicated by the block ID argument
   102        (which appears in local variable 0)
   103      - at block 0
   104        - check for Go stack overflow, call morestack if needed
   105        - subtract frame size from SP
   106        - note that arguments now start at SP+framesize+8
   107  
   108     Normal epilogue:
   109      - pop frame from Go stack
   110      - pop return address from Go stack
   111      - push 0 (type i32) on the Wasm stack
   112      - return
   113     Exit immediately epilogue:
   114      - push 1 (type i32) on the Wasm stack
   115      - return
   116      - note that the return address and stack frame are left on the Go stack
   117  
   118     The main loop that executes goroutines is wasm_pc_f_loop, in
   119     runtime/rt0_js_wasm.s. It grabs the saved return address from
   120     the top of the Go stack (actually SP-8?), splits it up into F
   121     and B parts, then calls F with its Wasm argument set to B.
   122  
   123     Note that when resuming a goroutine, only the most recent function
   124     invocation of that goroutine appears on the Wasm stack. When that
   125     Wasm function returns normally, the next most recent frame will
   126     then be started up by wasm_pc_f_loop.
   127  
   128     Global 0 is SP (stack pointer)
   129     Global 1 is CTXT (closure pointer)
   130     Global 2 is GP (goroutine pointer)
   131  */
   132  
   133  func Init(arch *ssagen.ArchInfo) {
   134  	arch.LinkArch = &wasm.Linkwasm
   135  	arch.REGSP = wasm.REG_SP
   136  	arch.MAXWIDTH = 1 << 50
   137  
   138  	arch.ZeroRange = zeroRange
   139  	arch.Ginsnop = ginsnop
   140  
   141  	arch.SSAMarkMoves = ssaMarkMoves
   142  	arch.SSAGenValue = ssaGenValue
   143  	arch.SSAGenBlock = ssaGenBlock
   144  }
   145  
   146  func zeroRange(pp *objw.Progs, p *obj.Prog, off, cnt int64, state *uint32) *obj.Prog {
   147  	if cnt == 0 {
   148  		return p
   149  	}
   150  	if cnt%8 != 0 {
   151  		base.Fatalf("zerorange count not a multiple of widthptr %d", cnt)
   152  	}
   153  
   154  	for i := int64(0); i < cnt; i += 8 {
   155  		p = pp.Append(p, wasm.AGet, obj.TYPE_REG, wasm.REG_SP, 0, 0, 0, 0)
   156  		p = pp.Append(p, wasm.AI64Const, obj.TYPE_CONST, 0, 0, 0, 0, 0)
   157  		p = pp.Append(p, wasm.AI64Store, 0, 0, 0, obj.TYPE_CONST, 0, off+i)
   158  	}
   159  
   160  	return p
   161  }
   162  
   163  func ginsnop(pp *objw.Progs) *obj.Prog {
   164  	return pp.Prog(wasm.ANop)
   165  }
   166  
   167  func ssaMarkMoves(s *ssagen.State, b *ssa.Block) {
   168  }
   169  
   170  func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) {
   171  	switch b.Kind {
   172  	case ssa.BlockPlain:
   173  		if next != b.Succs[0].Block() {
   174  			s.Br(obj.AJMP, b.Succs[0].Block())
   175  		}
   176  
   177  	case ssa.BlockIf:
   178  		switch next {
   179  		case b.Succs[0].Block():
   180  			// if false, jump to b.Succs[1]
   181  			getValue32(s, b.Controls[0])
   182  			s.Prog(wasm.AI32Eqz)
   183  			s.Prog(wasm.AIf)
   184  			s.Br(obj.AJMP, b.Succs[1].Block())
   185  			s.Prog(wasm.AEnd)
   186  		case b.Succs[1].Block():
   187  			// if true, jump to b.Succs[0]
   188  			getValue32(s, b.Controls[0])
   189  			s.Prog(wasm.AIf)
   190  			s.Br(obj.AJMP, b.Succs[0].Block())
   191  			s.Prog(wasm.AEnd)
   192  		default:
   193  			// if true, jump to b.Succs[0], else jump to b.Succs[1]
   194  			getValue32(s, b.Controls[0])
   195  			s.Prog(wasm.AIf)
   196  			s.Br(obj.AJMP, b.Succs[0].Block())
   197  			s.Prog(wasm.AEnd)
   198  			s.Br(obj.AJMP, b.Succs[1].Block())
   199  		}
   200  
   201  	case ssa.BlockRet:
   202  		s.Prog(obj.ARET)
   203  
   204  	case ssa.BlockExit, ssa.BlockRetJmp:
   205  
   206  	case ssa.BlockDefer:
   207  		p := s.Prog(wasm.AGet)
   208  		p.From = obj.Addr{Type: obj.TYPE_REG, Reg: wasm.REG_RET0}
   209  		s.Prog(wasm.AI64Eqz)
   210  		s.Prog(wasm.AI32Eqz)
   211  		s.Prog(wasm.AIf)
   212  		s.Br(obj.AJMP, b.Succs[1].Block())
   213  		s.Prog(wasm.AEnd)
   214  		if next != b.Succs[0].Block() {
   215  			s.Br(obj.AJMP, b.Succs[0].Block())
   216  		}
   217  
   218  	default:
   219  		panic("unexpected block")
   220  	}
   221  
   222  	// Entry point for the next block. Used by the JMP in goToBlock.
   223  	s.Prog(wasm.ARESUMEPOINT)
   224  
   225  	if s.OnWasmStackSkipped != 0 {
   226  		panic("wasm: bad stack")
   227  	}
   228  }
   229  
   230  func ssaGenValue(s *ssagen.State, v *ssa.Value) {
   231  	switch v.Op {
   232  	case ssa.OpWasmLoweredStaticCall, ssa.OpWasmLoweredClosureCall, ssa.OpWasmLoweredInterCall, ssa.OpWasmLoweredTailCall:
   233  		s.PrepareCall(v)
   234  		if call, ok := v.Aux.(*ssa.AuxCall); ok && call.Fn == ir.Syms.Deferreturn {
   235  			// The runtime needs to inject jumps to
   236  			// deferreturn calls using the address in
   237  			// _func.deferreturn. Hence, the call to
   238  			// deferreturn must itself be a resumption
   239  			// point so it gets a target PC.
   240  			s.Prog(wasm.ARESUMEPOINT)
   241  		}
   242  		if v.Op == ssa.OpWasmLoweredClosureCall {
   243  			getValue64(s, v.Args[1])
   244  			setReg(s, wasm.REG_CTXT)
   245  		}
   246  		if call, ok := v.Aux.(*ssa.AuxCall); ok && call.Fn != nil {
   247  			sym := call.Fn
   248  			p := s.Prog(obj.ACALL)
   249  			p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: sym}
   250  			p.Pos = v.Pos
   251  			if v.Op == ssa.OpWasmLoweredTailCall {
   252  				p.As = obj.ARET
   253  			}
   254  		} else {
   255  			getValue64(s, v.Args[0])
   256  			p := s.Prog(obj.ACALL)
   257  			p.To = obj.Addr{Type: obj.TYPE_NONE}
   258  			p.Pos = v.Pos
   259  		}
   260  
   261  	case ssa.OpWasmLoweredMove:
   262  		getValue32(s, v.Args[0])
   263  		getValue32(s, v.Args[1])
   264  		i32Const(s, int32(v.AuxInt))
   265  		s.Prog(wasm.AMemoryCopy)
   266  
   267  	case ssa.OpWasmLoweredZero:
   268  		getValue32(s, v.Args[0])
   269  		i32Const(s, 0)
   270  		i32Const(s, int32(v.AuxInt))
   271  		s.Prog(wasm.AMemoryFill)
   272  
   273  	case ssa.OpWasmLoweredNilCheck:
   274  		getValue64(s, v.Args[0])
   275  		s.Prog(wasm.AI64Eqz)
   276  		s.Prog(wasm.AIf)
   277  		p := s.Prog(wasm.ACALLNORESUME)
   278  		p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.SigPanic}
   279  		s.Prog(wasm.AEnd)
   280  		if logopt.Enabled() {
   281  			logopt.LogOpt(v.Pos, "nilcheck", "genssa", v.Block.Func.Name)
   282  		}
   283  		if base.Debug.Nil != 0 && v.Pos.Line() > 1 {	// v.Pos.Line()==1 in generated wrappers
   284  			base.WarnfAt(v.Pos, "generated nil check")
   285  		}
   286  
   287  	case ssa.OpWasmLoweredWB:
   288  		p := s.Prog(wasm.ACall)
   289  		// AuxInt encodes how many buffer entries we need.
   290  		p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.GCWriteBarrier[v.AuxInt-1]}
   291  		setReg(s, v.Reg0())	// move result from wasm stack to register local
   292  
   293  	case ssa.OpWasmI64Store8, ssa.OpWasmI64Store16, ssa.OpWasmI64Store32, ssa.OpWasmI64Store, ssa.OpWasmF32Store, ssa.OpWasmF64Store:
   294  		getValue32(s, v.Args[0])
   295  		getValue64(s, v.Args[1])
   296  		p := s.Prog(v.Op.Asm())
   297  		p.To = obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt}
   298  
   299  	case ssa.OpStoreReg:
   300  		getReg(s, wasm.REG_SP)
   301  		getValue64(s, v.Args[0])
   302  		p := s.Prog(storeOp(v.Type))
   303  		ssagen.AddrAuto(&p.To, v)
   304  
   305  	case ssa.OpClobber, ssa.OpClobberReg:
   306  		// TODO: implement for clobberdead experiment. Nop is ok for now.
   307  
   308  	default:
   309  		if v.Type.IsMemory() {
   310  			return
   311  		}
   312  		if v.OnWasmStack {
   313  			s.OnWasmStackSkipped++
   314  			// If a Value is marked OnWasmStack, we don't generate the value and store it to a register now.
   315  			// Instead, we delay the generation to when the value is used and then directly generate it on the WebAssembly stack.
   316  			return
   317  		}
   318  		ssaGenValueOnStack(s, v, true)
   319  		if s.OnWasmStackSkipped != 0 {
   320  			panic("wasm: bad stack")
   321  		}
   322  		setReg(s, v.Reg())
   323  	}
   324  }
   325  
   326  func ssaGenValueOnStack(s *ssagen.State, v *ssa.Value, extend bool) {
   327  	switch v.Op {
   328  	case ssa.OpWasmLoweredGetClosurePtr:
   329  		getReg(s, wasm.REG_CTXT)
   330  
   331  	case ssa.OpWasmLoweredGetCallerPC:
   332  		p := s.Prog(wasm.AI64Load)
   333  		// Caller PC is stored 8 bytes below first parameter.
   334  		p.From = obj.Addr{
   335  			Type:	obj.TYPE_MEM,
   336  			Name:	obj.NAME_PARAM,
   337  			Offset:	-8,
   338  		}
   339  
   340  	case ssa.OpWasmLoweredGetCallerSP:
   341  		p := s.Prog(wasm.AGet)
   342  		// Caller SP is the address of the first parameter.
   343  		p.From = obj.Addr{
   344  			Type:	obj.TYPE_ADDR,
   345  			Name:	obj.NAME_PARAM,
   346  			Reg:	wasm.REG_SP,
   347  			Offset:	0,
   348  		}
   349  
   350  	case ssa.OpWasmLoweredAddr:
   351  		if v.Aux == nil {	// address of off(SP), no symbol
   352  			getValue64(s, v.Args[0])
   353  			i64Const(s, v.AuxInt)
   354  			s.Prog(wasm.AI64Add)
   355  			break
   356  		}
   357  		p := s.Prog(wasm.AGet)
   358  		p.From.Type = obj.TYPE_ADDR
   359  		switch v.Aux.(type) {
   360  		case *obj.LSym:
   361  			ssagen.AddAux(&p.From, v)
   362  		case *ir.Name:
   363  			p.From.Reg = v.Args[0].Reg()
   364  			ssagen.AddAux(&p.From, v)
   365  		default:
   366  			panic("wasm: bad LoweredAddr")
   367  		}
   368  
   369  	case ssa.OpWasmLoweredConvert:
   370  		getValue64(s, v.Args[0])
   371  
   372  	case ssa.OpWasmSelect:
   373  		getValue64(s, v.Args[0])
   374  		getValue64(s, v.Args[1])
   375  		getValue32(s, v.Args[2])
   376  		s.Prog(v.Op.Asm())
   377  
   378  	case ssa.OpWasmI64AddConst:
   379  		getValue64(s, v.Args[0])
   380  		i64Const(s, v.AuxInt)
   381  		s.Prog(v.Op.Asm())
   382  
   383  	case ssa.OpWasmI64Const:
   384  		i64Const(s, v.AuxInt)
   385  
   386  	case ssa.OpWasmF32Const:
   387  		f32Const(s, v.AuxFloat())
   388  
   389  	case ssa.OpWasmF64Const:
   390  		f64Const(s, v.AuxFloat())
   391  
   392  	case ssa.OpWasmI64Load8U, ssa.OpWasmI64Load8S, ssa.OpWasmI64Load16U, ssa.OpWasmI64Load16S, ssa.OpWasmI64Load32U, ssa.OpWasmI64Load32S, ssa.OpWasmI64Load, ssa.OpWasmF32Load, ssa.OpWasmF64Load:
   393  		getValue32(s, v.Args[0])
   394  		p := s.Prog(v.Op.Asm())
   395  		p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt}
   396  
   397  	case ssa.OpWasmI64Eqz:
   398  		getValue64(s, v.Args[0])
   399  		s.Prog(v.Op.Asm())
   400  		if extend {
   401  			s.Prog(wasm.AI64ExtendI32U)
   402  		}
   403  
   404  	case ssa.OpWasmI64Eq, ssa.OpWasmI64Ne, ssa.OpWasmI64LtS, ssa.OpWasmI64LtU, ssa.OpWasmI64GtS, ssa.OpWasmI64GtU, ssa.OpWasmI64LeS, ssa.OpWasmI64LeU, ssa.OpWasmI64GeS, ssa.OpWasmI64GeU,
   405  		ssa.OpWasmF32Eq, ssa.OpWasmF32Ne, ssa.OpWasmF32Lt, ssa.OpWasmF32Gt, ssa.OpWasmF32Le, ssa.OpWasmF32Ge,
   406  		ssa.OpWasmF64Eq, ssa.OpWasmF64Ne, ssa.OpWasmF64Lt, ssa.OpWasmF64Gt, ssa.OpWasmF64Le, ssa.OpWasmF64Ge:
   407  		getValue64(s, v.Args[0])
   408  		getValue64(s, v.Args[1])
   409  		s.Prog(v.Op.Asm())
   410  		if extend {
   411  			s.Prog(wasm.AI64ExtendI32U)
   412  		}
   413  
   414  	case ssa.OpWasmI64Add, ssa.OpWasmI64Sub, ssa.OpWasmI64Mul, ssa.OpWasmI64DivU, ssa.OpWasmI64RemS, ssa.OpWasmI64RemU, ssa.OpWasmI64And, ssa.OpWasmI64Or, ssa.OpWasmI64Xor, ssa.OpWasmI64Shl, ssa.OpWasmI64ShrS, ssa.OpWasmI64ShrU, ssa.OpWasmI64Rotl,
   415  		ssa.OpWasmF32Add, ssa.OpWasmF32Sub, ssa.OpWasmF32Mul, ssa.OpWasmF32Div, ssa.OpWasmF32Copysign,
   416  		ssa.OpWasmF64Add, ssa.OpWasmF64Sub, ssa.OpWasmF64Mul, ssa.OpWasmF64Div, ssa.OpWasmF64Copysign:
   417  		getValue64(s, v.Args[0])
   418  		getValue64(s, v.Args[1])
   419  		s.Prog(v.Op.Asm())
   420  
   421  	case ssa.OpWasmI32Rotl:
   422  		getValue32(s, v.Args[0])
   423  		getValue32(s, v.Args[1])
   424  		s.Prog(wasm.AI32Rotl)
   425  		s.Prog(wasm.AI64ExtendI32U)
   426  
   427  	case ssa.OpWasmI64DivS:
   428  		getValue64(s, v.Args[0])
   429  		getValue64(s, v.Args[1])
   430  		if v.Type.Size() == 8 {
   431  			// Division of int64 needs helper function wasmDiv to handle the MinInt64 / -1 case.
   432  			p := s.Prog(wasm.ACall)
   433  			p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.WasmDiv}
   434  			break
   435  		}
   436  		s.Prog(wasm.AI64DivS)
   437  
   438  	case ssa.OpWasmI64TruncSatF32S, ssa.OpWasmI64TruncSatF64S:
   439  		getValue64(s, v.Args[0])
   440  		if buildcfg.GOWASM.SatConv {
   441  			s.Prog(v.Op.Asm())
   442  		} else {
   443  			if v.Op == ssa.OpWasmI64TruncSatF32S {
   444  				s.Prog(wasm.AF64PromoteF32)
   445  			}
   446  			p := s.Prog(wasm.ACall)
   447  			p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.WasmTruncS}
   448  		}
   449  
   450  	case ssa.OpWasmI64TruncSatF32U, ssa.OpWasmI64TruncSatF64U:
   451  		getValue64(s, v.Args[0])
   452  		if buildcfg.GOWASM.SatConv {
   453  			s.Prog(v.Op.Asm())
   454  		} else {
   455  			if v.Op == ssa.OpWasmI64TruncSatF32U {
   456  				s.Prog(wasm.AF64PromoteF32)
   457  			}
   458  			p := s.Prog(wasm.ACall)
   459  			p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.WasmTruncU}
   460  		}
   461  
   462  	case ssa.OpWasmF32DemoteF64:
   463  		getValue64(s, v.Args[0])
   464  		s.Prog(v.Op.Asm())
   465  
   466  	case ssa.OpWasmF64PromoteF32:
   467  		getValue64(s, v.Args[0])
   468  		s.Prog(v.Op.Asm())
   469  
   470  	case ssa.OpWasmF32ConvertI64S, ssa.OpWasmF32ConvertI64U,
   471  		ssa.OpWasmF64ConvertI64S, ssa.OpWasmF64ConvertI64U,
   472  		ssa.OpWasmI64Extend8S, ssa.OpWasmI64Extend16S, ssa.OpWasmI64Extend32S,
   473  		ssa.OpWasmF32Neg, ssa.OpWasmF32Sqrt, ssa.OpWasmF32Trunc, ssa.OpWasmF32Ceil, ssa.OpWasmF32Floor, ssa.OpWasmF32Nearest, ssa.OpWasmF32Abs,
   474  		ssa.OpWasmF64Neg, ssa.OpWasmF64Sqrt, ssa.OpWasmF64Trunc, ssa.OpWasmF64Ceil, ssa.OpWasmF64Floor, ssa.OpWasmF64Nearest, ssa.OpWasmF64Abs,
   475  		ssa.OpWasmI64Ctz, ssa.OpWasmI64Clz, ssa.OpWasmI64Popcnt:
   476  		getValue64(s, v.Args[0])
   477  		s.Prog(v.Op.Asm())
   478  
   479  	case ssa.OpLoadReg:
   480  		p := s.Prog(loadOp(v.Type))
   481  		ssagen.AddrAuto(&p.From, v.Args[0])
   482  
   483  	case ssa.OpCopy:
   484  		getValue64(s, v.Args[0])
   485  
   486  	default:
   487  		v.Fatalf("unexpected op: %s", v.Op)
   488  
   489  	}
   490  }
   491  
   492  func isCmp(v *ssa.Value) bool {
   493  	switch v.Op {
   494  	case ssa.OpWasmI64Eqz, ssa.OpWasmI64Eq, ssa.OpWasmI64Ne, ssa.OpWasmI64LtS, ssa.OpWasmI64LtU, ssa.OpWasmI64GtS, ssa.OpWasmI64GtU, ssa.OpWasmI64LeS, ssa.OpWasmI64LeU, ssa.OpWasmI64GeS, ssa.OpWasmI64GeU,
   495  		ssa.OpWasmF32Eq, ssa.OpWasmF32Ne, ssa.OpWasmF32Lt, ssa.OpWasmF32Gt, ssa.OpWasmF32Le, ssa.OpWasmF32Ge,
   496  		ssa.OpWasmF64Eq, ssa.OpWasmF64Ne, ssa.OpWasmF64Lt, ssa.OpWasmF64Gt, ssa.OpWasmF64Le, ssa.OpWasmF64Ge:
   497  		return true
   498  	default:
   499  		return false
   500  	}
   501  }
   502  
   503  func getValue32(s *ssagen.State, v *ssa.Value) {
   504  	if v.OnWasmStack {
   505  		s.OnWasmStackSkipped--
   506  		ssaGenValueOnStack(s, v, false)
   507  		if !isCmp(v) {
   508  			s.Prog(wasm.AI32WrapI64)
   509  		}
   510  		return
   511  	}
   512  
   513  	reg := v.Reg()
   514  	getReg(s, reg)
   515  	if reg != wasm.REG_SP {
   516  		s.Prog(wasm.AI32WrapI64)
   517  	}
   518  }
   519  
   520  func getValue64(s *ssagen.State, v *ssa.Value) {
   521  	if v.OnWasmStack {
   522  		s.OnWasmStackSkipped--
   523  		ssaGenValueOnStack(s, v, true)
   524  		return
   525  	}
   526  
   527  	reg := v.Reg()
   528  	getReg(s, reg)
   529  	if reg == wasm.REG_SP {
   530  		s.Prog(wasm.AI64ExtendI32U)
   531  	}
   532  }
   533  
   534  func i32Const(s *ssagen.State, val int32) {
   535  	p := s.Prog(wasm.AI32Const)
   536  	p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: int64(val)}
   537  }
   538  
   539  func i64Const(s *ssagen.State, val int64) {
   540  	p := s.Prog(wasm.AI64Const)
   541  	p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: val}
   542  }
   543  
   544  func f32Const(s *ssagen.State, val float64) {
   545  	p := s.Prog(wasm.AF32Const)
   546  	p.From = obj.Addr{Type: obj.TYPE_FCONST, Val: val}
   547  }
   548  
   549  func f64Const(s *ssagen.State, val float64) {
   550  	p := s.Prog(wasm.AF64Const)
   551  	p.From = obj.Addr{Type: obj.TYPE_FCONST, Val: val}
   552  }
   553  
   554  func getReg(s *ssagen.State, reg int16) {
   555  	p := s.Prog(wasm.AGet)
   556  	p.From = obj.Addr{Type: obj.TYPE_REG, Reg: reg}
   557  }
   558  
   559  func setReg(s *ssagen.State, reg int16) {
   560  	p := s.Prog(wasm.ASet)
   561  	p.To = obj.Addr{Type: obj.TYPE_REG, Reg: reg}
   562  }
   563  
   564  func loadOp(t *types.Type) obj.As {
   565  	if t.IsFloat() {
   566  		switch t.Size() {
   567  		case 4:
   568  			return wasm.AF32Load
   569  		case 8:
   570  			return wasm.AF64Load
   571  		default:
   572  			panic("bad load type")
   573  		}
   574  	}
   575  
   576  	switch t.Size() {
   577  	case 1:
   578  		if t.IsSigned() {
   579  			return wasm.AI64Load8S
   580  		}
   581  		return wasm.AI64Load8U
   582  	case 2:
   583  		if t.IsSigned() {
   584  			return wasm.AI64Load16S
   585  		}
   586  		return wasm.AI64Load16U
   587  	case 4:
   588  		if t.IsSigned() {
   589  			return wasm.AI64Load32S
   590  		}
   591  		return wasm.AI64Load32U
   592  	case 8:
   593  		return wasm.AI64Load
   594  	default:
   595  		panic("bad load type")
   596  	}
   597  }
   598  
   599  func storeOp(t *types.Type) obj.As {
   600  	if t.IsFloat() {
   601  		switch t.Size() {
   602  		case 4:
   603  			return wasm.AF32Store
   604  		case 8:
   605  			return wasm.AF64Store
   606  		default:
   607  			panic("bad store type")
   608  		}
   609  	}
   610  
   611  	switch t.Size() {
   612  	case 1:
   613  		return wasm.AI64Store8
   614  	case 2:
   615  		return wasm.AI64Store16
   616  	case 4:
   617  		return wasm.AI64Store32
   618  	case 8:
   619  		return wasm.AI64Store
   620  	default:
   621  		panic("bad store type")
   622  	}
   623  }