]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/link, runtime: on Wasm, put only function index in method table and func table
authorCherry Mui <cherryyz@google.com>
Tue, 26 Dec 2023 20:35:56 +0000 (15:35 -0500)
committerCherry Mui <cherryyz@google.com>
Wed, 16 Jul 2025 14:15:42 +0000 (07:15 -0700)
In the type descriptor's method table, it contains relative PCs of
the methods (relative to the start of the text section) stored as
32-bit offsets. On Wasm, a PC is PC_F<<16 + PC_B, where PC_F is
the function index, and PC_B is the block index. When there are
more than 65536 functions, the PC will not fit into 32-bit (and
relative to the section start doesn't help). Since there are no
more bits for the function index, and the method table always
targets the entry of a method, we put just the PC_F there, and
rewrite back to a full PC at run time when we need the PC. This
way we can have more than 65536 functions.

The func table also contains 32-bit relative PCs, and it also
always points to function entries. Do the same there, as well
as other places where we use relative text offsets.

Also add the relocation type in the relocation overflow error
message.

Also add check for function too big on Wasm. If a function has
more than 65536 blocks, PC_B will overflow and PC = PC_F<<16 + PC_B
will points to the wrong function.

Fixes #64856.

Change-Id: If9c307e9fb1641f367a5f19c39f88f455805d0bb
Reviewed-on: https://go-review.googlesource.com/c/go/+/552835
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/cmd/internal/obj/wasm/wasmobj.go
src/cmd/link/internal/ld/data.go
src/cmd/link/internal/ld/pcln.go
src/runtime/symtab.go

index e8bf927a82ec656684e128f3583777f5bc534e37..3f9243613efc19808eeb1e29bb682a0564afa802 100644 (file)
@@ -372,6 +372,9 @@ func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
        }
        tableIdxs = append(tableIdxs, uint64(numResumePoints))
        s.Size = pc + 1
