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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ output_*
./cmd/gtest/gtest
./cmd/gbc/gbc
./gbc

*.exe
5 changes: 5 additions & 0 deletions build.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@echo off

make %*
del gbc.exe
ren gbc gbc.exe
3 changes: 2 additions & 1 deletion cmd/gbc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Binary file added cmd/gtest/gtest.exe
Binary file not shown.
Binary file added gbc.exe
Binary file not shown.
9 changes: 7 additions & 2 deletions pkg/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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
}
Expand Down
65 changes: 65 additions & 0 deletions pkg/codegen/qbe_backend-fallback.go
Original file line number Diff line number Diff line change
@@ -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
}
27 changes: 27 additions & 0 deletions pkg/codegen/qbe_backend-libqbe.go
Original file line number Diff line number Diff line change
@@ -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
}
56 changes: 30 additions & 26 deletions pkg/codegen/qbe_backend.go
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -478,15 +479,17 @@ 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
float32Val := float32(val.Value)
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 {
Expand All @@ -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 ""
}
Expand Down
30 changes: 27 additions & 3 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/xplshn/gbc/pkg/cli"
"github.com/xplshn/gbc/pkg/token"
"modernc.org/libqbe"
)

type Feature int
Expand Down Expand Up @@ -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{
Expand All @@ -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 {
Expand Down