Source file src/cmd/compile/internal/wasm/ssa.go

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

View as plain text