From 132cc7b16cbeab28efd51fcd3d9dfcada9e619a0 Mon Sep 17 00:00:00 2001 From: Damian Gryski Date: Wed, 12 Apr 2023 09:20:45 -0700 Subject: [PATCH] Add support for using wizer to handle running initAll() --- builder/build.go | 36 +++++++++++++++++++++++++++++---- compileopts/options.go | 1 + goenv/goenv.go | 29 ++++++++++++++++++++++++++ main.go | 8 ++++++++ src/runtime/runtime.go | 6 ++++++ src/runtime/runtime_wasip1.go | 37 ++++++++++++++++++++++++++++++++-- src/runtime/scheduler_any.go | 9 +++++++-- src/runtime/scheduler_none.go | 7 +++++-- src/syscall/build_asserts.go | 7 +++++++ src/syscall/build_noasserts.go | 7 +++++++ src/syscall/syscall_libc.go | 6 ++++++ 11 files changed, 143 insertions(+), 10 deletions(-) create mode 100644 src/syscall/build_asserts.go create mode 100644 src/syscall/build_noasserts.go diff --git a/builder/build.go b/builder/build.go index c569bb1d21..daea496ce7 100644 --- a/builder/build.go +++ b/builder/build.go @@ -854,6 +854,33 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } } + if config.Options.WizerInit { + var args []string + + resultWizer := result.Executable + "-wizer" + + args = append(args, + "--allow-wasi", + "--wasm-bulk-memory=true", + "-f", "runtime.wizerInit", + result.Executable, + "-o", resultWizer, + ) + + cmd := exec.Command(goenv.Get("WIZER"), args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err := cmd.Run() + if err != nil { + return fmt.Errorf("wizer failed: %w", err) + } + + if err := os.Rename(resultWizer, result.Executable); err != nil { + return fmt.Errorf("rename failed: %w", err) + } + } + // Print code size if requested. if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" { packagePathMap := make(map[string]string, len(lprogram.Packages)) @@ -1051,9 +1078,10 @@ func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, c // needed to convert a program to its final form. Some transformations are not // optional and must be run as the compiler expects them to run. func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues map[string]map[string]string) error { - err := interp.Run(mod, config.Options.InterpTimeout, config.DumpSSA()) - if err != nil { - return err + if !config.Options.WizerInit { + if err := interp.Run(mod, config.Options.InterpTimeout, config.DumpSSA()); err != nil { + return err + } } if config.VerifyIR() { // Only verify if we really need it. @@ -1069,7 +1097,7 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues m } // Insert values from -ldflags="-X ..." into the IR. - err = setGlobalValues(mod, globalValues) + err := setGlobalValues(mod, globalValues) if err != nil { return err } diff --git a/compileopts/options.go b/compileopts/options.go index debdaf08cd..bcc3e447dc 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -53,6 +53,7 @@ type Options struct { Monitor bool BaudRate int Timeout time.Duration + WizerInit bool } // Verify performs a validation on the given options, raising an error if options are not valid. diff --git a/goenv/goenv.go b/goenv/goenv.go index be1c631ca9..9beceb41b6 100644 --- a/goenv/goenv.go +++ b/goenv/goenv.go @@ -159,6 +159,35 @@ func Get(name string) string { } return findWasmOpt() + + case "WIZER": + if path := os.Getenv("WIZER"); path != "" { + wizerBin := "wizer" + if runtime.GOOS == "windows" { + wizerBin += ".exe" + } + + _, err := exec.LookPath(wizerBin) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot use %q as wasm-opt (from WASMOPT environment variable): %s", path, err.Error()) + os.Exit(1) + } + + return path + } + + wizerBin := "wizer" + if runtime.GOOS == "windows" { + wizerBin += ".exe" + } + + path, err := exec.LookPath(wizerBin) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot use %q as wizer: %s", path, err.Error()) + os.Exit(1) + } + return path + default: return "" } diff --git a/main.go b/main.go index 6b3aeb3b03..25d56e747a 100644 --- a/main.go +++ b/main.go @@ -1440,6 +1440,7 @@ func main() { cpuprofile := flag.String("cpuprofile", "", "cpuprofile output") monitor := flag.Bool("monitor", false, "enable serial monitor") baudrate := flag.Int("baudrate", 115200, "baudrate of serial monitor") + wizerInit := flag.Bool("wizer-init", false, "use wizer for package initialization") // Internal flags, that are only intended for TinyGo development. printIR := flag.Bool("internal-printir", false, "print LLVM IR") @@ -1512,6 +1513,11 @@ func main() { ocdCommands = strings.Split(*ocdCommandsString, ",") } + if *wizerInit && *target != "wasip1" { + fmt.Fprintf(os.Stderr, "-wizer-init only makes sense with -target=wasip1, got -target=%q\n", *target) + os.Exit(1) + } + options := &compileopts.Options{ GOOS: goenv.Get("GOOS"), GOARCH: goenv.Get("GOARCH"), @@ -1544,7 +1550,9 @@ func main() { Monitor: *monitor, BaudRate: *baudrate, Timeout: *timeout, + WizerInit: *wizerInit, } + if *printCommands { options.PrintCommands = printCommand } diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index a93260ba85..f6e9a44ccd 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -12,6 +12,12 @@ const Compiler = "tinygo" // package. func initAll() +var runtimeInitialized = false + +func runtime_is_initialized() bool { + return runtimeInitialized +} + //go:linkname callMain main.main func callMain() diff --git a/src/runtime/runtime_wasip1.go b/src/runtime/runtime_wasip1.go index 595cab9bf0..26f55d4e67 100644 --- a/src/runtime/runtime_wasip1.go +++ b/src/runtime/runtime_wasip1.go @@ -15,10 +15,29 @@ func __wasm_call_ctors() //export _start func _start() { + runtimeInitialize() + run() +} + +//export runtime.wizerInit +func runtimeInitialize() { + if runtimeInitialized { + // Second time initialization is happening. Refresh environment + // to whatever our current host gives us instead of whatever + // libc cached. + reset_libc_environment() + return + } // These need to be initialized early so that the heap can be initialized. heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) - run() + initHeap() + if hasScheduler { + go initAll() + } else { + initAll() + } + runtimeInitialized = true } // Read the command line arguments from WASI. @@ -29,6 +48,17 @@ func init() { __wasm_call_ctors() } +//export __wasilibc_deinitialize_environ +func __wasilibc_deinitialize_environ() + +//export __wasilibc_initialize_environ +func __wasilibc_initialize_environ() + +func reset_libc_environment() { + __wasilibc_deinitialize_environ() + __wasilibc_initialize_environ() +} + var args []string //go:linkname os_runtime_args os.runtime_args @@ -39,7 +69,10 @@ func os_runtime_args() []string { var argc, argv_buf_size uint32 args_sizes_get(&argc, &argv_buf_size) if argc == 0 { - return nil + // Most things expect os.Args to have at least the + // program name. We don't have one, but also don't + // return just a nil slice. + return []string{""} } // Obtain the command line arguments diff --git a/src/runtime/scheduler_any.go b/src/runtime/scheduler_any.go index 0911a2dc73..e657a77bf8 100644 --- a/src/runtime/scheduler_any.go +++ b/src/runtime/scheduler_any.go @@ -19,9 +19,14 @@ func sleep(duration int64) { // run is called by the program entry point to execute the go program. // With a scheduler, init and the main function are invoked in a goroutine before starting the scheduler. func run() { - initHeap() + if !runtimeInitialized { + initHeap() + } go func() { - initAll() + if !runtimeInitialized { + initAll() + runtimeInitialized = true + } callMain() schedulerDone = true }() diff --git a/src/runtime/scheduler_none.go b/src/runtime/scheduler_none.go index 5079a80853..4fbdce235c 100644 --- a/src/runtime/scheduler_none.go +++ b/src/runtime/scheduler_none.go @@ -20,8 +20,11 @@ func getSystemStackPointer() uintptr { // run is called by the program entry point to execute the go program. // With the "none" scheduler, init and the main function are invoked directly. func run() { - initHeap() - initAll() + if !runtimeInitialized { + initHeap() + initAll() + runtimeInitialized = true + } callMain() } diff --git a/src/syscall/build_asserts.go b/src/syscall/build_asserts.go new file mode 100644 index 0000000000..beefe08097 --- /dev/null +++ b/src/syscall/build_asserts.go @@ -0,0 +1,7 @@ +//go:build syscall_asserts + +package syscall + +// enable assertions for checking Environ during runtime preinitialization. +// This is to catch people using os.Environ() during package inits with wizer. +const panicOnEnvironDuringInitAll = true diff --git a/src/syscall/build_noasserts.go b/src/syscall/build_noasserts.go new file mode 100644 index 0000000000..48ab0fb6af --- /dev/null +++ b/src/syscall/build_noasserts.go @@ -0,0 +1,7 @@ +//go:build !syscall_asserts + +package syscall + +// enable assertions for checking Environ during runtime preinitialization. +// This is to catch people using os.Environ() during package init with wizer. +const panicOnEnvironDuringInitAll = false diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index fb2e23968e..9133853167 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -260,7 +260,13 @@ func Mprotect(b []byte, prot int) (err error) { return } +//go:linkname runtime_is_initialized runtime.runtime_is_initialized +func runtime_is_initialized() bool + func Environ() []string { + if panicOnEnvironDuringInitAll && !runtime_is_initialized() { + panic("syscall.Environ() called during runtime preinitialization") + } // This function combines all the environment into a single allocation. // While this optimizes for memory usage and garbage collector