Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tinygo: -compiler command line argument (e.g. tinygo) #121

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

archie2x
Copy link

@archie2x archie2x commented Aug 6, 2024

help:

  • I haven't figured out how to propagate this to mkuimage.
  • it may make more sense as a buildopt instead of environ in build.go(?)
    • but the BuildOpts isn't always available
  • name of flag? -go-compiler(?)
  • any feedback!

This allows specifying the compiler explicitly in makebb and handles peculiarities of tinygo. It works by resolving and caching the path to the go-compiler then queries tinygo for its hidden go-build-tags and propagates them to package lookup / install. This allows the pruning algorithm to correctly add / remove files when syncing dependent packages.

Usage:

% makebb -h 
Usage of makebb:
  -compiler string
    	override go compiler to use (e.g. "/path/to/tinygo")
[...]

Resulting busybox is significantly smaller. I compiled a slightly more complicated project than e.g. u-root/cmds/core/{true,false} to test package resolution.

Standard go:

u-root % GOOS=linux makebb cmds/core/init
10:10:54 Build environment: GOARCH=arm64 GOOS=linux GOPATH=/Users/roger/go CGO_ENABLED=0 GO111MODULE= GOROOT=/opt/homebrew/Cellar/go/1.22.2/libexec PATH=/opt/homebrew/Cellar/go/1.22.2/libexec/bin
10:10:54 Compiler: go version go1.22.2 darwin/arm64
10:10:59 Successfully built "bb" (size 2556039 bytes -- 2.4 MiB). <============

tinygo:

u-root % GOOS=linux makebb -compiler tinygo cmds/core/init
10:10:06 Build environment: GOARCH=arm64 GOOS=linux GOPATH=/Users/roger/go CGO_ENABLED=0 GO111MODULE= GOROOT=/opt/homebrew/Cellar/go/1.22.2/libexec PATH=/opt/homebrew/Cellar/go/1.22.2/libexec/bin
10:10:06 Compiler: tinygo version 0.33.0-dev-6184a6cd darwin/arm64 (using go version go1.22.2 and LLVM version 18.1.2)
10:10:20 Successfully built "bb" (size 1408888 bytes -- 1.3 MiB).  <============

Needs:

diff --git a/cmds/core/init/fns_other.go b/cmds/core/init/fns_other.go
index 850ce067..f979aaea 100644
--- a/cmds/core/init/fns_other.go
+++ b/cmds/core/init/fns_other.go
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build !tinygo && !(darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris)
-// +build !tinygo,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
+//go:build !(darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris)
+// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
 
 package main
 
diff --git a/cmds/core/init/fns_unix.go b/cmds/core/init/fns_unix.go
index 4bcac715..4db8d9d0 100644
--- a/cmds/core/init/fns_unix.go
+++ b/cmds/core/init/fns_unix.go
@@ -2,8 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build !tinygo && (darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris)
-// +build !tinygo
+//go:build (darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris)
 // +build darwin dragonfly freebsd linux netbsd openbsd solaris
 
 package main
diff --git a/cmds/core/init/init_plan9.go b/cmds/core/init/init_plan9.go
index 4fb93f94..6b88f159 100644
--- a/cmds/core/init/init_plan9.go
+++ b/cmds/core/init/init_plan9.go
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build !tinygo && plan9
-// +build !tinygo,plan9
+//go:build plan9
+// +build plan9
 
 package main
 
diff --git a/cmds/core/init/init_tinygo.go b/cmds/core/init/init_tinygo.go
new file mode 100644
index 00000000..1d670023
--- /dev/null
+++ b/cmds/core/init/init_tinygo.go
@@ -0,0 +1,29 @@
+//go:build tinygo
+
+package main
+
+/*
+ld.lld: error: undefined symbol: syscall.runtime_BeforeExec
+>>> referenced by exec_unix.go:282 ([...]/go/1.22.2/libexec/src/syscall/exec_unix.go:282)
+>>>               /var/folders/7f/_6cnvk8j475gw2rk8q3hv8q00000gn/T/tinygo3894403787/main.lto.main.o:(runtime.run$1$gowrapper)
+
+ld.lld: error: undefined symbol: syscall.runtime_AfterExec
+>>> referenced by exec_unix.go:308 ([...]/go/1.22.2/libexec/src/syscall/exec_unix.go:308)
+>>>               /var/folders/7f/_6cnvk8j475gw2rk8q3hv8q00000gn/T/tinygo3894403787/main.lto.main.o:(runtime.run$1$gowrapper)
+failed to run tool: ld.lld
+failed to link /var/folders/7f/_6cnvk8j475gw2rk8q3hv8q00000gn/T/tinygo3894403787/main: exit status 1
+*/
+
+import (
+	_ "unsafe" // for go:linkname
+)
+
+//go:linkname runtime_BeforeExec syscall.runtime_BeforeExec
+func runtime_BeforeExec() {
+
+}
+
+//go:linkname runtime_AfterExec syscall.runtime_AfterExec
+func runtime_AfterExec() {
+
+}

