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

View as plain text