diff --git a/.gitignore b/.gitignore index 026c0a6..8f29201 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ output_* ./cmd/gtest/gtest ./cmd/gbc/gbc ./gbc + +*.exe \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..fed2909 --- /dev/null +++ b/build.bat @@ -0,0 +1,5 @@ +@echo off + +make %* +del gbc.exe +ren gbc gbc.exe diff --git a/cmd/gbc/main.go b/cmd/gbc/main.go index e1a636b..e5af96c 100644 --- a/cmd/gbc/main.go +++ b/cmd/gbc/main.go @@ -82,7 +82,7 @@ func main() { // Apply language standard if err := cfg.ApplyStd(std); err != nil { - util.Error(token.Token{}, err.Error()) + util.Error(token.Token{}, "%s", err.Error()) } // Set target architecture @@ -115,6 +115,7 @@ func main() { finalInputFiles := processInputFiles(inputFiles, cfg) if len(finalInputFiles) == 0 { util.Error(token.Token{}, "no input files specified.") + fmt.Fprintln(os.Stderr, "gbc: info: tip: use '-h' flag to show help.") } // Second pass: compile everything diff --git a/cmd/gtest/gtest.exe b/cmd/gtest/gtest.exe new file mode 100644 index 0000000..5ddb94c Binary files /dev/null and b/cmd/gtest/gtest.exe differ diff --git a/gbc.exe b/gbc.exe new file mode 100644 index 0000000..ed1916d Binary files /dev/null and b/gbc.exe differ diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 026f5d0..dd086ac 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -11,7 +11,10 @@ import ( "golang.org/x/term" ) -type IndentState struct { levels []uint8; baseUnit uint8 } +type IndentState struct { + levels []uint8 + baseUnit uint8 +} func NewIndentState() *IndentState { return &IndentState{ @@ -589,7 +592,9 @@ func (a *App) formatFlagGroup(sb *strings.Builder, group FlagGroup, indent *Inde func getTerminalWidth() int { width, _, err := term.GetSize(int(os.Stdout.Fd())) - if err != nil { return 80 } + if err != nil { + return 80 + } if width < 20 { return 20 } diff --git a/pkg/codegen/qbe_backend-fallback.go b/pkg/codegen/qbe_backend-fallback.go new file mode 100644 index 0000000..6ecafad --- /dev/null +++ b/pkg/codegen/qbe_backend-fallback.go @@ -0,0 +1,65 @@ +//go:build windows + +package codegen + +import ( + "bytes" + "fmt" + "io" + "os" + "os/exec" + + "github.com/xplshn/gbc/pkg/config" + "github.com/xplshn/gbc/pkg/ir" +) + +func (b *qbeBackend) Generate(prog *ir.Program, cfg *config.Config) (*bytes.Buffer, error) { + fmt.Println("Self-contained QBE backend is not supported on Windows. Fallbacking to system's 'qbe'.") + _, err := exec.LookPath("qbe") + if err != nil { + return nil, fmt.Errorf("QBE not found in PATH: %s", err.Error()) + } + + qbeIR, err := b.GenerateIR(prog, cfg) + if err != nil { + return nil, err + } + + input_file, err := os.CreateTemp("", "gbc-qbe-*.temp.ssa") + if err != nil { + return nil, err + } + defer input_file.Close() + defer os.Remove(input_file.Name()) + + if _, err = input_file.WriteString(qbeIR); err != nil { + return nil, err + } + + output_file_name := input_file.Name() + ".asm" + cmd := exec.Command( + "qbe", + "-o", output_file_name, + "-t", cfg.BackendTarget, + input_file.Name(), + ) + + err = cmd.Run() + if err != nil { + return nil, fmt.Errorf("\n--- QBE Compilation Failed ---\nGenerated IR:\n%s\n\nError: %w", qbeIR, err) + } + + output_file, err := os.Open(output_file_name) + if err != nil { + return nil, err + } + defer output_file.Close() + defer os.Remove(output_file_name) + + var asmBuf bytes.Buffer + if _, err = io.Copy(&asmBuf, output_file); err != nil { + return nil, err + } + + return &asmBuf, nil +} diff --git a/pkg/codegen/qbe_backend-libqbe.go b/pkg/codegen/qbe_backend-libqbe.go new file mode 100644 index 0000000..bdf52a1 --- /dev/null +++ b/pkg/codegen/qbe_backend-libqbe.go @@ -0,0 +1,27 @@ +//go:build !windows + +package codegen + +import ( + "bytes" + "fmt" + "strings" + + "github.com/xplshn/gbc/pkg/config" + "github.com/xplshn/gbc/pkg/ir" + "modernc.org/libqbe" +) + +func (b *qbeBackend) Generate(prog *ir.Program, cfg *config.Config) (*bytes.Buffer, error) { + qbeIR, err := b.GenerateIR(prog, cfg) + if err != nil { + return nil, err + } + + var asmBuf bytes.Buffer + err = libqbe.Main(cfg.BackendTarget, "input.ssa", strings.NewReader(qbeIR), &asmBuf, nil) + if err != nil { + return nil, fmt.Errorf("\n--- QBE Compilation Failed ---\nGenerated IR:\n%s\n\nlibqbe error: %w", qbeIR, err) + } + return &asmBuf, nil +} diff --git a/pkg/codegen/qbe_backend.go b/pkg/codegen/qbe_backend.go index 76ca7c2..56fbcad 100644 --- a/pkg/codegen/qbe_backend.go +++ b/pkg/codegen/qbe_backend.go @@ -1,17 +1,15 @@ package codegen import ( - "bytes" "fmt" "strings" "github.com/xplshn/gbc/pkg/ast" "github.com/xplshn/gbc/pkg/config" "github.com/xplshn/gbc/pkg/ir" - "modernc.org/libqbe" ) -type qbeBackend struct{ +type qbeBackend struct { out *strings.Builder prog *ir.Program currentFn *ir.Func @@ -21,16 +19,6 @@ type qbeBackend struct{ func NewQBEBackend() Backend { return &qbeBackend{structTypes: make(map[string]bool)} } -func (b *qbeBackend) Generate(prog *ir.Program, cfg *config.Config) (*bytes.Buffer, error) { - qbeIR, err := b.GenerateIR(prog, cfg) - if err != nil { return nil, err } - - var asmBuf bytes.Buffer - err = libqbe.Main(cfg.BackendTarget, "input.ssa", strings.NewReader(qbeIR), &asmBuf, nil) - if err != nil { return nil, fmt.Errorf("\n--- QBE Compilation Failed ---\nGenerated IR:\n%s\n\nlibqbe error: %w", qbeIR, err) } - return &asmBuf, nil -} - func (b *qbeBackend) GenerateIR(prog *ir.Program, cfg *config.Config) (string, error) { var qbeIRBuilder strings.Builder b.out = &qbeIRBuilder @@ -59,7 +47,9 @@ func (b *qbeBackend) gen() { } for i := 0; i < len(s); i++ { - if i > 0 { b.out.WriteString(", ") } + if i > 0 { + b.out.WriteString(", ") + } b.out.WriteString(fmt.Sprintf("b %d", s[i])) } b.out.WriteString(", b 0 }\n") @@ -72,15 +62,21 @@ func (b *qbeBackend) gen() { } func (b *qbeBackend) formatFieldType(t *ast.BxType) (string, bool) { - if t == nil { return b.formatType(ir.GetType(nil, b.prog.WordSize)), true } + if t == nil { + return b.formatType(ir.GetType(nil, b.prog.WordSize)), true + } switch t.Kind { case ast.TYPE_STRUCT: if t.Name != "" { - if _, defined := b.structTypes[t.Name]; defined { return ":" + t.Name, true } + if _, defined := b.structTypes[t.Name]; defined { + return ":" + t.Name, true + } } return "", false - case ast.TYPE_POINTER, ast.TYPE_ARRAY: return b.formatType(ir.GetType(nil, b.prog.WordSize)), true - default: return b.formatType(ir.GetType(t, b.prog.WordSize)), true + case ast.TYPE_POINTER, ast.TYPE_ARRAY: + return b.formatType(ir.GetType(nil, b.prog.WordSize)), true + default: + return b.formatType(ir.GetType(t, b.prog.WordSize)), true } } @@ -427,11 +423,16 @@ func (b *qbeBackend) genCall(instr *ir.Instruction) { // Select extension operation based on source type var extOp string switch argType { - case ir.TypeW: extOp = "extsw" - case ir.TypeUB: extOp = "extub" - case ir.TypeSB: extOp = "extsb" - case ir.TypeUH: extOp = "extuh" - case ir.TypeSH: extOp = "extsh" + case ir.TypeW: + extOp = "extsw" + case ir.TypeUB: + extOp = "extub" + case ir.TypeSB: + extOp = "extsb" + case ir.TypeUH: + extOp = "extuh" + case ir.TypeSH: + extOp = "extsh" default: extOp = "extub" // Default for ambiguous b/h types } @@ -478,7 +479,8 @@ func (b *qbeBackend) formatValue(v ir.Value) string { return "" } switch val := v.(type) { - case *ir.Const: return fmt.Sprintf("%d", val.Value) + case *ir.Const: + return fmt.Sprintf("%d", val.Value) case *ir.FloatConst: if val.Typ == ir.TypeS { // For 32-bit floats, truncate to float32 precision first @@ -486,7 +488,8 @@ func (b *qbeBackend) formatValue(v ir.Value) string { return fmt.Sprintf("s_%f", float64(float32Val)) } return fmt.Sprintf("%s_%f", b.formatType(val.Typ), val.Value) - case *ir.Global: return "$" + val.Name + case *ir.Global: + return "$" + val.Name case *ir.Temporary: safeName := strings.NewReplacer(".", "_", "[", "_", "]", "_").Replace(val.Name) if val.ID == -1 { @@ -496,7 +499,8 @@ func (b *qbeBackend) formatValue(v ir.Value) string { return fmt.Sprintf("%%.%s_%d", safeName, val.ID) } return fmt.Sprintf("%%t%d", val.ID) - case *ir.Label: return "@" + val.Name + case *ir.Label: + return "@" + val.Name default: return "" } diff --git a/pkg/config/config.go b/pkg/config/config.go index 196f8f5..52107e7 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -8,7 +8,6 @@ import ( "github.com/xplshn/gbc/pkg/cli" "github.com/xplshn/gbc/pkg/token" - "modernc.org/libqbe" ) type Feature int @@ -169,13 +168,38 @@ func NewConfig() *Config { return cfg } +// Direct implementation of libqbe.DefaultTarget copied from +// https://gitlab.com/cznic/libqbe/-/blob/master/libqbe.go?ref_type=heads#L322 +// It is only included here because libqbe does NOT support windows OS + +// libqbe_defaultTarget returns the C ABI QBE target string for given goos/goarch. It +// defaults to "amd64_sysv" on unsupported goos/goarch combinations. +func libqbe_defaultTarget(goos, goarch string) string { + switch fmt.Sprintf("%s/%s", goos, goarch) { + case "darwin/amd64": + return "amd64_apple" + case "darwin/arm64": + return "arm64_apple" + case "freebsd/arm64": + return "arm64" + case "linux/arm64": + return "arm64" + case "linux/riscv64": + return "rv64" + default: + return "amd64_sysv" + } +} + func (c *Config) SetTarget(hostOS, hostArch, targetFlag string) { c.GOOS, c.GOARCH, c.BackendName = hostOS, hostArch, "qbe" if targetFlag != "" { parts := strings.SplitN(targetFlag, "/", 2) c.BackendName = parts[0] - if len(parts) > 1 { c.BackendTarget = parts[1] } + if len(parts) > 1 { + c.BackendTarget = parts[1] + } } validQBETargets := map[string]string{ @@ -185,7 +209,7 @@ func (c *Config) SetTarget(hostOS, hostArch, targetFlag string) { if c.BackendName == "qbe" { if c.BackendTarget == "" { - c.BackendTarget = libqbe.DefaultTarget(hostOS, hostArch) + c.BackendTarget = libqbe_defaultTarget(hostOS, hostArch) fmt.Fprintf(os.Stderr, "gbc: info: no target specified, defaulting to host target '%s' for backend '%s'\n", c.BackendTarget, c.BackendName) } if goArch, ok := validQBETargets[c.BackendTarget]; ok {