github.com/bir3/gocompiler@v0.3.205/src/cmd/link/internal/wasm/asm.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 "bytes" 9 "github.com/bir3/gocompiler/src/cmd/internal/objabi" 10 "github.com/bir3/gocompiler/src/cmd/link/internal/ld" 11 "github.com/bir3/gocompiler/src/cmd/link/internal/loader" 12 "github.com/bir3/gocompiler/src/cmd/link/internal/sym" 13 "github.com/bir3/gocompiler/src/internal/buildcfg" 14 "io" 15 "regexp" 16 ) 17 18 const ( 19 I32 = 0x7F 20 I64 = 0x7E 21 F32 = 0x7D 22 F64 = 0x7C 23 ) 24 25 const ( 26 sectionCustom = 0 27 sectionType = 1 28 sectionImport = 2 29 sectionFunction = 3 30 sectionTable = 4 31 sectionMemory = 5 32 sectionGlobal = 6 33 sectionExport = 7 34 sectionStart = 8 35 sectionElement = 9 36 sectionCode = 10 37 sectionData = 11 38 ) 39 40 // funcValueOffset is the offset between the PC_F value of a function and the index of the function in WebAssembly 41 const funcValueOffset = 0x1000 // TODO(neelance): make function addresses play nice with heap addresses 42 43 func gentext(ctxt *ld.Link, ldr *loader.Loader) { 44 } 45 46 type wasmFunc struct { 47 Name string 48 Type uint32 49 Code []byte 50 } 51 52 type wasmFuncType struct { 53 Params []byte 54 Results []byte 55 } 56 57 var wasmFuncTypes = map[string]*wasmFuncType{ 58 "_rt0_wasm_js": {Params: []byte{}}, // 59 "wasm_export_run": {Params: []byte{I32, I32}}, // argc, argv 60 "wasm_export_resume": {Params: []byte{}}, // 61 "wasm_export_getsp": {Results: []byte{I32}}, // sp 62 "wasm_pc_f_loop": {Params: []byte{}}, // 63 "runtime.wasmDiv": {Params: []byte{I64, I64}, Results: []byte{I64}}, // x, y -> x/y 64 "runtime.wasmTruncS": {Params: []byte{F64}, Results: []byte{I64}}, // x -> int(x) 65 "runtime.wasmTruncU": {Params: []byte{F64}, Results: []byte{I64}}, // x -> uint(x) 66 "runtime.gcWriteBarrier": {Params: []byte{I64, I64}}, // ptr, val 67 "cmpbody": {Params: []byte{I64, I64, I64, I64}, Results: []byte{I64}}, // a, alen, b, blen -> -1/0/1 68 "memeqbody": {Params: []byte{I64, I64, I64}, Results: []byte{I64}}, // a, b, len -> 0/1 69 "memcmp": {Params: []byte{I32, I32, I32}, Results: []byte{I32}}, // a, b, len -> <0/0/>0 70 "memchr": {Params: []byte{I32, I32, I32}, Results: []byte{I32}}, // s, c, len -> index 71 } 72 73 func assignAddress(ldr *loader.Loader, sect *sym.Section, n int, s loader.Sym, va uint64, isTramp bool) (*sym.Section, int, uint64) { 74 // WebAssembly functions do not live in the same address space as the linear memory. 75 // Instead, WebAssembly automatically assigns indices. Imported functions (section "import") 76 // have indices 0 to n. They are followed by native functions (sections "function" and "code") 77 // with indices n+1 and following. 78 // 79 // The following rules describe how wasm handles function indices and addresses: 80 // PC_F = funcValueOffset + WebAssembly function index (not including the imports) 81 // s.Value = PC = PC_F<<16 + PC_B 82 // 83 // The funcValueOffset is necessary to avoid conflicts with expectations 84 // that the Go runtime has about function addresses. 85 // The field "s.Value" corresponds to the concept of PC at runtime. 86 // However, there is no PC register, only PC_F and PC_B. PC_F denotes the function, 87 // PC_B the resume point inside of that function. The entry of the function has PC_B = 0. 88 ldr.SetSymSect(s, sect) 89 ldr.SetSymValue(s, int64(funcValueOffset+va/ld.MINFUNC)<<16) // va starts at zero 90 va += uint64(ld.MINFUNC) 91 return sect, n, va 92 } 93 94 type wasmDataSect struct { 95 sect *sym.Section 96 data []byte 97 } 98 99 var dataSects []wasmDataSect 100 101 func asmb(ctxt *ld.Link, ldr *loader.Loader) { 102 sections := []*sym.Section{ 103 ldr.SymSect(ldr.Lookup("runtime.rodata", 0)), 104 ldr.SymSect(ldr.Lookup("runtime.typelink", 0)), 105 ldr.SymSect(ldr.Lookup("runtime.itablink", 0)), 106 ldr.SymSect(ldr.Lookup("runtime.symtab", 0)), 107 ldr.SymSect(ldr.Lookup("runtime.pclntab", 0)), 108 ldr.SymSect(ldr.Lookup("runtime.noptrdata", 0)), 109 ldr.SymSect(ldr.Lookup("runtime.data", 0)), 110 } 111 112 dataSects = make([]wasmDataSect, len(sections)) 113 for i, sect := range sections { 114 data := ld.DatblkBytes(ctxt, int64(sect.Vaddr), int64(sect.Length)) 115 dataSects[i] = wasmDataSect{sect, data} 116 } 117 } 118 119 // asmb writes the final WebAssembly module binary. 120 // Spec: https://webassembly.github.io/spec/core/binary/modules.html 121 func asmb2(ctxt *ld.Link, ldr *loader.Loader) { 122 types := []*wasmFuncType{ 123 // For normal Go functions, the single parameter is PC_B, 124 // the return value is 125 // 0 if the function returned normally or 126 // 1 if the stack needs to be unwound. 127 {Params: []byte{I32}, Results: []byte{I32}}, 128 } 129 130 // collect host imports (functions that get imported from the WebAssembly host, usually JavaScript) 131 hostImports := []*wasmFunc{ 132 { 133 Name: "debug", 134 Type: lookupType(&wasmFuncType{Params: []byte{I32}}, &types), 135 }, 136 } 137 hostImportMap := make(map[loader.Sym]int64) 138 for _, fn := range ctxt.Textp { 139 relocs := ldr.Relocs(fn) 140 for ri := 0; ri < relocs.Count(); ri++ { 141 r := relocs.At(ri) 142 if r.Type() == objabi.R_WASMIMPORT { 143 hostImportMap[r.Sym()] = int64(len(hostImports)) 144 hostImports = append(hostImports, &wasmFunc{ 145 Name: ldr.SymName(r.Sym()), 146 Type: lookupType(&wasmFuncType{Params: []byte{I32}}, &types), 147 }) 148 } 149 } 150 } 151 152 // collect functions with WebAssembly body 153 var buildid []byte 154 fns := make([]*wasmFunc, len(ctxt.Textp)) 155 for i, fn := range ctxt.Textp { 156 wfn := new(bytes.Buffer) 157 if ldr.SymName(fn) == "go:buildid" { 158 writeUleb128(wfn, 0) // number of sets of locals 159 writeI32Const(wfn, 0) 160 wfn.WriteByte(0x0b) // end 161 buildid = ldr.Data(fn) 162 } else { 163 // Relocations have variable length, handle them here. 164 relocs := ldr.Relocs(fn) 165 P := ldr.Data(fn) 166 off := int32(0) 167 for ri := 0; ri < relocs.Count(); ri++ { 168 r := relocs.At(ri) 169 if r.Siz() == 0 { 170 continue // skip marker relocations 171 } 172 wfn.Write(P[off:r.Off()]) 173 off = r.Off() 174 rs := r.Sym() 175 switch r.Type() { 176 case objabi.R_ADDR: 177 writeSleb128(wfn, ldr.SymValue(rs)+r.Add()) 178 case objabi.R_CALL: 179 writeSleb128(wfn, int64(len(hostImports))+ldr.SymValue(rs)>>16-funcValueOffset) 180 case objabi.R_WASMIMPORT: 181 writeSleb128(wfn, hostImportMap[rs]) 182 default: 183 ldr.Errorf(fn, "bad reloc type %d (%s)", r.Type(), sym.RelocName(ctxt.Arch, r.Type())) 184 continue 185 } 186 } 187 wfn.Write(P[off:]) 188 } 189 190 typ := uint32(0) 191 if sig, ok := wasmFuncTypes[ldr.SymName(fn)]; ok { 192 typ = lookupType(sig, &types) 193 } 194 195 name := nameRegexp.ReplaceAllString(ldr.SymName(fn), "_") 196 fns[i] = &wasmFunc{Name: name, Type: typ, Code: wfn.Bytes()} 197 } 198 199 ctxt.Out.Write([]byte{0x00, 0x61, 0x73, 0x6d}) // magic 200 ctxt.Out.Write([]byte{0x01, 0x00, 0x00, 0x00}) // version 201 202 // Add any buildid early in the binary: 203 if len(buildid) != 0 { 204 writeBuildID(ctxt, buildid) 205 } 206 207 writeTypeSec(ctxt, types) 208 writeImportSec(ctxt, hostImports) 209 writeFunctionSec(ctxt, fns) 210 writeTableSec(ctxt, fns) 211 writeMemorySec(ctxt, ldr) 212 writeGlobalSec(ctxt) 213 writeExportSec(ctxt, ldr, len(hostImports)) 214 writeElementSec(ctxt, uint64(len(hostImports)), uint64(len(fns))) 215 writeCodeSec(ctxt, fns) 216 writeDataSec(ctxt) 217 writeProducerSec(ctxt) 218 if !*ld.FlagS { 219 writeNameSec(ctxt, len(hostImports), fns) 220 } 221 } 222 223 func lookupType(sig *wasmFuncType, types *[]*wasmFuncType) uint32 { 224 for i, t := range *types { 225 if bytes.Equal(sig.Params, t.Params) && bytes.Equal(sig.Results, t.Results) { 226 return uint32(i) 227 } 228 } 229 *types = append(*types, sig) 230 return uint32(len(*types) - 1) 231 } 232 233 func writeSecHeader(ctxt *ld.Link, id uint8) int64 { 234 ctxt.Out.WriteByte(id) 235 sizeOffset := ctxt.Out.Offset() 236 ctxt.Out.Write(make([]byte, 5)) // placeholder for length 237 return sizeOffset 238 } 239 240 func writeSecSize(ctxt *ld.Link, sizeOffset int64) { 241 endOffset := ctxt.Out.Offset() 242 ctxt.Out.SeekSet(sizeOffset) 243 writeUleb128FixedLength(ctxt.Out, uint64(endOffset-sizeOffset-5), 5) 244 ctxt.Out.SeekSet(endOffset) 245 } 246 247 func writeBuildID(ctxt *ld.Link, buildid []byte) { 248 sizeOffset := writeSecHeader(ctxt, sectionCustom) 249 writeName(ctxt.Out, "go:buildid") 250 ctxt.Out.Write(buildid) 251 writeSecSize(ctxt, sizeOffset) 252 } 253 254 // writeTypeSec writes the section that declares all function types 255 // so they can be referenced by index. 256 func writeTypeSec(ctxt *ld.Link, types []*wasmFuncType) { 257 sizeOffset := writeSecHeader(ctxt, sectionType) 258 259 writeUleb128(ctxt.Out, uint64(len(types))) 260 261 for _, t := range types { 262 ctxt.Out.WriteByte(0x60) // functype 263 writeUleb128(ctxt.Out, uint64(len(t.Params))) 264 for _, v := range t.Params { 265 ctxt.Out.WriteByte(byte(v)) 266 } 267 writeUleb128(ctxt.Out, uint64(len(t.Results))) 268 for _, v := range t.Results { 269 ctxt.Out.WriteByte(byte(v)) 270 } 271 } 272 273 writeSecSize(ctxt, sizeOffset) 274 } 275 276 // writeImportSec writes the section that lists the functions that get 277 // imported from the WebAssembly host, usually JavaScript. 278 func writeImportSec(ctxt *ld.Link, hostImports []*wasmFunc) { 279 sizeOffset := writeSecHeader(ctxt, sectionImport) 280 281 writeUleb128(ctxt.Out, uint64(len(hostImports))) // number of imports 282 for _, fn := range hostImports { 283 writeName(ctxt.Out, "go") // provided by the import object in wasm_exec.js 284 writeName(ctxt.Out, fn.Name) 285 ctxt.Out.WriteByte(0x00) // func import 286 writeUleb128(ctxt.Out, uint64(fn.Type)) 287 } 288 289 writeSecSize(ctxt, sizeOffset) 290 } 291 292 // writeFunctionSec writes the section that declares the types of functions. 293 // The bodies of these functions will later be provided in the "code" section. 294 func writeFunctionSec(ctxt *ld.Link, fns []*wasmFunc) { 295 sizeOffset := writeSecHeader(ctxt, sectionFunction) 296 297 writeUleb128(ctxt.Out, uint64(len(fns))) 298 for _, fn := range fns { 299 writeUleb128(ctxt.Out, uint64(fn.Type)) 300 } 301 302 writeSecSize(ctxt, sizeOffset) 303 } 304 305 // writeTableSec writes the section that declares tables. Currently there is only a single table 306 // that is used by the CallIndirect operation to dynamically call any function. 307 // The contents of the table get initialized by the "element" section. 308 func writeTableSec(ctxt *ld.Link, fns []*wasmFunc) { 309 sizeOffset := writeSecHeader(ctxt, sectionTable) 310 311 numElements := uint64(funcValueOffset + len(fns)) 312 writeUleb128(ctxt.Out, 1) // number of tables 313 ctxt.Out.WriteByte(0x70) // type: anyfunc 314 ctxt.Out.WriteByte(0x00) // no max 315 writeUleb128(ctxt.Out, numElements) // min 316 317 writeSecSize(ctxt, sizeOffset) 318 } 319 320 // writeMemorySec writes the section that declares linear memories. Currently one linear memory is being used. 321 // Linear memory always starts at address zero. More memory can be requested with the GrowMemory instruction. 322 func writeMemorySec(ctxt *ld.Link, ldr *loader.Loader) { 323 sizeOffset := writeSecHeader(ctxt, sectionMemory) 324 325 dataSection := ldr.SymSect(ldr.Lookup("runtime.data", 0)) 326 dataEnd := dataSection.Vaddr + dataSection.Length 327 var initialSize = dataEnd + 16<<20 // 16MB, enough for runtime init without growing 328 329 const wasmPageSize = 64 << 10 // 64KB 330 331 writeUleb128(ctxt.Out, 1) // number of memories 332 ctxt.Out.WriteByte(0x00) // no maximum memory size 333 writeUleb128(ctxt.Out, initialSize/wasmPageSize) // minimum (initial) memory size 334 335 writeSecSize(ctxt, sizeOffset) 336 } 337 338 // writeGlobalSec writes the section that declares global variables. 339 func writeGlobalSec(ctxt *ld.Link) { 340 sizeOffset := writeSecHeader(ctxt, sectionGlobal) 341 342 globalRegs := []byte{ 343 I32, // 0: SP 344 I64, // 1: CTXT 345 I64, // 2: g 346 I64, // 3: RET0 347 I64, // 4: RET1 348 I64, // 5: RET2 349 I64, // 6: RET3 350 I32, // 7: PAUSE 351 } 352 353 writeUleb128(ctxt.Out, uint64(len(globalRegs))) // number of globals 354 355 for _, typ := range globalRegs { 356 ctxt.Out.WriteByte(typ) 357 ctxt.Out.WriteByte(0x01) // var 358 switch typ { 359 case I32: 360 writeI32Const(ctxt.Out, 0) 361 case I64: 362 writeI64Const(ctxt.Out, 0) 363 } 364 ctxt.Out.WriteByte(0x0b) // end 365 } 366 367 writeSecSize(ctxt, sizeOffset) 368 } 369 370 // writeExportSec writes the section that declares exports. 371 // Exports can be accessed by the WebAssembly host, usually JavaScript. 372 // The wasm_export_* functions and the linear memory get exported. 373 func writeExportSec(ctxt *ld.Link, ldr *loader.Loader, lenHostImports int) { 374 sizeOffset := writeSecHeader(ctxt, sectionExport) 375 376 writeUleb128(ctxt.Out, 4) // number of exports 377 378 for _, name := range []string{"run", "resume", "getsp"} { 379 s := ldr.Lookup("wasm_export_"+name, 0) 380 idx := uint32(lenHostImports) + uint32(ldr.SymValue(s)>>16) - funcValueOffset 381 writeName(ctxt.Out, name) // inst.exports.run/resume/getsp in wasm_exec.js 382 ctxt.Out.WriteByte(0x00) // func export 383 writeUleb128(ctxt.Out, uint64(idx)) // funcidx 384 } 385 386 writeName(ctxt.Out, "mem") // inst.exports.mem in wasm_exec.js 387 ctxt.Out.WriteByte(0x02) // mem export 388 writeUleb128(ctxt.Out, 0) // memidx 389 390 writeSecSize(ctxt, sizeOffset) 391 } 392 393 // writeElementSec writes the section that initializes the tables declared by the "table" section. 394 // The table for CallIndirect gets initialized in a very simple way so that each table index (PC_F value) 395 // maps linearly to the function index (numImports + PC_F). 396 func writeElementSec(ctxt *ld.Link, numImports, numFns uint64) { 397 sizeOffset := writeSecHeader(ctxt, sectionElement) 398 399 writeUleb128(ctxt.Out, 1) // number of element segments 400 401 writeUleb128(ctxt.Out, 0) // tableidx 402 writeI32Const(ctxt.Out, funcValueOffset) 403 ctxt.Out.WriteByte(0x0b) // end 404 405 writeUleb128(ctxt.Out, numFns) // number of entries 406 for i := uint64(0); i < numFns; i++ { 407 writeUleb128(ctxt.Out, numImports+i) 408 } 409 410 writeSecSize(ctxt, sizeOffset) 411 } 412 413 // writeCodeSec writes the section that provides the function bodies for the functions 414 // declared by the "func" section. 415 func writeCodeSec(ctxt *ld.Link, fns []*wasmFunc) { 416 sizeOffset := writeSecHeader(ctxt, sectionCode) 417 418 writeUleb128(ctxt.Out, uint64(len(fns))) // number of code entries 419 for _, fn := range fns { 420 writeUleb128(ctxt.Out, uint64(len(fn.Code))) 421 ctxt.Out.Write(fn.Code) 422 } 423 424 writeSecSize(ctxt, sizeOffset) 425 } 426 427 // writeDataSec writes the section that provides data that will be used to initialize the linear memory. 428 func writeDataSec(ctxt *ld.Link) { 429 sizeOffset := writeSecHeader(ctxt, sectionData) 430 431 type dataSegment struct { 432 offset int32 433 data []byte 434 } 435 436 // Omit blocks of zeroes and instead emit data segments with offsets skipping the zeroes. 437 // This reduces the size of the WebAssembly binary. We use 8 bytes as an estimate for the 438 // overhead of adding a new segment (same as wasm-opt's memory-packing optimization uses). 439 const segmentOverhead = 8 440 441 // Generate at most this many segments. A higher number of segments gets rejected by some WebAssembly runtimes. 442 const maxNumSegments = 100000 443 444 var segments []*dataSegment 445 for secIndex, ds := range dataSects { 446 data := ds.data 447 offset := int32(ds.sect.Vaddr) 448 449 // skip leading zeroes 450 for len(data) > 0 && data[0] == 0 { 451 data = data[1:] 452 offset++ 453 } 454 455 for len(data) > 0 { 456 dataLen := int32(len(data)) 457 var segmentEnd, zeroEnd int32 458 if len(segments)+(len(dataSects)-secIndex) == maxNumSegments { 459 segmentEnd = dataLen 460 zeroEnd = dataLen 461 } else { 462 for { 463 // look for beginning of zeroes 464 for segmentEnd < dataLen && data[segmentEnd] != 0 { 465 segmentEnd++ 466 } 467 // look for end of zeroes 468 zeroEnd = segmentEnd 469 for zeroEnd < dataLen && data[zeroEnd] == 0 { 470 zeroEnd++ 471 } 472 // emit segment if omitting zeroes reduces the output size 473 if zeroEnd-segmentEnd >= segmentOverhead || zeroEnd == dataLen { 474 break 475 } 476 segmentEnd = zeroEnd 477 } 478 } 479 480 segments = append(segments, &dataSegment{ 481 offset: offset, 482 data: data[:segmentEnd], 483 }) 484 data = data[zeroEnd:] 485 offset += zeroEnd 486 } 487 } 488 489 writeUleb128(ctxt.Out, uint64(len(segments))) // number of data entries 490 for _, seg := range segments { 491 writeUleb128(ctxt.Out, 0) // memidx 492 writeI32Const(ctxt.Out, seg.offset) 493 ctxt.Out.WriteByte(0x0b) // end 494 writeUleb128(ctxt.Out, uint64(len(seg.data))) 495 ctxt.Out.Write(seg.data) 496 } 497 498 writeSecSize(ctxt, sizeOffset) 499 } 500 501 // writeProducerSec writes an optional section that reports the source language and compiler version. 502 func writeProducerSec(ctxt *ld.Link) { 503 sizeOffset := writeSecHeader(ctxt, sectionCustom) 504 writeName(ctxt.Out, "producers") 505 506 writeUleb128(ctxt.Out, 2) // number of fields 507 508 writeName(ctxt.Out, "language") // field name 509 writeUleb128(ctxt.Out, 1) // number of values 510 writeName(ctxt.Out, "Go") // value: name 511 writeName(ctxt.Out, buildcfg.Version) // value: version 512 513 writeName(ctxt.Out, "processed-by") // field name 514 writeUleb128(ctxt.Out, 1) // number of values 515 writeName(ctxt.Out, "Go cmd/compile") // value: name 516 writeName(ctxt.Out, buildcfg.Version) // value: version 517 518 writeSecSize(ctxt, sizeOffset) 519 } 520 521 var nameRegexp = regexp.MustCompile(`[^\w.]`) 522 523 // writeNameSec writes an optional section that assigns names to the functions declared by the "func" section. 524 // The names are only used by WebAssembly stack traces, debuggers and decompilers. 525 // TODO(neelance): add symbol table of DATA symbols 526 func writeNameSec(ctxt *ld.Link, firstFnIndex int, fns []*wasmFunc) { 527 sizeOffset := writeSecHeader(ctxt, sectionCustom) 528 writeName(ctxt.Out, "name") 529 530 sizeOffset2 := writeSecHeader(ctxt, 0x01) // function names 531 writeUleb128(ctxt.Out, uint64(len(fns))) 532 for i, fn := range fns { 533 writeUleb128(ctxt.Out, uint64(firstFnIndex+i)) 534 writeName(ctxt.Out, fn.Name) 535 } 536 writeSecSize(ctxt, sizeOffset2) 537 538 writeSecSize(ctxt, sizeOffset) 539 } 540 541 type nameWriter interface { 542 io.ByteWriter 543 io.Writer 544 } 545 546 func writeI32Const(w io.ByteWriter, v int32) { 547 w.WriteByte(0x41) // i32.const 548 writeSleb128(w, int64(v)) 549 } 550 551 func writeI64Const(w io.ByteWriter, v int64) { 552 w.WriteByte(0x42) // i64.const 553 writeSleb128(w, v) 554 } 555 556 func writeName(w nameWriter, name string) { 557 writeUleb128(w, uint64(len(name))) 558 w.Write([]byte(name)) 559 } 560 561 func writeUleb128(w io.ByteWriter, v uint64) { 562 if v < 128 { 563 w.WriteByte(uint8(v)) 564 return 565 } 566 more := true 567 for more { 568 c := uint8(v & 0x7f) 569 v >>= 7 570 more = v != 0 571 if more { 572 c |= 0x80 573 } 574 w.WriteByte(c) 575 } 576 } 577 578 func writeUleb128FixedLength(w io.ByteWriter, v uint64, length int) { 579 for i := 0; i < length; i++ { 580 c := uint8(v & 0x7f) 581 v >>= 7 582 if i < length-1 { 583 c |= 0x80 584 } 585 w.WriteByte(c) 586 } 587 if v != 0 { 588 panic("writeUleb128FixedLength: length too small") 589 } 590 } 591 592 func writeSleb128(w io.ByteWriter, v int64) { 593 more := true 594 for more { 595 c := uint8(v & 0x7f) 596 s := uint8(v & 0x40) 597 v >>= 7 598 more = !((v == 0 && s == 0) || (v == -1 && s != 0)) 599 if more { 600 c |= 0x80 601 } 602 w.WriteByte(c) 603 } 604 }