Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if err != nil {
return BuildResult{}, err
}
if _, ok := globalValues["runtime"]["godebugDefault"]; !ok {
globalValues["runtime"]["godebugDefault"] = lprogram.MainPkg().DefaultGODEBUG
}
result := BuildResult{
ModuleRoot: lprogram.MainPkg().Module.Dir,
MainDir: lprogram.MainPkg().Dir,
Expand Down Expand Up @@ -1365,6 +1368,9 @@ 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) error {
if err := transform.PrepareRuntimeStartup(mod); err != nil {
return err
}
err := interp.Run(mod, config.Options.InterpTimeout, config.Options.InterpMaxLoopIterations, config.DumpSSA())
if err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions builder/testdata/binary-size.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ pico2 ./testdata/size-os z 6199 1209 128 5592
pico2 ./testdata/size-os 2 15290 1014 128 5592
cortex-m-qemu ./testdata/size-corpus z 9891 1301 80 4188
cortex-m-qemu ./testdata/size-corpus 2 17466 862 72 4188
riscv-qemu ./testdata/size-corpus z 17508 1028 88 16660
riscv-qemu ./testdata/size-corpus 2 25160 744 80 16644
riscv-qemu ./testdata/size-corpus z 17556 1028 88 16660
riscv-qemu ./testdata/size-corpus 2 23225 935 80 16644
2 changes: 2 additions & 0 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2355,6 +2355,8 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
//
// This is also where compiler intrinsics are implemented.
func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) {
b.markRuntimeFeatureUse(instr)

// See if this is an intrinsic function that is handled specially.
if fn := instr.StaticCallee(); fn != nil {
// Direct function call, either to a named or anonymous (directly
Expand Down
2 changes: 2 additions & 0 deletions compiler/defer.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,8 @@ func (b *builder) loadDeferredCallParams(structType llvm.Type, ptr llvm.Value) [
// createDefer emits a single defer instruction, to be run when this function
// returns.
func (b *builder) createDefer(instr *ssa.Defer) {
b.markRuntimeFeatureUse(&instr.Call)

// The pointer to the previous defer struct, which we will replace to
// make a linked list.
next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next")
Expand Down
2 changes: 2 additions & 0 deletions compiler/goroutine.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (

// createGo emits code to start a new goroutine.
func (b *builder) createGo(instr *ssa.Go) {
b.markRuntimeFeatureUse(&instr.Call)

if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
// We cheat. None of the builtins do any long or blocking operation, so
// we might as well run these builtins right away without the program
Expand Down
34 changes: 34 additions & 0 deletions compiler/runtimefeature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package compiler

import (
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)

func (b *builder) markRuntimeFeatureUse(call *ssa.CallCommon) {
callee := call.StaticCallee()
if callee == nil {
return
}

var markerName string
switch b.getFunctionInfo(callee).linkName {
case "runtime.GOROOT":
markerName = "tinygo.runtime.feature.goroot"
case "(*internal/godebug.Setting).Value":
markerName = "tinygo.runtime.feature.godebug"
case "internal/godebug.setUpdate":
if b.fn.Pkg != nil && b.fn.Pkg.Pkg.Path() == "internal/godebug" {
return
}
markerName = "tinygo.runtime.feature.godebug"
default:
return
}

marker := b.mod.NamedFunction(markerName)
if marker.IsNil() {
marker = llvm.AddFunction(b.mod, markerName, llvm.FunctionType(b.ctx.VoidType(), nil, false))
}
b.CreateCall(marker.GlobalValueType(), marker, nil, "")
}
17 changes: 9 additions & 8 deletions loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,15 @@ type Program struct {

// PackageJSON is a subset of the JSON struct returned from `go list`.
type PackageJSON struct {
Dir string
ImportPath string
Name string
ForTest string
Root string
Standard bool
DepOnly bool
Module struct {
Dir string
ImportPath string
Name string
ForTest string
Root string
Standard bool
DepOnly bool
DefaultGODEBUG string
Module struct {
Path string
Main bool
Dir string
Expand Down
69 changes: 69 additions & 0 deletions runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
"github.com/tinygo-org/tinygo/builder"
"github.com/tinygo-org/tinygo/goenv"
)

// TestTimerStopResetRace checks that stopping or resetting a timer while its
Expand Down Expand Up @@ -166,3 +167,71 @@ func TestStdin(t *testing.T) {
}
checkOutput(t, TESTDATA+"/stdin.txt", output.Bytes())
}

func TestRuntimeGOROOT(t *testing.T) {
t.Parallel()

for _, test := range []struct {
name string
environment []string
want string
}{
{"startup environment", []string{"GOROOT=/startup"}, "/startup"},
{"empty startup environment", []string{"GOROOT="}, goenv.Get("GOROOT")},
} {
t.Run(test.name, func(t *testing.T) {
options := optionsFromTarget(*testTarget, sema)
config, err := builder.NewConfig(&options)
if err != nil {
t.Fatal(err)
}

output := &bytes.Buffer{}
_, err = buildAndRun("testdata/runtimegoroot.go", config, output, nil, test.environment, time.Minute, func(cmd *exec.Cmd, _ builder.BuildResult) error {
return cmd.Run()
})
if err != nil {
t.Fatal(err)
}
want := test.want + "\n" + test.want + "\n"
if output.String() != want {
t.Fatalf("unexpected output:\n%s\nwant:\n%s", output.String(), want)
}
})
}
}

func TestRuntimeGODEBUG(t *testing.T) {
t.Parallel()

for _, test := range []struct {
name string
program string
environment []string
want string
}{
{"compiled default", "testdata/runtimegodebug.go", nil, "true \"\"\ntrue \"\"\ntrue \"\"\ntrue \"tarinsecurepath=1\"\ntrue \"\"\ntrue \"\"\n"},
{"environment override", "testdata/runtimegodebug.go", []string{"GODEBUG=tarinsecurepath=1"}, "true \"tarinsecurepath=1\"\ntrue \"\"\ntrue \"\"\ntrue \"tarinsecurepath=1\"\ntrue \"\"\ntrue \"\"\n"},
{"setting compiled default", "testdata/runtimegodebugsetting.go", nil, "true\nfalse\ntrue\ntrue\n"},
{"setting environment override", "testdata/runtimegodebugsetting.go", []string{"GODEBUG=tarinsecurepath=1"}, "false\nfalse\ntrue\ntrue\n"},
} {
t.Run(test.name, func(t *testing.T) {
options := optionsFromTarget(*testTarget, sema)
config, err := builder.NewConfig(&options)
if err != nil {
t.Fatal(err)
}

output := &bytes.Buffer{}
_, err = buildAndRun(test.program, config, output, nil, test.environment, time.Minute, func(cmd *exec.Cmd, _ builder.BuildResult) error {
return cmd.Run()
})
if err != nil {
t.Fatal(err)
}
if output.String() != test.want {
t.Fatalf("unexpected output:\n%s\nwant:\n%s", output.String(), test.want)
}
})
}
}
7 changes: 5 additions & 2 deletions src/runtime/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ func syscallSetenv(key, value string) {
keydata := cstring(key)
valdata := cstring(value)
setenv(&keydata[0], &valdata[0])
if key == "GODEBUG" && godebugUpdate != nil {
if key == "GODEBUG" && godebugEnvEnabled() {
// Starting with Go 1.20, we need to call a callback (set by
// internal/godebug) to notify the GODEBUG environment variable has
// changed. This is necessary to get archive/zip to pass tests.
godebugUpdate(key, value)
godebugSetEnv(value)
}
}

Expand All @@ -25,6 +25,9 @@ func syscallSetenv(key, value string) {
func syscallUnsetenv(key string) {
keydata := cstring(key)
unsetenv(&keydata[0])
if key == "GODEBUG" && godebugEnvEnabled() {
godebugUnsetEnv()
}
}

// Clear the environment.
Expand Down
15 changes: 9 additions & 6 deletions src/runtime/env_nonhosted.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ func syscallSetenv(key, value string) {
for i, e := range env {
if envKey(e) == key {
env[i] = entry
if key == "GODEBUG" && godebugUpdate != nil {
godebugUpdate(key, value)
if key == "GODEBUG" && godebugEnvEnabled() {
godebugSetEnv(value)
}
return
}
}
env = append(env, entry)
if key == "GODEBUG" && godebugUpdate != nil {
godebugUpdate(key, value)
if key == "GODEBUG" && godebugEnvEnabled() {
godebugSetEnv(value)
}
}

Expand All @@ -25,12 +25,15 @@ func syscallUnsetenv(key string) {
for i, e := range env {
if envKey(e) == key {
env = append(env[:i], env[i+1:]...)
if key == "GODEBUG" && godebugUpdate != nil {
godebugUpdate(key, "")
if key == "GODEBUG" && godebugEnvEnabled() {
godebugUnsetEnv()
}
return
}
}
if key == "GODEBUG" {
godebugUnsetEnv()
}
}

func envKey(entry string) string {
Expand Down
27 changes: 27 additions & 0 deletions src/runtime/env_startup_libc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//go:build darwin || windows || wasip1 || (linux && !baremetal && !wasm_unknown && !wasip2 && !nintendoswitch)

package runtime

import "unsafe"

//export getenv
func libc_getenv(name *byte) *byte

func startupEnv(key string) string {
name := make([]byte, len(key)+1)
copy(name, key)
value := libc_getenv(&name[0])
if value == nil {
return ""
}
length := strlen(unsafe.Pointer(value))
return string(unsafe.Slice(value, length))
}

func init() {
if gorootEnvEnabled() {
if value := startupEnv("GOROOT"); value != "" {
goroot = value
}
}
}
8 changes: 4 additions & 4 deletions src/runtime/env_wasip2.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ package runtime

//go:linkname syscallSetenv syscall.runtimeSetenv
func syscallSetenv(key, value string) {
if key == "GODEBUG" && godebugUpdate != nil {
godebugUpdate(key, value)
if key == "GODEBUG" && godebugEnvEnabled() {
godebugSetEnv(value)
}
}

//go:linkname syscallUnsetenv syscall.runtimeUnsetenv
func syscallUnsetenv(key string) {
if key == "GODEBUG" && godebugUpdate != nil {
godebugUpdate(key, "")
if key == "GODEBUG" && godebugEnvEnabled() {
godebugUnsetEnv()
}
}
9 changes: 9 additions & 0 deletions src/runtime/godebug_startup_libc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//go:build (linux && !baremetal && !wasm_unknown && !wasip2 && !nintendoswitch) || darwin || windows

package runtime

func init() {
if godebugEnvEnabled() {
godebugStartup = startupEnv("GODEBUG")
}
}
20 changes: 20 additions & 0 deletions src/runtime/nonhosted.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,23 @@ func init() {
env = append(env, s[start:])
}
}

func init() {
if gorootEnvEnabled() {
if value := startupEnv("GOROOT"); value != "" {
goroot = value

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This puts the feature-gate calls in the same initializer that parses osArgs and osEnv. Interp runs before the optimizer resolves these gates, so it cannot interpret this initializer and leaves the parsing code to run at startup. Could we split the gate checks into a second initializer?

 		env = append(env, s[start:])
 	}
+}
+
+func init() {
 	if gorootEnvEnabled() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed that, but not really clear it did much?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I’ll test.

}
}
if godebugEnvEnabled() {
godebugStartup = startupEnv("GODEBUG")
}
}

func startupEnv(key string) string {
for _, entry := range env {
if len(entry) > len(key) && entry[:len(key)] == key && entry[len(key)] == '=' {
return entry[len(key)+1:]
}
}
return ""
}
Loading
Loading