Copy link
Contributor

@leongross leongross left a comment

Choose a reason for hiding this comment

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

Nice work, I anticipate this will help a lot to enable u-root builds with tinygo

Comment on lines 266 to 278
s := strings.Fields(string(v))
if len(s) < 3 {
return "", fmt.Errorf("unknown go version, tool returned weird output for 'go version': %v", string(v))
if len(s) < 1 {
return fmt.Errorf(efmt, string(v))
}
return s[2], nil

compiler := c.Compiler
compiler.Identifier = s[0]
compiler.VersionOutput = string(v)
compiler.IsInit = true

switch compiler.Identifier {

case "go":
Copy link
Contributor

@leongross leongross Aug 7, 2024

Choose a reason for hiding this comment

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

What do you think of refactoring this into smaller chunks and make it a bit more generic, such that it becomes a bit more readable?

type CompilerType int
const (
	CompilerGo CompilerType = iota
	CompilerTinygo
	CompilerUnkown
)

func CompilerTypeFromString(name string) CompilerType {
	val, ok := map[string]CompilerType{
		"go":     CompilerGo,
		"tinygo": CompilerTinygo,
	}[name]
	if ok {
		return val
	}
	return CompilerUnkown
}

Further one could implement a parsing function over the Compiler Type so that the parsing logic is put there.

func (c *Compiler) Parse(cType CompilerType) (err error){
  switch cType {
    case CompilerGo:
      // parse
    
    case CompilerTinygo:
      // parse
    
    case CompilerUnkown:
      // parse
    
    default:
      return fmt.Errorf("unkown")
  }
}
compilerID := CompilerTypeFromString(compiler.Identifier)
if err := compiler.Parse(compilerID); err != nil {
    // handle error
}

Copy link
Author

Choose a reason for hiding this comment

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

I would be happy to! Thanks for the feedback!

[I'm just beginning to learn go]

Copy link
Member

Choose a reason for hiding this comment

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

+1 on this

Comment on lines 436 to 437
switch c.Compiler.Identifier {
case "go":
Copy link
Contributor

Choose a reason for hiding this comment

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

Same thing here, this could be separated from the main logic of the build function to make it more readable.

@leongross
Copy link
Contributor

@archie2x if you need said changes

diff --git a/cmds/core/init/fns_other.go b/cmds/core/init/fns_other.go

in u-root, do you mind opening a pr? at some point it needs to be merged anyway, right?

@archie2x
Copy link
Author

archie2x commented Aug 7, 2024

@archie2x if you need said changes

diff --git a/cmds/core/init/fns_other.go b/cmds/core/init/fns_other.go

in u-root, do you mind opening a pr? at some point it needs to be merged anyway, right?

I was going to to, and actually, maybe a larger patch for enabling tinygo build of all cmds in u-root but I saw you already have a PR to make init in u-root/u-root#3062 or am I misunderstanding?

I think you're on a better track: putting the link fixes for tinygo in a shared package.

For the sake of managing all this, I just created a patch to tinygo to help us identify versions in the sources: tinygo-org/tinygo#4389

@leongross
Copy link
Contributor

@archie2x in this project you have to sign your commits (see here). Without that, the CI is not run and with that reviewing changes might take longer.

@leongross
Copy link
Contributor

@rminnich @hugelgupf what do you think of this?

@archie2x
Copy link
Author

archie2x commented Aug 9, 2024

@rminnich @hugelgupf what do you think of this?

I marked it as draft because I want to work through the GOROOT. I think, unless overridden by env, it has to be the same as the tinygo 'cached-GOROOT'

unique := make(map[string]bool)
addUnique := func(tag string) {
if unique[tag] {
return
Copy link
Member

@hugelgupf hugelgupf Sep 14, 2024

Choose a reason for hiding this comment

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

tags := make(map[string]struct{})
for _, tag := range c.BuildTags {
  tags[tag] = struct{}{}
}
for _, tag := range info["build_tags"].([]string) {
  tags[tag] = struct{}{}
}
c.BuildTags = maps.Keys(tags) // import "maps"

}

efmt := "go-compiler 'version' output unrecognized: %v"
Copy link
Member

Choose a reason for hiding this comment

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

here's what I'd do for errors.

var ErrNoGoVersion = fmt.Errorf("go-compiler version output unrecognized")

...

return fmt.Errorf("%w: %s", ErrNoGoVersion, string(v))

This allows using errors.Is with ErrNoGoVersion and keeping the error message the same for the two efmt cases

@archie2x archie2x marked this pull request as ready for review September 16, 2024 23:17
Signed-off-by: Roger Standridge <[email protected]>
Copy link

codecov bot commented Sep 18, 2024

Codecov Report

Attention: Patch coverage is 61.29032% with 48 lines in your changes missing coverage. Please review.

Project coverage is 61.61%. Comparing base (d8fbaca) to head (81f0c36).

Files with missing lines Patch % Lines
src/pkg/golang/compiler.go 60.00% 46 Missing ⚠️
src/cmd/makebb/makebb.go 75.00% 1 Missing ⚠️
src/pkg/golang/build.go 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #121      +/-   ##
==========================================
- Coverage   62.44%   61.61%   -0.84%     
==========================================
  Files          16       17       +1     
  Lines        1185     1266      +81     
==========================================
+ Hits          740      780      +40     
- Misses        445      486      +41     
Flag Coverage Δ
1.20 81.27% <61.29%> (-2.96%) ⬇️
1.21.x 81.27% <61.29%> (-2.96%) ⬇️
1.22.x 61.61% <61.29%> (-0.84%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Comment on lines 52 to 53
l.Printf("Build environment: %s", env)
l.Printf("Compiler: %s", env.Compiler.VersionOutput)
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks weird, probably one of my suggestions fits better?

Suggested change
l.Printf("Build environment: %s", env)
l.Printf("Compiler: %s", env.Compiler.VersionOutput)
l.Printf("Build environment: %s\n", env)
l.Printf("Compiler: %s\n", env.Compiler.VersionOutput)

Or

Suggested change
l.Printf("Build environment: %s", env)
l.Printf("Compiler: %s", env.Compiler.VersionOutput)
l.Printf("Build environment: %s Compiler: %s\n", env, env.Compiler.VersionOutput)

return cmd
}

// if go-compiler specified, resolve absolute-path from PATH,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// if go-compiler specified, resolve absolute-path from PATH,
// If go-compiler is specified, resolve the absolute-path from PATH,


// runs compilerCmd("version") and parse/caches output, to environ.Compiler
func (c *Environ) CompilerInit() error {

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change

compiler.IsInit = true

switch compiler.Type {

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change

c.compilerAbs()

cmd := c.compilerCmd("version")
v, err := cmd.CombinedOutput()
Copy link
Contributor

Choose a reason for hiding this comment

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

There is no occurance in your code that uses v as raw []bytes returned by cmd.CombinedOutput. Consider making v a string and get rid of all the string(v) if you never need v itself anyways.

}

func (c Environ) build(dirPath string, binaryPath string, pattern []string, opts *BuildOpts) error {

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change


switch c.Compiler.Type {
case CompilerGo:

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change

}

case CompilerTinygo:

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change

Comment on lines +217 to +218
// TODO: handle force-rebuild of packages (-a to standard go)
// TODO: handle EnableInlining
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume this is postponed? I am not sure about the options tinygo may offer here either. I will do some research about this, but independent of this PR, it should not be blocked by this. I think we can squeeze out some size reduction here by setting the right build options.

Copy link
Author

Choose a reason for hiding this comment

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

I think the issues is more that tinygo already does most of these by default so the existing command line arguments don't map well.

Signed-off-by: Roger Standridge <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants