Black Lives Matter. Support the Equal Justice Initiative.

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

Documentation: cmd/compile/internal/wasm

     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/gc"
     9  	"cmd/compile/internal/logopt"
    10  	"cmd/compile/internal/ssa"
    11  	"cmd/compile/internal/types"
    12  	"cmd/internal/obj"
    13  	"cmd/internal/obj/wasm"
    14  	"cmd/internal/objabi"
    15  )
    16  
    17  func Init(arch *gc.Arch) {
    18  	arch.LinkArch = &wasm.Linkwasm
    19  	arch.REGSP = wasm.REG_SP
    20  	arch.MAXWIDTH = 1 << 50
    21  
    22  	arch.ZeroRange = zeroRange
    23  	arch.Ginsnop = ginsnop
    24  	arch.Ginsnopdefer = ginsnop
    25  
    26  	arch.SSAMarkMoves = ssaMarkMoves
    27  	arch.SSAGenValue = ssaGenValue
    28  	arch.SSAGenBlock = ssaGenBlock
    29  }
    30  
    31  func zeroRange(pp *gc.Progs, p *obj.Prog, off, cnt int64, state *uint32) *obj.Prog {
    32  	if cnt == 0 {
    33  		return p
    34  	}
    35  	if cnt%8 != 0 {
    36  		gc.Fatalf("zerorange count not a multiple of widthptr %d", cnt)
    37  	}
    38  
    39  	for i := int64(0); i < cnt; i += 8 {
    40  		p = pp.Appendpp(p, wasm.AGet, obj.TYPE_REG, wasm.REG_SP, 0, 0, 0, 0)
    41  		p = pp.Appendpp(p, wasm.AI64Const, obj.TYPE_CONST, 0, 0, 0, 0, 0)
    42  		p = pp.Appendpp(p, wasm.AI64Store, 0, 0, 0, obj.TYPE_CONST, 0, off+i)
    43  	}
    44  
    45  	return p
    46  }
    47  
    48  func ginsnop(pp *gc.Progs) *obj.Prog {
    49  	return pp.Prog(wasm.ANop)
    50  }
    51  
    52  func ssaMarkMoves(s *gc.SSAGenState, b *ssa.Block) {
    53  }
    54  
    55  func ssaGenBlock(s *gc.SSAGenState, b, next *ssa.Block) {
    56  	switch b.Kind {
    57  	case ssa.BlockPlain:
    58  		if next != b.Succs[0].Block() {
    59  			s.Br(obj.AJMP, b.Succs[0].Block())
    60  		}
    61  
    62  	case ssa.BlockIf:
    63  		switch next {
    64  		case b.Succs[0].Block():
    65  			// if false, jump to b.Succs[1]
    66  			getValue32(s, b.Controls[0])
    67  			s.Prog(wasm.AI32Eqz)
    68  			s.Prog(wasm.AIf)
    69  			s.Br(obj.AJMP, b.Succs[1].Block())
    70  			s.Prog(wasm.AEnd)
    71  		case b.Succs[1].Block():
    72  			// if true, jump to b.Succs[0]
    73  			getValue32(s, b.Controls[0])
    74  			s.Prog(wasm.AIf)
    75  			s.Br(obj.AJMP, b.Succs[0].Block())
    76  			s.Prog(wasm.AEnd)
    77  		default:
    78  			// if true, jump to b.Succs[0], else jump to b.Succs[1]
    79  			getValue32(s, b.Controls[0])
    80  			s.Prog(wasm.AIf)
    81  			s.Br(obj.AJMP, b.Succs[0].Block())
    82  			s.Prog(wasm.AEnd)
    83  			s.Br(obj.AJMP, b.Succs[1].Block())
    84  		}
    85  
    86  	case ssa.BlockRet:
    87  		s.Prog(obj.ARET)
    88  
    89  	case ssa.BlockRetJmp:
    90  		p := s.Prog(obj.ARET)
    91  		p.To.Type = obj.TYPE_MEM
    92  		p.To.Name = obj.NAME_EXTERN
    93  		p.To.Sym = b.Aux.(*obj.LSym)
    94  
    95  	case ssa.BlockExit:
    96  
    97  	case ssa.BlockDefer:
    98  		p := s.Prog(wasm.AGet)
    99  		p.From = obj.Addr{Type: obj.TYPE_REG, Reg: wasm.REG_RET0}
   100  		s.Prog(wasm.AI64Eqz)
   101  		s.Prog(wasm.AI32Eqz)
   102  		s.Prog(wasm.AIf)
   103  		s.Br(obj.AJMP, b.Succs[1].Block())
   104  		s.Prog(wasm.AEnd)
   105  		if next != b.Succs[0].Block() {
   106  			s.Br(obj.AJMP, b.Succs[0].Block())
   107  		}
   108  
   109  	default:
   110  		panic("unexpected block")
   111  	}
   112  
   113  	// Entry point for the next block. Used by the JMP in goToBlock.
   114  	s.Prog(wasm.ARESUMEPOINT)
   115  
   116  	if s.OnWasmStackSkipped != 0 {
   117  		panic("wasm: bad stack")
   118  	}
   119  }
   120  
   121  func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
   122  	switch v.Op {
   123  	case ssa.OpWasmLoweredStaticCall, ssa.OpWasmLoweredClosureCall, ssa.OpWasmLoweredInterCall:
   124  		s.PrepareCall(v)
   125  		if v.Aux == gc.Deferreturn {
   126  			// add a resume point before call to deferreturn so it can be called again via jmpdefer
   127  			s.Prog(wasm.ARESUMEPOINT)
   128  		}
   129  		if v.Op == ssa.OpWasmLoweredClosureCall {
   130  			getValue64(s, v.Args[1])
   131  			setReg(s, wasm.REG_CTXT)
   132  		}
   133  		if sym, ok := v.Aux.(*obj.LSym); ok {
   134  			p := s.Prog(obj.ACALL)
   135  			p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: sym}
   136  			p.Pos = v.Pos
   137  		} else {
   138  			getValue64(s, v.Args[0])
   139  			p := s.Prog(obj.ACALL)
   140  			p.To = obj.Addr{Type: obj.TYPE_NONE}
   141  			p.Pos = v.Pos
   142  		}
   143  
   144  	case ssa.OpWasmLoweredMove:
   145  		getValue32(s, v.Args[0])
   146  		getValue32(s, v.Args[1])
   147  		i32Const(s, int32(v.AuxInt))
   148  		p := s.Prog(wasm.ACall)
   149  		p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmMove}
   150  
   151  	case ssa.OpWasmLoweredZero:
   152  		getValue32(s, v.Args[0])
   153  		i32Const(s, int32(v.AuxInt))
   154  		p := s.Prog(wasm.ACall)
   155  		p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmZero}
   156  
   157  	case ssa.OpWasmLoweredNilCheck:
   158  		getValue64(s, v.Args[0])
   159  		s.Prog(wasm.AI64Eqz)
   160  		s.Prog(wasm.AIf)
   161  		p := s.Prog(wasm.ACALLNORESUME)
   162  		p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.SigPanic}
   163  		s.Prog(wasm.AEnd)
   164  		if logopt.Enabled() {
   165  			logopt.LogOpt(v.Pos, "nilcheck", "genssa", v.Block.Func.Name)
   166  		}
   167  		if gc.Debug_checknil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers
   168  			gc.Warnl(v.Pos, "generated nil check")
   169  		}
   170  
   171  	case ssa.OpWasmLoweredWB:
   172  		getValue64(s, v.Args[0])
   173  		getValue64(s, v.Args[1])
   174  		p := s.Prog(wasm.ACALLNORESUME) // TODO(neelance): If possible, turn this into a simple wasm.ACall).
   175  		p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: v.Aux.(*obj.LSym)}
   176  
   177  	case ssa.OpWasmI64Store8, ssa.OpWasmI64Store16, ssa.OpWasmI64Store32, ssa.OpWasmI64Store, ssa.OpWasmF32Store, ssa.OpWasmF64Store:
   178  		getValue32(s, v.Args[0])
   179  		getValue64(s, v.Args[1])
   180  		p := s.Prog(v.Op.Asm())
   181  		p.To = obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt}
   182  
   183  	case ssa.OpStoreReg:
   184  		getReg(s, wasm.REG_SP)
   185  		getValue64(s, v.Args[0])
   186  		p := s.Prog(storeOp(v.Type))
   187  		gc.AddrAuto(&p.To, v)
   188  
   189  	default:
   190  		if v.Type.IsMemory() {
   191  			return
   192  		}
   193  		if v.OnWasmStack {
   194  			s.OnWasmStackSkipped++
   195  			// If a Value is marked OnWasmStack, we don't generate the value and store it to a register now.
   196  			// Instead, we delay the generation to when the value is used and then directly generate it on the WebAssembly stack.
   197  			return
   198  		}
   199  		ssaGenValueOnStack(s, v, true)
   200  		if s.OnWasmStackSkipped != 0 {
   201  			panic("wasm: bad stack")
   202  		}
   203  		setReg(s, v.Reg())
   204  	}
   205  }
   206  
   207  func ssaGenValueOnStack(s *gc.SSAGenState, v *ssa.Value, extend bool) {
   208  	switch v.Op {
   209  	case ssa.OpWasmLoweredGetClosurePtr:
   210  		getReg(s, wasm.REG_CTXT)
   211  
   212  	case ssa.OpWasmLoweredGetCallerPC:
   213  		p := s.Prog(wasm.AI64Load)
   214  		// Caller PC is stored 8 bytes below first parameter.
   215  		p.From = obj.Addr{
   216  			Type:   obj.TYPE_MEM,
   217  			Name:   obj.NAME_PARAM,
   218  			Offset: -8,
   219  		}
   220  
   221  	case ssa.OpWasmLoweredGetCallerSP:
   222  		p := s.Prog(wasm.AGet)
   223  		// Caller SP is the address of the first parameter.
   224  		p.From = obj.Addr{
   225  			Type:   obj.TYPE_ADDR,
   226  			Name:   obj.NAME_PARAM,
   227  			Reg:    wasm.REG_SP,
   228  			Offset: 0,
   229  		}
   230  
   231  	case ssa.OpWasmLoweredAddr:
   232  		p := s.Prog(wasm.AGet)
   233  		p.From.Type = obj.TYPE_ADDR
   234  		switch v.Aux.(type) {
   235  		case *obj.LSym:
   236  			gc.AddAux(&p.From, v)
   237  		case *gc.Node:
   238  			p.From.Reg = v.Args[0].Reg()
   239  			gc.AddAux(&p.From, v)
   240  		default:
   241  			panic("wasm: bad LoweredAddr")
   242  		}
   243  
   244  	case ssa.OpWasmLoweredConvert:
   245  		getValue64(s, v.Args[0])
   246  
   247  	case ssa.OpWasmSelect:
   248  		getValue64(s, v.Args[0])
   249  		getValue64(s, v.Args[1])
   250  		getValue32(s, v.Args[2])
   251  		s.Prog(v.Op.Asm())
   252  
   253  	case ssa.OpWasmI64AddConst:
   254  		getValue64(s, v.Args[0])
   255  		i64Const(s, v.AuxInt)
   256  		s.Prog(v.Op.Asm())
   257  
   258  	case ssa.OpWasmI64Const:
   259  		i64Const(s, v.AuxInt)
   260  
   261  	case ssa.OpWasmF32Const:
   262  		f32Const(s, v.AuxFloat())
   263  
   264  	case ssa.OpWasmF64Const:
   265  		f64Const(s, v.AuxFloat())
   266  
   267  	case ssa.OpWasmI64Load8U, ssa.OpWasmI64Load8S, ssa.OpWasmI64Load16U, ssa.OpWasmI64Load16S, ssa.OpWasmI64Load32U, ssa.OpWasmI64Load32S, ssa.OpWasmI64Load, ssa.OpWasmF32Load, ssa.OpWasmF64Load:
   268  		getValue32(s, v.Args[0])
   269  		p := s.Prog(v.Op.Asm())
   270  		p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: v.AuxInt}
   271  
   272  	case ssa.OpWasmI64Eqz:
   273  		getValue64(s, v.Args[0])
   274  		s.Prog(v.Op.Asm())
   275  		if extend {
   276  			s.Prog(wasm.AI64ExtendI32U)
   277  		}
   278  
   279  	case ssa.OpWasmI64Eq, ssa.OpWasmI64Ne, ssa.OpWasmI64LtS, ssa.OpWasmI64LtU, ssa.OpWasmI64GtS, ssa.OpWasmI64GtU, ssa.OpWasmI64LeS, ssa.OpWasmI64LeU, ssa.OpWasmI64GeS, ssa.OpWasmI64GeU,
   280  		ssa.OpWasmF32Eq, ssa.OpWasmF32Ne, ssa.OpWasmF32Lt, ssa.OpWasmF32Gt, ssa.OpWasmF32Le, ssa.OpWasmF32Ge,
   281  		ssa.OpWasmF64Eq, ssa.OpWasmF64Ne, ssa.OpWasmF64Lt, ssa.OpWasmF64Gt, ssa.OpWasmF64Le, ssa.OpWasmF64Ge:
   282  		getValue64(s, v.Args[0])
   283  		getValue64(s, v.Args[1])
   284  		s.Prog(v.Op.Asm())
   285  		if extend {
   286  			s.Prog(wasm.AI64ExtendI32U)
   287  		}
   288  
   289  	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,
   290  		ssa.OpWasmF32Add, ssa.OpWasmF32Sub, ssa.OpWasmF32Mul, ssa.OpWasmF32Div, ssa.OpWasmF32Copysign,
   291  		ssa.OpWasmF64Add, ssa.OpWasmF64Sub, ssa.OpWasmF64Mul, ssa.OpWasmF64Div, ssa.OpWasmF64Copysign:
   292  		getValue64(s, v.Args[0])
   293  		getValue64(s, v.Args[1])
   294  		s.Prog(v.Op.Asm())
   295  
   296  	case ssa.OpWasmI32Rotl:
   297  		getValue32(s, v.Args[0])
   298  		getValue32(s, v.Args[1])
   299  		s.Prog(wasm.AI32Rotl)
   300  		s.Prog(wasm.AI64ExtendI32U)
   301  
   302  	case ssa.OpWasmI64DivS:
   303  		getValue64(s, v.Args[0])
   304  		getValue64(s, v.Args[1])
   305  		if v.Type.Size() == 8 {
   306  			// Division of int64 needs helper function wasmDiv to handle the MinInt64 / -1 case.
   307  			p := s.Prog(wasm.ACall)
   308  			p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmDiv}
   309  			break
   310  		}
   311  		s.Prog(wasm.AI64DivS)
   312  
   313  	case ssa.OpWasmI64TruncSatF32S, ssa.OpWasmI64TruncSatF64S:
   314  		getValue64(s, v.Args[0])
   315  		if objabi.GOWASM.SatConv {
   316  			s.Prog(v.Op.Asm())
   317  		} else {
   318  			if v.Op == ssa.OpWasmI64TruncSatF32S {
   319  				s.Prog(wasm.AF64PromoteF32)
   320  			}
   321  			p := s.Prog(wasm.ACall)
   322  			p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmTruncS}
   323  		}
   324  
   325  	case ssa.OpWasmI64TruncSatF32U, ssa.OpWasmI64TruncSatF64U:
   326  		getValue64(s, v.Args[0])
   327  		if objabi.GOWASM.SatConv {
   328  			s.Prog(v.Op.Asm())
   329  		} else {
   330  			if v.Op == ssa.OpWasmI64TruncSatF32U {
   331  				s.Prog(wasm.AF64PromoteF32)
   332  			}
   333  			p := s.Prog(wasm.ACall)
   334  			p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: gc.WasmTruncU}
   335  		}
   336  
   337  	case ssa.OpWasmF32DemoteF64:
   338  		getValue64(s, v.Args[0])
   339  		s.Prog(v.Op.Asm())
   340  
   341  	case ssa.OpWasmF64PromoteF32:
   342  		getValue64(s, v.Args[0])
   343  		s.Prog(v.Op.Asm())
   344  
   345  	case ssa.OpWasmF32ConvertI64S, ssa.OpWasmF32ConvertI64U,
   346  		ssa.OpWasmF64ConvertI64S, ssa.OpWasmF64ConvertI64U,
   347  		ssa.OpWasmI64Extend8S, ssa.OpWasmI64Extend16S, ssa.OpWasmI64Extend32S,
   348  		ssa.OpWasmF32Neg, ssa.OpWasmF32Sqrt, ssa.OpWasmF32Trunc, ssa.OpWasmF32Ceil, ssa.OpWasmF32Floor, ssa.OpWasmF32Nearest, ssa.OpWasmF32Abs,
   349  		ssa.OpWasmF64Neg, ssa.OpWasmF64Sqrt, ssa.OpWasmF64Trunc, ssa.OpWasmF64Ceil, ssa.OpWasmF64Floor, ssa.OpWasmF64Nearest, ssa.OpWasmF64Abs,
   350  		ssa.OpWasmI64Ctz, ssa.OpWasmI64Clz, ssa.OpWasmI64Popcnt:
   351  		getValue64(s, v.Args[0])
   352  		s.Prog(v.Op.Asm())
   353  
   354  	case ssa.OpLoadReg:
   355  		p := s.Prog(loadOp(v.Type))
   356  		gc.AddrAuto(&p.From, v.Args[0])
   357  
   358  	case ssa.OpCopy:
   359  		getValue64(s, v.Args[0])
   360  
   361  	default:
   362  		v.Fatalf("unexpected op: %s", v.Op)
   363  
   364  	}
   365  }
   366  
   367  func isCmp(v *ssa.Value) bool {
   368  	switch v.Op {
   369  	case ssa.OpWasmI64Eqz, ssa.OpWasmI64Eq, ssa.OpWasmI64Ne, ssa.OpWasmI64LtS, ssa.OpWasmI64LtU, ssa.OpWasmI64GtS, ssa.OpWasmI64GtU, ssa.OpWasmI64LeS, ssa.OpWasmI64LeU, ssa.OpWasmI64GeS, ssa.OpWasmI64GeU,
   370  		ssa.OpWasmF32Eq, ssa.OpWasmF32Ne, ssa.OpWasmF32Lt, ssa.OpWasmF32Gt, ssa.OpWasmF32Le, ssa.OpWasmF32Ge,
   371  		ssa.OpWasmF64Eq, ssa.OpWasmF64Ne, ssa.OpWasmF64Lt, ssa.OpWasmF64Gt, ssa.OpWasmF64Le, ssa.OpWasmF64Ge:
   372  		return true
   373  	default:
   374  		return false
   375  	}
   376  }
   377  
   378  func getValue32(s *gc.SSAGenState, v *ssa.Value) {
   379  	if v.OnWasmStack {
   380  		s.OnWasmStackSkipped--
   381  		ssaGenValueOnStack(s, v, false)
   382  		if !isCmp(v) {
   383  			s.Prog(wasm.AI32WrapI64)
   384  		}
   385  		return
   386  	}
   387  
   388  	reg := v.Reg()
   389  	getReg(s, reg)
   390  	if reg != wasm.REG_SP {
   391  		s.Prog(wasm.AI32WrapI64)
   392  	}
   393  }
   394  
   395  func getValue64(s *gc.SSAGenState, v *ssa.Value) {
   396  	if v.OnWasmStack {
   397  		s.OnWasmStackSkipped--
   398  		ssaGenValueOnStack(s, v, true)
   399  		return
   400  	}
   401  
   402  	reg := v.Reg()
   403  	getReg(s, reg)
   404  	if reg == wasm.REG_SP {
   405  		s.Prog(wasm.AI64ExtendI32U)
   406  	}
   407  }
   408  
   409  func i32Const(s *gc.SSAGenState, val int32) {
   410  	p := s.Prog(wasm.AI32Const)
   411  	p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: int64(val)}
   412  }
   413  
   414  func i64Const(s *gc.SSAGenState, val int64) {
   415  	p := s.Prog(wasm.AI64Const)
   416  	p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: val}
   417  }
   418  
   419  func f32Const(s *gc.SSAGenState, val float64) {
   420  	p := s.Prog(wasm.AF32Const)
   421  	p.From = obj.Addr{Type: obj.TYPE_FCONST, Val: val}
   422  }
   423  
   424  func f64Const(s *gc.SSAGenState, val float64) {
   425  	p := s.Prog(wasm.AF64Const)
   426  	p.From = obj.Addr{Type: obj.TYPE_FCONST, Val: val}
   427  }
   428  
   429  func getReg(s *gc.SSAGenState, reg int16) {
   430  	p := s.Prog(wasm.AGet)
   431  	p.From = obj.Addr{Type: obj.TYPE_REG, Reg: reg}
   432  }
   433  
   434  func setReg(s *gc.SSAGenState, reg int16) {
   435  	p := s.Prog(wasm.ASet)
   436  	p.To = obj.Addr{Type: obj.TYPE_REG, Reg: reg}
   437  }
   438  
   439  func loadOp(t *types.Type) obj.As {
   440  	if t.IsFloat() {
   441  		switch t.Size() {
   442  		case 4:
   443  			return wasm.AF32Load
   444  		case 8:
   445  			return wasm.AF64Load
   446  		default:
   447  			panic("bad load type")
   448  		}
   449  	}
   450  
   451  	switch t.Size() {
   452  	case 1:
   453  		if t.IsSigned() {
   454  			return wasm.AI64Load8S
   455  		}
   456  		return wasm.AI64Load8U
   457  	case 2:
   458  		if t.IsSigned() {
   459  			return wasm.AI64Load16S
   460  		}
   461  		return wasm.AI64Load16U
   462  	case 4:
   463  		if t.IsSigned() {
   464  			return wasm.AI64Load32S
   465  		}
   466  		return wasm.AI64Load32U
   467  	case 8:
   468  		return wasm.AI64Load
   469  	default:
   470  		panic("bad load type")
   471  	}
   472  }
   473  
   474  func storeOp(t *types.Type) obj.As {
   475  	if t.IsFloat() {
   476  		switch t.Size() {
   477  		case 4:
   478  			return wasm.AF32Store
   479  		case 8:
   480  			return wasm.AF64Store
   481  		default:
   482  			panic("bad store type")
   483  		}
   484  	}
   485  
   486  	switch t.Size() {
   487  	case 1:
   488  		return wasm.AI64Store8
   489  	case 2:
   490  		return wasm.AI64Store16
   491  	case 4:
   492  		return wasm.AI64Store32
   493  	case 8:
   494  		return wasm.AI64Store
   495  	default:
   496  		panic("bad store type")
   497  	}
   498  }
   499  

View as plain text