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