Commit fae4a128 authored by Michael Hudson-Doyle's avatar Michael Hudson-Doyle Committed by Ian Lance Taylor

runtime, reflect: support multiple moduledata objects

This changes all the places that consult themoduledata to consult a
linked list of moduledata objects, as will be necessary for
-linkshared to work.

Obviously, as there is as yet no way of adding moduledata objects to
this list, all this change achieves right now is wasting a few
instructions here and there.

Change-Id: I397af7f60d0849b76aaccedf72238fe664867051
Reviewed-on: https://go-review.googlesource.com/8231Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent cb6e9ec0
...@@ -410,6 +410,7 @@ func symtab() { ...@@ -410,6 +410,7 @@ func symtab() {
// This code uses several global variables that are set by pcln.go:pclntab. // This code uses several global variables that are set by pcln.go:pclntab.
moduledata := Linklookup(Ctxt, "runtime.themoduledata", 0) moduledata := Linklookup(Ctxt, "runtime.themoduledata", 0)
moduledata.Type = SNOPTRDATA moduledata.Type = SNOPTRDATA
moduledatasize := moduledata.Size
moduledata.Size = 0 // truncate symbol back to 0 bytes to reinitialize moduledata.Size = 0 // truncate symbol back to 0 bytes to reinitialize
moduledata.Reachable = true moduledata.Reachable = true
moduledata.Local = true moduledata.Local = true
...@@ -448,4 +449,7 @@ func symtab() { ...@@ -448,4 +449,7 @@ func symtab() {
Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.typelink", 0)) Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.typelink", 0))
adduint(Ctxt, moduledata, uint64(ntypelinks)) adduint(Ctxt, moduledata, uint64(ntypelinks))
adduint(Ctxt, moduledata, uint64(ntypelinks)) adduint(Ctxt, moduledata, uint64(ntypelinks))
// The rest of moduledata is zero initialized.
moduledata.Size = moduledatasize
Symgrow(Ctxt, moduledata, moduledatasize)
} }
...@@ -1301,15 +1301,17 @@ func haveIdenticalUnderlyingType(T, V *rtype) bool { ...@@ -1301,15 +1301,17 @@ func haveIdenticalUnderlyingType(T, V *rtype) bool {
// there can be more than one with a given string. // there can be more than one with a given string.
// Only types we might want to look up are included: // Only types we might want to look up are included:
// channels, maps, slices, and arrays. // channels, maps, slices, and arrays.
func typelinks() []*rtype func typelinks() [][]*rtype
// typesByString returns the subslice of typelinks() whose elements have // typesByString returns the subslice of typelinks() whose elements have
// the given string representation. // the given string representation.
// It may be empty (no known types with that string) or may have // It may be empty (no known types with that string) or may have
// multiple elements (multiple types with that string). // multiple elements (multiple types with that string).
func typesByString(s string) []*rtype { func typesByString(s string) []*rtype {
typ := typelinks() typs := typelinks()
var ret []*rtype
for _, typ := range typs {
// We are looking for the first index i where the string becomes >= s. // We are looking for the first index i where the string becomes >= s.
// This is a copy of sort.Search, with f(h) replaced by (*typ[h].string >= s). // This is a copy of sort.Search, with f(h) replaced by (*typ[h].string >= s).
i, j := 0, len(typ) i, j := 0, len(typ)
...@@ -1332,8 +1334,15 @@ func typesByString(s string) []*rtype { ...@@ -1332,8 +1334,15 @@ func typesByString(s string) []*rtype {
j++ j++
} }
// This slice will be empty if the string is not found. if j > i {
return typ[i:j] if ret == nil {
ret = typ[i:j:j]
} else {
ret = append(ret, typ[i:j]...)
}
}
}
return ret
} }
// The lookupCache caches ChanOf, MapOf, and SliceOf lookups. // The lookupCache caches ChanOf, MapOf, and SliceOf lookups.
......
...@@ -431,19 +431,20 @@ func finq_callback(fn *funcval, obj unsafe.Pointer, nret uintptr, fint *_type, o ...@@ -431,19 +431,20 @@ func finq_callback(fn *funcval, obj unsafe.Pointer, nret uintptr, fint *_type, o
} }
func dumproots() { func dumproots() {
// TODO(mwhudson): dump datamask etc from all objects
// data segment // data segment
dumpbvtypes(&gcdatamask, unsafe.Pointer(themoduledata.data)) dumpbvtypes(&themoduledata.gcdatamask, unsafe.Pointer(themoduledata.data))
dumpint(tagData) dumpint(tagData)
dumpint(uint64(themoduledata.data)) dumpint(uint64(themoduledata.data))
dumpmemrange(unsafe.Pointer(themoduledata.data), themoduledata.edata-themoduledata.data) dumpmemrange(unsafe.Pointer(themoduledata.data), themoduledata.edata-themoduledata.data)
dumpfields(gcdatamask) dumpfields(themoduledata.gcdatamask)
// bss segment // bss segment
dumpbvtypes(&gcbssmask, unsafe.Pointer(themoduledata.bss)) dumpbvtypes(&themoduledata.gcbssmask, unsafe.Pointer(themoduledata.bss))
dumpint(tagBSS) dumpint(tagBSS)
dumpint(uint64(themoduledata.bss)) dumpint(uint64(themoduledata.bss))
dumpmemrange(unsafe.Pointer(themoduledata.bss), themoduledata.ebss-themoduledata.bss) dumpmemrange(unsafe.Pointer(themoduledata.bss), themoduledata.ebss-themoduledata.bss)
dumpfields(gcbssmask) dumpfields(themoduledata.gcbssmask)
// MSpan.types // MSpan.types
allspans := h_allspans allspans := h_allspans
......
...@@ -426,44 +426,47 @@ func wbshadowinit() { ...@@ -426,44 +426,47 @@ func wbshadowinit() {
memmove(p1, unsafe.Pointer(mheap_.arena_start), mheap_.arena_used-mheap_.arena_start) memmove(p1, unsafe.Pointer(mheap_.arena_start), mheap_.arena_used-mheap_.arena_start)
mheap_.shadow_reserved = reserved mheap_.shadow_reserved = reserved
for datap := &themoduledata; datap != nil; datap = datap.next {
start := ^uintptr(0) start := ^uintptr(0)
end := uintptr(0) end := uintptr(0)
if start > themoduledata.noptrdata { if start > datap.noptrdata {
start = themoduledata.noptrdata start = datap.noptrdata
} }
if start > themoduledata.data { if start > datap.data {
start = themoduledata.data start = datap.data
} }
if start > themoduledata.noptrbss { if start > datap.noptrbss {
start = themoduledata.noptrbss start = datap.noptrbss
} }
if start > themoduledata.bss { if start > datap.bss {
start = themoduledata.bss start = datap.bss
} }
if end < themoduledata.enoptrdata { if end < datap.enoptrdata {
end = themoduledata.enoptrdata end = datap.enoptrdata
} }
if end < themoduledata.edata { if end < datap.edata {
end = themoduledata.edata end = datap.edata
} }
if end < themoduledata.enoptrbss { if end < datap.enoptrbss {
end = themoduledata.enoptrbss end = datap.enoptrbss
} }
if end < themoduledata.ebss { if end < datap.ebss {
end = themoduledata.ebss end = datap.ebss
} }
start &^= _PhysPageSize - 1 start &^= _PhysPageSize - 1
end = round(end, _PhysPageSize) end = round(end, _PhysPageSize)
mheap_.data_start = start datap.data_start = start
mheap_.data_end = end datap.data_end = end
reserved = false reserved = false
p1 = sysReserveHigh(end-start, &reserved) p1 = sysReserveHigh(end-start, &reserved)
if p1 == nil { if p1 == nil {
throw("cannot map shadow data") throw("cannot map shadow data")
} }
mheap_.shadow_data = uintptr(p1) - start datap.shadow_data = uintptr(p1) - start
sysMap(p1, end-start, reserved, &memstats.other_sys) sysMap(p1, end-start, reserved, &memstats.other_sys)
memmove(p1, unsafe.Pointer(start), end-start) memmove(p1, unsafe.Pointer(start), end-start)
}
mheap_.shadow_enabled = true mheap_.shadow_enabled = true
} }
...@@ -471,13 +474,15 @@ func wbshadowinit() { ...@@ -471,13 +474,15 @@ func wbshadowinit() {
// shadowptr returns a pointer to the shadow value for addr. // shadowptr returns a pointer to the shadow value for addr.
//go:nosplit //go:nosplit
func shadowptr(addr uintptr) *uintptr { func shadowptr(addr uintptr) *uintptr {
var shadow *uintptr for datap := &themoduledata; datap != nil; datap = datap.next {
if mheap_.data_start <= addr && addr < mheap_.data_end { if datap.data_start <= addr && addr < datap.data_end {
shadow = (*uintptr)(unsafe.Pointer(addr + mheap_.shadow_data)) return (*uintptr)(unsafe.Pointer(addr + datap.shadow_data))
} else if inheap(addr) { }
shadow = (*uintptr)(unsafe.Pointer(addr + mheap_.shadow_heap)) }
if inheap(addr) {
return (*uintptr)(unsafe.Pointer(addr + mheap_.shadow_heap))
} }
return shadow return nil
} }
// istrackedptr reports whether the pointer value p requires a write barrier // istrackedptr reports whether the pointer value p requires a write barrier
......
...@@ -747,30 +747,32 @@ func getgcmask(p unsafe.Pointer, t *_type, mask **byte, len *uintptr) { ...@@ -747,30 +747,32 @@ func getgcmask(p unsafe.Pointer, t *_type, mask **byte, len *uintptr) {
const typeBitsPerByte = 8 / typeBitsWidth const typeBitsPerByte = 8 / typeBitsWidth
// data // data
if themoduledata.data <= uintptr(p) && uintptr(p) < themoduledata.edata { for datap := &themoduledata; datap != nil; datap = datap.next {
if datap.data <= uintptr(p) && uintptr(p) < datap.edata {
n := (*ptrtype)(unsafe.Pointer(t)).elem.size n := (*ptrtype)(unsafe.Pointer(t)).elem.size
*len = n / ptrSize *len = n / ptrSize
*mask = &make([]byte, *len)[0] *mask = &make([]byte, *len)[0]
for i := uintptr(0); i < n; i += ptrSize { for i := uintptr(0); i < n; i += ptrSize {
off := (uintptr(p) + i - themoduledata.data) / ptrSize off := (uintptr(p) + i - datap.data) / ptrSize
bits := (*(*byte)(add(unsafe.Pointer(gcdatamask.bytedata), off/typeBitsPerByte)) >> ((off % typeBitsPerByte) * typeBitsWidth)) & typeMask bits := (*(*byte)(add(unsafe.Pointer(datap.gcdatamask.bytedata), off/typeBitsPerByte)) >> ((off % typeBitsPerByte) * typeBitsWidth)) & typeMask
*(*byte)(add(unsafe.Pointer(*mask), i/ptrSize)) = bits *(*byte)(add(unsafe.Pointer(*mask), i/ptrSize)) = bits
} }
return return
} }
// bss // bss
if themoduledata.bss <= uintptr(p) && uintptr(p) < themoduledata.ebss { if datap.bss <= uintptr(p) && uintptr(p) < datap.ebss {
n := (*ptrtype)(unsafe.Pointer(t)).elem.size n := (*ptrtype)(unsafe.Pointer(t)).elem.size
*len = n / ptrSize *len = n / ptrSize
*mask = &make([]byte, *len)[0] *mask = &make([]byte, *len)[0]
for i := uintptr(0); i < n; i += ptrSize { for i := uintptr(0); i < n; i += ptrSize {
off := (uintptr(p) + i - themoduledata.bss) / ptrSize off := (uintptr(p) + i - datap.bss) / ptrSize
bits := (*(*byte)(add(unsafe.Pointer(gcbssmask.bytedata), off/typeBitsPerByte)) >> ((off % typeBitsPerByte) * typeBitsWidth)) & typeMask bits := (*(*byte)(add(unsafe.Pointer(datap.gcbssmask.bytedata), off/typeBitsPerByte)) >> ((off % typeBitsPerByte) * typeBitsWidth)) & typeMask
*(*byte)(add(unsafe.Pointer(*mask), i/ptrSize)) = bits *(*byte)(add(unsafe.Pointer(*mask), i/ptrSize)) = bits
} }
return return
} }
}
// heap // heap
var n uintptr var n uintptr
......
...@@ -289,12 +289,14 @@ func SetFinalizer(obj interface{}, finalizer interface{}) { ...@@ -289,12 +289,14 @@ func SetFinalizer(obj interface{}, finalizer interface{}) {
// The relevant segments are: noptrdata, data, bss, noptrbss. // The relevant segments are: noptrdata, data, bss, noptrbss.
// We cannot assume they are in any order or even contiguous, // We cannot assume they are in any order or even contiguous,
// due to external linking. // due to external linking.
if themoduledata.noptrdata <= uintptr(e.data) && uintptr(e.data) < themoduledata.enoptrdata || for datap := &themoduledata; datap != nil; datap = datap.next {
themoduledata.data <= uintptr(e.data) && uintptr(e.data) < themoduledata.edata || if datap.noptrdata <= uintptr(e.data) && uintptr(e.data) < datap.enoptrdata ||
themoduledata.bss <= uintptr(e.data) && uintptr(e.data) < themoduledata.ebss || datap.data <= uintptr(e.data) && uintptr(e.data) < datap.edata ||
themoduledata.noptrbss <= uintptr(e.data) && uintptr(e.data) < themoduledata.enoptrbss { datap.bss <= uintptr(e.data) && uintptr(e.data) < datap.ebss ||
datap.noptrbss <= uintptr(e.data) && uintptr(e.data) < datap.enoptrbss {
return return
} }
}
throw("runtime.SetFinalizer: pointer not in allocated block") throw("runtime.SetFinalizer: pointer not in allocated block")
} }
......
...@@ -136,9 +136,6 @@ func have_cgo_allocate() bool { ...@@ -136,9 +136,6 @@ func have_cgo_allocate() bool {
return &weak_cgo_allocate != nil return &weak_cgo_allocate != nil
} }
var gcdatamask bitvector
var gcbssmask bitvector
// heapminimum is the minimum number of bytes in the heap. // heapminimum is the minimum number of bytes in the heap.
// This cleans up the corner case of where we have a very small live set but a lot // This cleans up the corner case of where we have a very small live set but a lot
// of allocations and collecting every GOGC * live set is expensive. // of allocations and collecting every GOGC * live set is expensive.
...@@ -154,8 +151,10 @@ func gcinit() { ...@@ -154,8 +151,10 @@ func gcinit() {
work.markfor = parforalloc(_MaxGcproc) work.markfor = parforalloc(_MaxGcproc)
gcpercent = readgogc() gcpercent = readgogc()
gcdatamask = unrollglobgcprog((*byte)(unsafe.Pointer(themoduledata.gcdata)), themoduledata.edata-themoduledata.data) for datap := &themoduledata; datap != nil; datap = datap.next {
gcbssmask = unrollglobgcprog((*byte)(unsafe.Pointer(themoduledata.gcbss)), themoduledata.ebss-themoduledata.bss) datap.gcdatamask = unrollglobgcprog((*byte)(unsafe.Pointer(datap.gcdata)), datap.edata-datap.data)
datap.gcbssmask = unrollglobgcprog((*byte)(unsafe.Pointer(datap.gcbss)), datap.ebss-datap.bss)
}
memstats.next_gc = heapminimum memstats.next_gc = heapminimum
} }
......
...@@ -60,10 +60,14 @@ func markroot(desc *parfor, i uint32) { ...@@ -60,10 +60,14 @@ func markroot(desc *parfor, i uint32) {
// Note: if you add a case here, please also update heapdump.go:dumproots. // Note: if you add a case here, please also update heapdump.go:dumproots.
switch i { switch i {
case _RootData: case _RootData:
scanblock(themoduledata.data, themoduledata.edata-themoduledata.data, gcdatamask.bytedata, &gcw) for datap := &themoduledata; datap != nil; datap = datap.next {
scanblock(datap.data, datap.edata-datap.data, datap.gcdatamask.bytedata, &gcw)
}
case _RootBss: case _RootBss:
scanblock(themoduledata.bss, themoduledata.ebss-themoduledata.bss, gcbssmask.bytedata, &gcw) for datap := &themoduledata; datap != nil; datap = datap.next {
scanblock(datap.bss, datap.ebss-datap.bss, datap.gcbssmask.bytedata, &gcw)
}
case _RootFinalizers: case _RootFinalizers:
for fb := allfin; fb != nil; fb = fb.alllink { for fb := allfin; fb != nil; fb = fb.alllink {
......
...@@ -37,14 +37,13 @@ type mheap struct { ...@@ -37,14 +37,13 @@ type mheap struct {
arena_end uintptr arena_end uintptr
arena_reserved bool arena_reserved bool
// write barrier shadow data+heap. // write barrier shadow heap.
// 64-bit systems only, enabled by GODEBUG=wbshadow=1. // 64-bit systems only, enabled by GODEBUG=wbshadow=1.
// See also shadow_data, data_start, data_end fields on moduledata in
// symtab.go.
shadow_enabled bool // shadow should be updated and checked shadow_enabled bool // shadow should be updated and checked
shadow_reserved bool // shadow memory is reserved shadow_reserved bool // shadow memory is reserved
shadow_heap uintptr // heap-addr + shadow_heap = shadow heap addr shadow_heap uintptr // heap-addr + shadow_heap = shadow heap addr
shadow_data uintptr // data-addr + shadow_data = shadow data addr
data_start uintptr // start of shadowed data addresses
data_end uintptr // end of shadowed data addresses
// central free lists for small size classes. // central free lists for small size classes.
// the padding makes sure that the MCentrals are // the padding makes sure that the MCentrals are
......
...@@ -426,8 +426,12 @@ func gomcache() *mcache { ...@@ -426,8 +426,12 @@ func gomcache() *mcache {
//go:linkname reflect_typelinks reflect.typelinks //go:linkname reflect_typelinks reflect.typelinks
//go:nosplit //go:nosplit
func reflect_typelinks() []*_type { func reflect_typelinks() [][]*_type {
return themoduledata.typelinks ret := [][]*_type{themoduledata.typelinks}
for datap := themoduledata.next; datap != nil; datap = datap.next {
ret = append(ret, datap.typelinks)
}
return ret
} }
// TODO: move back into mgc.go // TODO: move back into mgc.go
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
func isgoexception(info *exceptionrecord, r *context) bool { func isgoexception(info *exceptionrecord, r *context) bool {
// Only handle exception if executing instructions in Go binary // Only handle exception if executing instructions in Go binary
// (not Windows library code). // (not Windows library code).
// TODO(mwhudson): needs to loop to support shared libs
if r.ip() < themoduledata.text || themoduledata.etext < r.ip() { if r.ip() < themoduledata.text || themoduledata.etext < r.ip() {
return false return false
} }
......
...@@ -47,6 +47,17 @@ type moduledata struct { ...@@ -47,6 +47,17 @@ type moduledata struct {
end, gcdata, gcbss uintptr end, gcdata, gcbss uintptr
typelinks []*_type typelinks []*_type
gcdatamask, gcbssmask bitvector
// write barrier shadow data
// 64-bit systems only, enabled by GODEBUG=wbshadow=1.
// See also the shadow_* fields on mheap in mheap.go.
shadow_data uintptr // data-addr + shadow_data = shadow data addr
data_start uintptr // start of shadowed data addresses
data_end uintptr // end of shadowed data addresses
next *moduledata
} }
var themoduledata moduledata // linker symbol var themoduledata moduledata // linker symbol
...@@ -135,34 +146,45 @@ func (f *Func) FileLine(pc uintptr) (file string, line int) { ...@@ -135,34 +146,45 @@ func (f *Func) FileLine(pc uintptr) (file string, line int) {
return file, int(line32) return file, int(line32)
} }
func findmoduledatap(pc uintptr) *moduledata {
for datap := &themoduledata; datap != nil; datap = datap.next {
if datap.minpc <= pc && pc <= datap.maxpc {
return datap
}
}
return nil
}
func findfunc(pc uintptr) *_func { func findfunc(pc uintptr) *_func {
if pc < themoduledata.minpc || pc >= themoduledata.maxpc { datap := findmoduledatap(pc)
if datap == nil {
return nil return nil
} }
const nsub = uintptr(len(findfuncbucket{}.subbuckets)) const nsub = uintptr(len(findfuncbucket{}.subbuckets))
x := pc - themoduledata.minpc x := pc - datap.minpc
b := x / pcbucketsize b := x / pcbucketsize
i := x % pcbucketsize / (pcbucketsize / nsub) i := x % pcbucketsize / (pcbucketsize / nsub)
ffb := (*findfuncbucket)(add(unsafe.Pointer(themoduledata.findfunctab), b*unsafe.Sizeof(findfuncbucket{}))) ffb := (*findfuncbucket)(add(unsafe.Pointer(datap.findfunctab), b*unsafe.Sizeof(findfuncbucket{})))
idx := ffb.idx + uint32(ffb.subbuckets[i]) idx := ffb.idx + uint32(ffb.subbuckets[i])
if pc < themoduledata.ftab[idx].entry { if pc < datap.ftab[idx].entry {
throw("findfunc: bad findfunctab entry") throw("findfunc: bad findfunctab entry")
} }
// linear search to find func with pc >= entry. // linear search to find func with pc >= entry.
for themoduledata.ftab[idx+1].entry <= pc { for datap.ftab[idx+1].entry <= pc {
idx++ idx++
} }
return (*_func)(unsafe.Pointer(&themoduledata.pclntable[themoduledata.ftab[idx].funcoff])) return (*_func)(unsafe.Pointer(&datap.pclntable[datap.ftab[idx].funcoff]))
} }
func pcvalue(f *_func, off int32, targetpc uintptr, strict bool) int32 { func pcvalue(f *_func, off int32, targetpc uintptr, strict bool) int32 {
if off == 0 { if off == 0 {
return -1 return -1
} }
p := themoduledata.pclntable[off:] datap := findmoduledatap(f.entry) // inefficient
p := datap.pclntable[off:]
pc := f.entry pc := f.entry
val := int32(-1) val := int32(-1)
for { for {
...@@ -184,7 +206,7 @@ func pcvalue(f *_func, off int32, targetpc uintptr, strict bool) int32 { ...@@ -184,7 +206,7 @@ func pcvalue(f *_func, off int32, targetpc uintptr, strict bool) int32 {
print("runtime: invalid pc-encoded table f=", funcname(f), " pc=", hex(pc), " targetpc=", hex(targetpc), " tab=", p, "\n") print("runtime: invalid pc-encoded table f=", funcname(f), " pc=", hex(pc), " targetpc=", hex(targetpc), " tab=", p, "\n")
p = themoduledata.pclntable[off:] p = datap.pclntable[off:]
pc = f.entry pc = f.entry
val = -1 val = -1
for { for {
...@@ -204,7 +226,8 @@ func cfuncname(f *_func) *byte { ...@@ -204,7 +226,8 @@ func cfuncname(f *_func) *byte {
if f == nil || f.nameoff == 0 { if f == nil || f.nameoff == 0 {
return nil return nil
} }
return (*byte)(unsafe.Pointer(&themoduledata.pclntable[f.nameoff])) datap := findmoduledatap(f.entry) // inefficient
return (*byte)(unsafe.Pointer(&datap.pclntable[f.nameoff]))
} }
func funcname(f *_func) string { func funcname(f *_func) string {
...@@ -212,13 +235,14 @@ func funcname(f *_func) string { ...@@ -212,13 +235,14 @@ func funcname(f *_func) string {
} }
func funcline1(f *_func, targetpc uintptr, strict bool) (file string, line int32) { func funcline1(f *_func, targetpc uintptr, strict bool) (file string, line int32) {
datap := findmoduledatap(f.entry) // inefficient
fileno := int(pcvalue(f, f.pcfile, targetpc, strict)) fileno := int(pcvalue(f, f.pcfile, targetpc, strict))
line = pcvalue(f, f.pcln, targetpc, strict) line = pcvalue(f, f.pcln, targetpc, strict)
if fileno == -1 || line == -1 || fileno >= len(themoduledata.filetab) { if fileno == -1 || line == -1 || fileno >= len(datap.filetab) {
// print("looking for ", hex(targetpc), " in ", funcname(f), " got file=", fileno, " line=", lineno, "\n") // print("looking for ", hex(targetpc), " in ", funcname(f), " got file=", fileno, " line=", lineno, "\n")
return "?", 0 return "?", 0
} }
file = gostringnocopy(&themoduledata.pclntable[themoduledata.filetab[fileno]]) file = gostringnocopy(&datap.pclntable[datap.filetab[fileno]])
return return
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment