From 7e7814a0872c079d82d2f1837078e7eb5db06798 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 8 Sep 2022 13:44:48 +0200 Subject: [PATCH 1/2] 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). --- compiler/compiler.go | 7 ++++--- compiler/symbol.go | 32 +++++++++++--------------------- compiler/testdata/pragma.ll | 4 ++-- src/runtime/arch_tinygowasm.go | 4 ++++ 4 files changed, 21 insertions(+), 26 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index bbaf6432f4..179a0928ee 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -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. diff --git a/compiler/symbol.go b/compiler/symbol.go index c4ce6f21df..491b845c6f 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -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 - } - } - } } diff --git a/compiler/testdata/pragma.ll b/compiler/testdata/pragma.ll index b243602d30..403f4f4a07 100644 --- a/compiler/testdata/pragma.ll +++ b/compiler/testdata/pragma.ll @@ -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" } diff --git a/src/runtime/arch_tinygowasm.go b/src/runtime/arch_tinygowasm.go index 910858be6d..eb5ba5b588 100644 --- a/src/runtime/arch_tinygowasm.go +++ b/src/runtime/arch_tinygowasm.go @@ -68,6 +68,7 @@ func growHeap() bool { var allocs = make(map[uintptr][]byte) //export malloc +//go:wasm-module func libc_malloc(size uintptr) unsafe.Pointer { buf := make([]byte, size) ptr := unsafe.Pointer(&buf[0]) @@ -76,6 +77,7 @@ func libc_malloc(size uintptr) unsafe.Pointer { } //export free +//go:wasm-module func libc_free(ptr unsafe.Pointer) { if ptr == nil { return @@ -88,12 +90,14 @@ func libc_free(ptr unsafe.Pointer) { } //export calloc +//go:wasm-module func libc_calloc(nmemb, size uintptr) unsafe.Pointer { // 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 { // 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. From 28a083633c23d5ac0703aa9e98f49a97ffe12652 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 14 Sep 2022 13:07:27 +0200 Subject: [PATCH 2/2] 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. --- cgo/security.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cgo/security.go b/cgo/security.go index 2fea40c8a2..28bd23ef68 100644 --- a/cgo/security.go +++ b/cgo/security.go @@ -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=([^@\-].*)`),