Compare commits

..

2 Commits

Author SHA1 Message Date
Ayke van Laethem 28a083633c cgo: allow --export= in LDFLAGS
This allows people to export some functions, such as malloc. Example:

    // #cgo LDFLAGS: --export=malloc
    import "C"

This exports the function malloc.

Note that this is somewhat unsafe right now, but it is used regardless.
By using this workaround, people have some time to transition away from
using malloc/free directly or until malloc is made safe to be used in
this way.
2022-09-15 11:36:29 +02:00
Ayke van Laethem 7e7814a087 wasm: do not export malloc, calloc, realloc, free
These functions were exported by accident, because the compiler had no
way of saying these functions shouldn't be exported.

This can be a big code size reduction for small programs. Before:

    $ tinygo build -o test.wasm -target=wasi -no-debug -scheduler=none ./testdata/alias.go && ls -l test.wasm
    -rwxrwxr-x 1 ayke ayke 2947  8 sep 13:47 test.wasm

After:

    $ tinygo build -o test.wasm -target=wasi -no-debug -scheduler=none ./testdata/alias.go && ls -l test.wasm
    -rwxrwxr-x 1 ayke ayke 968  8 sep 13:47 test.wasm

This is all because the GC isn't needed anymore.

This commit also adds support for using //go:wasm-module to set the
module name of an exported function (the default remains env).
2022-09-15 11:36:26 +02:00
6 changed files with 65 additions and 79 deletions
+1
View File
@@ -142,6 +142,7 @@ var validLinkerFlags = []*regexp.Regexp{
re(`-L([^@\-].*)`),
re(`-O`),
re(`-O([^@\-].*)`),
re(`--export=(.+)`), // for wasm-ld
re(`-f(no-)?(pic|PIC|pie|PIE)`),
re(`-f(no-)?openmp(-simd)?`),
re(`-fsanitize=([^@\-].*)`),
+4 -3
View File
@@ -1059,11 +1059,12 @@ func (b *builder) createFunctionStart(intrinsic bool) {
if b.info.section != "" {
b.llvmFn.SetSection(b.info.section)
}
if b.info.exported && strings.HasPrefix(b.Triple, "wasm") {
if b.info.exported && b.info.module != "" && strings.HasPrefix(b.Triple, "wasm") {
// Set the exported name. This is necessary for WebAssembly because
// otherwise the function is not exported.
functionAttr := b.ctx.CreateStringAttribute("wasm-export-name", b.info.linkName)
b.llvmFn.AddFunctionAttr(functionAttr)
b.llvmFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.linkName))
// Set the export module.
b.llvmFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-module", b.info.module))
}
// Some functions have a pragma controlling the inlining level.
+11 -21
View File
@@ -210,8 +210,9 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
// exported.
func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
info := functionInfo{
// Pick the default linkName.
linkName: f.RelString(nil),
module: "env",
importName: f.Name(),
linkName: f.RelString(nil), // pick the default linkName
}
// Check for //go: pragmas, which may change the link name (among others).
info.parsePragmas(f)
@@ -225,10 +226,6 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
return
}
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
// Our importName for a wasm module (if we are compiling to wasm), or llvm link name
var importName string
for _, comment := range decl.Doc.List {
text := comment.Text
if strings.HasPrefix(text, "//export ") {
@@ -246,7 +243,8 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
continue
}
importName = parts[1]
info.importName = parts[1]
info.linkName = parts[1]
info.exported = true
case "//go:interrupt":
if hasUnsafeImport(f.Pkg.Pkg) {
@@ -254,10 +252,13 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
}
case "//go:wasm-module":
// Alternative comment for setting the import module.
if len(parts) != 2 {
continue
if len(parts) == 1 {
// Function must not be exported outside of the WebAssembly
// module (but only be made available for linking).
info.module = ""
} else if len(parts) == 2 {
info.module = parts[1]
}
info.module = parts[1]
case "//go:inline":
info.inline = inlineHint
case "//go:noinline":
@@ -297,17 +298,6 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
}
}
}
// Set the importName for our exported function if we have one
if importName != "" {
if info.module == "" {
info.linkName = importName
} else {
// WebAssembly import
info.importName = importName
}
}
}
}
+2 -2
View File
@@ -62,7 +62,7 @@ declare void @main.undefinedFunctionNotInSection(i8*) #0
attributes #0 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #2 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="extern_func" "wasm-import-module"="env" "wasm-import-name"="extern_func" }
attributes #2 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-module"="env" "wasm-export-name"="extern_func" "wasm-import-module"="env" "wasm-import-name"="extern_func" }
attributes #3 = { inlinehint nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #4 = { noinline nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #5 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="exportedFunctionInSection" "wasm-import-module"="env" "wasm-import-name"="exportedFunctionInSection" }
attributes #5 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-module"="env" "wasm-export-name"="exportedFunctionInSection" "wasm-import-module"="env" "wasm-import-name"="exportedFunctionInSection" }
+34 -40
View File
@@ -62,63 +62,57 @@ func growHeap() bool {
}
// The below functions override the default allocator of wasi-libc. This ensures
// code linked from other languages can allocate memory on the same heap as the
// TinyGo heap.
// code linked from other languages can allocate memory without colliding with
// our GC allocations.
// Keep track of all the heap allocations while they're in use.
// This is not the most efficient solution but it costs a lot less in code size
// compared to a map.
var allocs []unsafe.Pointer
func trackAlloc(ptr unsafe.Pointer) {
// Try to find some empty space in the allocs slice.
for i, slot := range allocs {
if slot == nil {
allocs[i] = slot
return
}
}
// Couldn't find this space. Fall back to appending to the end.
allocs = append(allocs, ptr)
}
func removeAlloc(ptr unsafe.Pointer) {
// Remove the pointer so it can be garbage collected.
for i, slot := range allocs {
if ptr == slot {
allocs[i] = nil
return
}
}
}
var allocs = make(map[uintptr][]byte)
//export malloc
//go:wasm-module
func libc_malloc(size uintptr) unsafe.Pointer {
ptr := alloc(size, nil)
trackAlloc(ptr)
buf := make([]byte, size)
ptr := unsafe.Pointer(&buf[0])
allocs[uintptr(ptr)] = buf
return ptr
}
//export free
//go:wasm-module
func libc_free(ptr unsafe.Pointer) {
removeAlloc(ptr)
free(ptr)
if ptr == nil {
return
}
if _, ok := allocs[uintptr(ptr)]; ok {
delete(allocs, uintptr(ptr))
} else {
panic("free: invalid pointer")
}
}
//export calloc
//go:wasm-module
func libc_calloc(nmemb, size uintptr) unsafe.Pointer {
// Note: we could be even more correct here and check that nmemb * size
// doesn't overflow. However the current implementation should normally work
// fine.
// No difference between calloc and malloc.
return libc_malloc(nmemb * size)
}
//export realloc
//go:wasm-module
func libc_realloc(oldPtr unsafe.Pointer, size uintptr) unsafe.Pointer {
newPtr := realloc(oldPtr, size)
if newPtr != oldPtr {
removeAlloc(oldPtr)
trackAlloc(newPtr)
// It's hard to optimize this to expand the current buffer with our GC, but
// it is theoretically possible. For now, just always allocate fresh.
buf := make([]byte, size)
if oldPtr != nil {
if oldBuf, ok := allocs[uintptr(oldPtr)]; ok {
copy(buf, oldBuf)
delete(allocs, uintptr(oldPtr))
} else {
panic("realloc: invalid pointer")
}
}
return newPtr
ptr := unsafe.Pointer(&buf[0])
allocs[uintptr(ptr)] = buf
return ptr
}
+13 -13
View File
@@ -23,13 +23,13 @@ func libc_calloc(nmemb, size uintptr) unsafe.Pointer
//export realloc
func libc_realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer
func getFilledBuffer_malloc() unsafe.Pointer {
func getFilledBuffer_malloc() uintptr {
ptr := libc_malloc(5)
fillPanda(ptr)
return ptr
return uintptr(ptr)
}
func getFilledBuffer_calloc() unsafe.Pointer {
func getFilledBuffer_calloc() uintptr {
ptr := libc_calloc(2, 5)
fillPanda(ptr)
*(*byte)(unsafe.Add(ptr, 5)) = 'b'
@@ -37,23 +37,23 @@ func getFilledBuffer_calloc() unsafe.Pointer {
*(*byte)(unsafe.Add(ptr, 7)) = 'a'
*(*byte)(unsafe.Add(ptr, 8)) = 'r'
*(*byte)(unsafe.Add(ptr, 9)) = 's'
return ptr
return uintptr(ptr)
}
func getFilledBuffer_realloc() unsafe.Pointer {
func getFilledBuffer_realloc() uintptr {
origPtr := getFilledBuffer_malloc()
ptr := libc_realloc(origPtr, 9)
ptr := libc_realloc(unsafe.Pointer(origPtr), 9)
*(*byte)(unsafe.Add(ptr, 5)) = 'b'
*(*byte)(unsafe.Add(ptr, 6)) = 'e'
*(*byte)(unsafe.Add(ptr, 7)) = 'a'
*(*byte)(unsafe.Add(ptr, 8)) = 'r'
return ptr
return uintptr(ptr)
}
func getFilledBuffer_reallocNil() unsafe.Pointer {
func getFilledBuffer_reallocNil() uintptr {
ptr := libc_realloc(nil, 5)
fillPanda(ptr)
return ptr
return uintptr(ptr)
}
func fillPanda(ptr unsafe.Pointer) {
@@ -64,10 +64,10 @@ func fillPanda(ptr unsafe.Pointer) {
*(*byte)(unsafe.Add(ptr, 4)) = 'a'
}
func checkFilledBuffer(t *testing.T, ptr unsafe.Pointer, content string) {
func checkFilledBuffer(t *testing.T, ptr uintptr, content string) {
t.Helper()
buf := *(*string)(unsafe.Pointer(&reflect.StringHeader{
Data: uintptr(ptr),
Data: ptr,
Len: uintptr(len(content)),
}))
if buf != content {
@@ -78,7 +78,7 @@ func checkFilledBuffer(t *testing.T, ptr unsafe.Pointer, content string) {
func TestMallocFree(t *testing.T) {
tests := []struct {
name string
getBuffer func() unsafe.Pointer
getBuffer func() uintptr
content string
}{
{
@@ -121,7 +121,7 @@ func TestMallocFree(t *testing.T) {
checkFilledBuffer(t, bufPtr, tt.content)
libc_free(bufPtr)
libc_free(unsafe.Pointer(bufPtr))
})
}
}