+       if pc >= 1<<16 {
+               ctxt.Diag("function too big: %s exceeds 65536 blocks", s)
+       }
 
        if needMoreStack {
                p := pMorestack
index 42756e86bbf0ea32e0ba6325223c85d3032c5e36..138547a3d3f424e32a5125b8b5016f852dc1f989 100644 (file)
@@ -496,6 +496,15 @@ func (st *relocSymState) relocsym(s loader.Sym, P []byte) {
                        // to the start of the first text section, even if there are multiple.
                        if sect.Name == ".text" {
                                o = ldr.SymValue(rs) - int64(Segtext.Sections[0].Vaddr) + r.Add()
+                               if target.IsWasm() {
+                                       // On Wasm, textoff (e.g. in the method table) is just the function index,
+                                       // whereas the "PC" (rs's Value) is function index << 16 + block index (see
+                                       // ../wasm/asm.go:assignAddress).
+                                       if o&(1<<16-1) != 0 {
+                                               st.err.Errorf(s, "textoff relocation %s does not target function entry: %s %#x", rt, ldr.SymName(rs), o)
+                                       }
+                                       o >>= 16
+                               }
                        } else {
                                o = ldr.SymValue(rs) - int64(ldr.SymSect(rs).Vaddr) + r.Add()
                        }
@@ -606,16 +615,16 @@ func (st *relocSymState) relocsym(s loader.Sym, P []byte) {
                        P[off] = byte(int8(o))
                case 2:
                        if (rt == objabi.R_PCREL || rt == objabi.R_CALL) && o != int64(int16(o)) {
-                               st.err.Errorf(s, "pc-relative relocation address for %s is too big: %#x", ldr.SymName(rs), o)
+                               st.err.Errorf(s, "pc-relative relocation %s address for %s is too big: %#x", rt, ldr.SymName(rs), o)
                        } else if o != int64(int16(o)) && o != int64(uint16(o)) {
-                               st.err.Errorf(s, "non-pc-relative relocation address for %s is too big: %#x", ldr.SymName(rs), uint64(o))
+                               st.err.Errorf(s, "non-pc-relative relocation %s address for %s is too big: %#x", rt, ldr.SymName(rs), uint64(o))
                        }
                        target.Arch.ByteOrder.PutUint16(P[off:], uint16(o))
                case 4:
                        if (rt == objabi.R_PCREL || rt == objabi.R_CALL) && o != int64(int32(o)) {
-                               st.err.Errorf(s, "pc-relative relocation address for %s is too big: %#x", ldr.SymName(rs), o)
+                               st.err.Errorf(s, "pc-relative relocation %s address for %s is too big: %#x", rt, ldr.SymName(rs), o)
                        } else if o != int64(int32(o)) && o != int64(uint32(o)) {
-                               st.err.Errorf(s, "non-pc-relative relocation address for %s is too big: %#x", ldr.SymName(rs), uint64(o))
+                               st.err.Errorf(s, "non-pc-relative relocation %s address for %s is too big: %#x", rt, ldr.SymName(rs), uint64(o))
                        }
                        target.Arch.ByteOrder.PutUint32(P[off:], uint32(o))
                case 8:
index a09d3acd5e265b6780a7bed42d5ca39fa4fa971e..9532b33a9bc1ccd1434636c9573b0cc25f9ad0b1 100644 (file)
@@ -614,16 +614,36 @@ func (state pclntab) calculateFunctabSize(ctxt *Link, funcs []loader.Sym) (int64
        return size, startLocations
 }
 
+// textOff computes the offset of a text symbol, relative to textStart,
+// similar to an R_ADDROFF relocation,  for various runtime metadata and
+// tables (see runtime/symtab.go:(*moduledata).textAddr).
+func textOff(ctxt *Link, s loader.Sym, textStart int64) uint32 {
+       ldr := ctxt.loader
+       off := ldr.SymValue(s) - textStart
+       if off < 0 {
+               panic(fmt.Sprintf("expected func %s(%x) to be placed at or after textStart (%x)", ldr.SymName(s), ldr.SymValue(s), textStart))
+       }
+       if ctxt.IsWasm() {
+               // On Wasm, the function table contains just the function index, whereas
+               // the "PC" (s's Value) is function index << 16 + block index (see
+               // ../wasm/asm.go:assignAddress).
+               if off&(1<<16-1) != 0 {
+                       ctxt.Errorf(s, "nonzero PC_B at function entry: %#x", off)
+               }
+               off >>= 16
+       }
+       if int64(uint32(off)) != off {
+               ctxt.Errorf(s, "textOff overflow: %#x", off)
+       }
+       return uint32(off)
+}
+
 // writePCToFunc writes the PC->func lookup table.
 func writePCToFunc(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, startLocations []uint32) {
        ldr := ctxt.loader
        textStart := ldr.SymValue(ldr.Lookup("runtime.text", 0))
        pcOff := func(s loader.Sym) uint32 {
-               off := ldr.SymValue(s) - textStart
-               if off < 0 {
-                       panic(fmt.Sprintf("expected func %s(%x) to be placed at or after textStart (%x)", ldr.SymName(s), ldr.SymValue(s), textStart))
-               }
-               return uint32(off)
+               return textOff(ctxt, s, textStart)
        }
        for i, s := range funcs {
                sb.SetUint32(ctxt.Arch, int64(i*2*4), pcOff(s))
@@ -632,7 +652,11 @@ func writePCToFunc(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, sta
 
        // Final entry of table is just end pc offset.
        lastFunc := funcs[len(funcs)-1]
-       sb.SetUint32(ctxt.Arch, int64(len(funcs))*2*4, pcOff(lastFunc)+uint32(ldr.SymSize(lastFunc)))
+       lastPC := pcOff(lastFunc) + uint32(ldr.SymSize(lastFunc))
+       if ctxt.IsWasm() {
+               lastPC = pcOff(lastFunc) + 1 // On Wasm it is function index (see above)
+       }
+       sb.SetUint32(ctxt.Arch, int64(len(funcs))*2*4, lastPC)
 }
 
 // writeFuncs writes the func structures and pcdata to runtime.functab.
@@ -646,7 +670,7 @@ func writeFuncs(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSym
        var pcsp, pcfile, pcline, pcinline loader.Sym
        var pcdata []loader.Sym
 
-       // Write the individual func objects.
+       // Write the individual func objects (runtime._func struct).
        for i, s := range funcs {
                startLine := int32(0)
                fi := ldr.FuncInfo(s)
@@ -658,10 +682,7 @@ func writeFuncs(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSym
 
                off := int64(startLocations[i])
                // entryOff uint32 (offset of func entry PC from textStart)
-               entryOff := ldr.SymValue(s) - textStart
-               if entryOff < 0 {
-                       panic(fmt.Sprintf("expected func %s(%x) to be placed before or at textStart (%x)", ldr.SymName(s), ldr.SymValue(s), textStart))
-               }
+               entryOff := textOff(ctxt, s, textStart)
                off = sb.SetUint32(ctxt.Arch, off, uint32(entryOff))
 
                // nameOff int32
index 866c46a83d7fdf43657f8f6194c21d17b839d486..56f2a00d76ef862e0237e2b4144c4d5716cb395d 100644 (file)
@@ -647,8 +647,15 @@ func moduledataverify1(datap *moduledata) {
 
        min := datap.textAddr(datap.ftab[0].entryoff)
        max := datap.textAddr(datap.ftab[nftab].entryoff)
-       if datap.minpc != min || datap.maxpc != max {
-               println("minpc=", hex(datap.minpc), "min=", hex(min), "maxpc=", hex(datap.maxpc), "max=", hex(max))
+       minpc := datap.minpc
+       maxpc := datap.maxpc
+       if GOARCH == "wasm" {
+               // On Wasm, the func table contains the function index, whereas
+               // the "PC" is function index << 16 + block index.
+               maxpc = alignUp(maxpc, 1<<16) // round up for end PC
+       }
+       if minpc != min || maxpc != max {
+               println("minpc=", hex(minpc), "min=", hex(min), "maxpc=", hex(maxpc), "max=", hex(max))
                throw("minpc or maxpc invalid")
        }
 
@@ -694,6 +701,11 @@ func (md *moduledata) textAddr(off32 uint32) uintptr {
                        throw("runtime: text offset out of range")
                }
        }
+       if GOARCH == "wasm" {
+               // On Wasm, a text offset (e.g. in the method table) is function index, whereas
+               // the "PC" is function index << 16 + block index.
+               res <<= 16
+       }
        return res
 }
 
@@ -704,8 +716,17 @@ func (md *moduledata) textAddr(off32 uint32) uintptr {
 //
 //go:nosplit
 func (md *moduledata) textOff(pc uintptr) (uint32, bool) {
-       res := uint32(pc - md.text)
+       off := pc - md.text
+       if GOARCH == "wasm" {
+               // On Wasm, the func table contains the function index, whereas
+               // the "PC" is function index << 16 + block index.
+               off >>= 16
+       }
+       res := uint32(off)
        if len(md.textsectmap) > 1 {
+               if GOARCH == "wasm" {
+                       fatal("unexpected multiple text sections on Wasm")
+               }
                for i, sect := range md.textsectmap {
                        if sect.baseaddr > pc {
                                // pc is not in any section.
@@ -904,6 +925,11 @@ func findfunc(pc uintptr) funcInfo {
        }
 
        x := uintptr(pcOff) + datap.text - datap.minpc // TODO: are datap.text and datap.minpc always equal?
+       if GOARCH == "wasm" {
+               // On Wasm, pcOff is the function index, whereas
+               // the "PC" is function index << 16 + block index.
+               x = uintptr(pcOff)<<16 + datap.text - datap.minpc
+       }
        b := x / abi.FuncTabBucketSize
        i := x % abi.FuncTabBucketSize / (abi.FuncTabBucketSize / nsub)